-
Notifications
You must be signed in to change notification settings - Fork 89
feat: add recurring payment smart contract #1633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking βSign up for GitHubβ, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,855
β79
Merged
Changes from all commits
Commits
Show all changes
59 commits
Select commit
Hold shift + click to select a range
0ec1761
feat: add ERC20RecurringPaymentProxy contract for executing recurringβ¦
aimensahnoun e946bbd
feat: integrate ERC20RecurringPaymentProxy deployment into test scripβ¦
aimensahnoun 41b25ca
fix(ERC20RecurringPaymentProxy): correct execution index validation aβ¦
aimensahnoun ab83d32
feat(ERC20RecurringPaymentProxy): implement Ownable pattern for enhanβ¦
aimensahnoun c50569e
test(ERC20RecurringPaymentProxy): add comprehensive test suite for ERβ¦
aimensahnoun 5f534ba
refactor(ERC20RecurringPaymentProxy.test): streamline imports and remβ¦
aimensahnoun f738b0a
test(ERC20RecurringPaymentProxy): enhance test suite with execution sβ¦
aimensahnoun 0733e35
test(ERC20RecurringPaymentProxy): update signature generation to use β¦
aimensahnoun 5e38fb9
test(ERC20RecurringPaymentProxy): improve signature generation to supβ¦
aimensahnoun 318a8c5
test(ERC20RecurringPaymentProxy): add snapshot management for consistβ¦
aimensahnoun 97349ab
test:skip sequential test to debug failing CI
aimensahnoun b59f3ea
test(ERC20RecurringPaymentProxy): re-enable sequential payment executβ¦
aimensahnoun 0e306bd
feat: setup necessary scripts for ERC20RecurringPaymentProxy deployment
aimensahnoun 1baf1a6
feat(ERC20RecurringPaymentProxy): add function to retrieve ERC-20 allβ¦
aimensahnoun 15ac8f1
feat(ERC20RecurringPaymentProxy): add function to encode transaction β¦
aimensahnoun ab384c9
feat(ERC20RecurringPaymentProxy): implement functions for decreasing β¦
aimensahnoun 345d4f3
test(ERC20RecurringPaymentProxy): add comprehensive test suite for reβ¦
aimensahnoun e1b7d99
test(ERC20RecurringPaymentProxy): refactor allowance spy implementatiβ¦
aimensahnoun 2f8fd85
test(ERC20RecurringPaymentProxy): enhance tests for encoding approvalβ¦
aimensahnoun 2b80af8
test(ERC20RecurringPaymentProxy): remove redundant allowance tests anβ¦
aimensahnoun 001b91c
test(ERC20RecurringPaymentProxy): update test suite to use dynamic waβ¦
aimensahnoun e21ad15
fix(ERC20RecurringPaymentProxy): update error message to specify netwβ¦
aimensahnoun ad40443
chore(ERC20RecurringPaymentProxy): remove unused overrides parameter β¦
aimensahnoun 67dd8e5
refactor(ERC20RecurringPaymentProxy): simplify _hashSchedule functionβ¦
aimensahnoun 3f56cc4
docs(ERC20RecurringPaymentProxy): update documentation to reflect chaβ¦
aimensahnoun ac33544
refactor(ERC20RecurringPaymentProxy): rename gasFee to executorFee inβ¦
aimensahnoun 4597b8d
feat(ERC20RecurringPaymentProxy): implement USDT-specific approval anβ¦
aimensahnoun 012578a
refactor(ERC20RecurringPaymentProxy): consolidate approval methods inβ¦
aimensahnoun 7a9c2a2
test(ERC20RecurringPaymentProxy): add afterEach hook to restore mocksβ¦
aimensahnoun e8f35fd
Merge branch 'master' into feat/ERC20-recurring-payment-proxy
aimensahnoun 187b0f1
Merge branch 'master' of github.com:RequestNetwork/requestNetwork intβ¦
aimensahnoun 8863dce
Merge branch 'feat/ERC20-recurring-payment-proxy' of github.com:Requeβ¦
aimensahnoun 360de5f
refactor(constructor-args): extract environment variable retrieval inβ¦
aimensahnoun 1e259e4
feat(ERC20RecurringPaymentProxy): add strictOrder parameter to scheduβ¦
aimensahnoun 3f11e0b
feat(payment-types): add strictOrder property to SchedulePermit interβ¦
aimensahnoun a2a8e66
test(erc-20-recurring-payment): add strictOrder property to SchedulePβ¦
aimensahnoun c066310
fix(ERC20RecurringPaymentProxy): correct Solidity version declaration
aimensahnoun 9d45f29
test(ERC20RecurringPaymentProxy): comment out out-of-order execution β¦
aimensahnoun f6ed717
refactor(ERC20RecurringPaymentProxy): rename execution functions and β¦
aimensahnoun 098c985
test(ERC20RecurringPaymentProxy): update test cases to use BigNumber β¦
aimensahnoun 6b390ae
refactor(ERC20RecurringPaymentProxy): reorganize signer variable declβ¦
aimensahnoun 02c0f27
test(ERC20RecurringPaymentProxy): update tests to expect generic reveβ¦
aimensahnoun f676170
refactor(ERC20RecurringPaymentProxy): adjust signing logic to improveβ¦
aimensahnoun 799ea21
refactor(ERC20RecurringPaymentProxy): rename firstExec to firstPaymenβ¦
aimensahnoun ef71174
refactor(ERC20RecurringPaymentProxy): update SchedulePermit to use Biβ¦
aimensahnoun 7efe903
refactor(ERC20RecurringPaymentProxy): rename triggerRecurringPayment β¦
aimensahnoun 905fd0f
refactor(ERC20RecurringPaymentProxy): rename executor-related terms tβ¦
aimensahnoun 55f9e8b
refactor(ERC20RecurringPaymentProxy): rename firstExec to firstPaymenβ¦
aimensahnoun cd19cae
feat(ERC20RecurringPaymentProxy): add new recurring payment proxy funβ¦
aimensahnoun c5b0e1a
fix(ERC20RecurringPaymentProxy): change return type of triggerRecurriβ¦
aimensahnoun 96984d8
chore: update dependencies and configuration for hardhat-verify integβ¦
aimensahnoun 37f106e
chore: pin hardhat-verify dependency version to 2.0.14 for consistency
aimensahnoun 3fa1f5c
chore: update typescript version to 4.8.4 and adjust dependencies in β¦
aimensahnoun 69a41b0
feat(ERC20RecurringPaymentProxy): implement EIP-712 signature generatβ¦
aimensahnoun fc87fd2
feat(ERC20RecurringPaymentProxy): add base contract address and creatβ¦
aimensahnoun 9c3dfa6
test(ERC20RecurringPayment): skip recurring payment test for further β¦
aimensahnoun a641515
test(ERC20RecurringPayment): implement and enhance recurring payment β¦
aimensahnoun 0a8eb3a
fix(ERC20RecurringPaymentProxy): update documentation for triggerRecuβ¦
aimensahnoun 0bea365
test(ERC20RecurringPaymentProxy): re-enable out of order execution teβ¦
aimensahnoun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
packages/payment-processor/src/payment/erc20-recurring-payment-proxy.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import { CurrencyTypes, PaymentTypes } from '@requestnetwork/types'; | ||
import { providers, Signer, BigNumberish } from 'ethers'; | ||
import { erc20RecurringPaymentProxyArtifact } from '@requestnetwork/smart-contracts'; | ||
import { ERC20__factory } from '@requestnetwork/smart-contracts/types'; | ||
import { getErc20Allowance } from './erc20'; | ||
|
||
/** | ||
* Retrieves the current ERC-20 allowance that a subscriber (`payerAddress`) has | ||
* granted to the `ERC20RecurringPaymentProxy` on a specific network. | ||
* | ||
* @param payerAddress - Address of the token owner (subscriber) whose allowance is queried. | ||
* @param tokenAddress - Address of the ERC-20 token involved in the recurring payment schedule. | ||
* @param provider - A Web3 provider or signer used to perform the on-chain call. | ||
* @param network - The EVM chain name (e.g. `'mainnet'`, `'goerli'`, `'matic'`). | ||
* | ||
* @returns A Promise that resolves to the allowance **as a decimal string** (same | ||
* units as `token.decimals`). An empty allowance is returned as `"0"`. | ||
* | ||
* @throws {Error} If the `ERC20RecurringPaymentProxy` has no known deployment | ||
* on the provided `network`.. | ||
*/ | ||
export async function getPayerRecurringPaymentAllowance({ | ||
payerAddress, | ||
tokenAddress, | ||
provider, | ||
network, | ||
}: { | ||
payerAddress: string; | ||
tokenAddress: string; | ||
provider: Signer | providers.Provider; | ||
network: CurrencyTypes.EvmChainName; | ||
}): Promise<string> { | ||
const erc20RecurringPaymentProxy = erc20RecurringPaymentProxyArtifact.connect(network, provider); | ||
|
||
if (!erc20RecurringPaymentProxy.address) { | ||
throw new Error(`ERC20RecurringPaymentProxy not found on ${network}`); | ||
} | ||
|
||
const allowance = await getErc20Allowance( | ||
payerAddress, | ||
erc20RecurringPaymentProxy.address, | ||
provider, | ||
tokenAddress, | ||
); | ||
|
||
return allowance.toString(); | ||
} | ||
|
||
/** | ||
* Encodes the transaction data to set the allowance for the ERC20RecurringPaymentProxy. | ||
* | ||
* @param tokenAddress - The ERC20 token contract address | ||
* @param amount - The amount to approve, as a BigNumberish value | ||
* @param provider - Web3 provider or signer to interact with the blockchain | ||
* @param network - The EVM chain name where the proxy is deployed | ||
* @param isUSDT - Flag to indicate if the token is USDT, which requires special handling | ||
* | ||
* @returns Array of transaction objects ready to be sent to the blockchain | ||
* | ||
* @throws {Error} If the ERC20RecurringPaymentProxy is not deployed on the specified network | ||
* | ||
* @remarks | ||
* β’ For USDT, it returns two transactions: approve(0) and then approve(amount) | ||
* β’ For other ERC20 tokens, it returns a single approve(amount) transaction | ||
*/ | ||
export function encodeSetRecurringAllowance({ | ||
tokenAddress, | ||
amount, | ||
provider, | ||
network, | ||
isUSDT = false, | ||
}: { | ||
tokenAddress: string; | ||
amount: BigNumberish; | ||
provider: providers.Provider | Signer; | ||
network: CurrencyTypes.EvmChainName; | ||
isUSDT?: boolean; | ||
}): Array<{ to: string; data: string; value: number }> { | ||
const erc20RecurringPaymentProxy = erc20RecurringPaymentProxyArtifact.connect(network, provider); | ||
|
||
if (!erc20RecurringPaymentProxy.address) { | ||
throw new Error(`ERC20RecurringPaymentProxy not found on ${network}`); | ||
} | ||
|
||
const paymentTokenContract = ERC20__factory.connect(tokenAddress, provider); | ||
|
||
const transactions: Array<{ to: string; data: string; value: number }> = []; | ||
|
||
if (isUSDT) { | ||
const resetData = paymentTokenContract.interface.encodeFunctionData('approve', [ | ||
erc20RecurringPaymentProxy.address, | ||
0, | ||
]); | ||
transactions.push({ to: tokenAddress, data: resetData, value: 0 }); | ||
} | ||
|
||
const setData = paymentTokenContract.interface.encodeFunctionData('approve', [ | ||
erc20RecurringPaymentProxy.address, | ||
amount, | ||
]); | ||
transactions.push({ to: tokenAddress, data: setData, value: 0 }); | ||
|
||
return transactions; | ||
} | ||
|
||
/** | ||
* Encodes the transaction data to trigger a recurring payment through the ERC20RecurringPaymentProxy. | ||
* | ||
* @param permitTuple - The SchedulePermit struct data | ||
* @param permitSignature - The signature authorizing the recurring payment schedule | ||
* @param paymentIndex - The index of the payment to trigger (1-based) | ||
* @param paymentReference - Reference data for the payment | ||
* @param network - The EVM chain name where the proxy is deployed | ||
* | ||
* @returns The encoded function data as a hex string, ready to be used in a transaction | ||
* | ||
* @throws {Error} If the ERC20RecurringPaymentProxy is not deployed on the specified network | ||
* | ||
* @remarks | ||
* β’ The function only encodes the transaction data without sending it | ||
* β’ The encoded data can be used with any web3 library or multisig wallet | ||
* β’ Make sure the paymentIndex matches the expected payment sequence | ||
*/ | ||
export function encodeRecurringPaymentTrigger({ | ||
permitTuple, | ||
permitSignature, | ||
paymentIndex, | ||
paymentReference, | ||
network, | ||
provider, | ||
}: { | ||
permitTuple: PaymentTypes.SchedulePermit; | ||
permitSignature: string; | ||
paymentIndex: number; | ||
paymentReference: string; | ||
network: CurrencyTypes.EvmChainName; | ||
provider: providers.Provider | Signer; | ||
}): string { | ||
const proxyContract = erc20RecurringPaymentProxyArtifact.connect(network, provider); | ||
|
||
return proxyContract.interface.encodeFunctionData('triggerRecurringPayment', [ | ||
permitTuple, | ||
permitSignature, | ||
paymentIndex, | ||
paymentReference, | ||
]); | ||
} | ||
|
||
/** | ||
* Triggers a recurring payment through the ERC20RecurringPaymentProxy. | ||
* | ||
* @param permitTuple - The SchedulePermit struct data | ||
* @param permitSignature - The signature authorizing the recurring payment schedule | ||
* @param paymentIndex - The index of the payment to trigger (1-based) | ||
* @param paymentReference - Reference data for the payment | ||
* @param signer - The signer that will trigger the transaction (must have RELAYER_ROLE) | ||
* @param network - The EVM chain name where the proxy is deployed | ||
* | ||
* @returns A Promise resolving to the transaction response (TransactionResponse) | ||
* | ||
* @throws {Error} If the ERC20RecurringPaymentProxy is not deployed on the specified network | ||
* @throws {Error} If the transaction fails (e.g. wrong index, expired permit, insufficient allowance) | ||
* | ||
* @remarks | ||
* β’ The function returns the transaction response immediately after sending | ||
* β’ The signer must have been granted RELAYER_ROLE by the proxy admin | ||
* β’ Make sure all preconditions are met (allowance, balance, timing) before calling | ||
* β’ To wait for confirmation, call tx.wait() on the returned TransactionResponse | ||
*/ | ||
export async function triggerRecurringPayment({ | ||
permitTuple, | ||
permitSignature, | ||
paymentIndex, | ||
paymentReference, | ||
signer, | ||
network, | ||
}: { | ||
permitTuple: PaymentTypes.SchedulePermit; | ||
permitSignature: string; | ||
paymentIndex: number; | ||
paymentReference: string; | ||
signer: Signer; | ||
network: CurrencyTypes.EvmChainName; | ||
}): Promise<providers.TransactionResponse> { | ||
const proxyAddress = getRecurringPaymentProxyAddress(network); | ||
|
||
const data = encodeRecurringPaymentTrigger({ | ||
permitTuple, | ||
permitSignature, | ||
paymentIndex, | ||
paymentReference, | ||
network, | ||
provider: signer, | ||
}); | ||
|
||
const tx = await signer.sendTransaction({ | ||
to: proxyAddress, | ||
data, | ||
value: 0, | ||
}); | ||
|
||
return tx; | ||
} | ||
|
||
/** | ||
* Returns the deployed address of the ERC20RecurringPaymentProxy contract for a given network. | ||
* | ||
* @param network - The EVM chain name (e.g. 'mainnet', 'sepolia', 'matic') | ||
* | ||
* @returns The deployed proxy contract address for the specified network | ||
* | ||
* @throws {Error} If the ERC20RecurringPaymentProxy has no known deployment | ||
* on the provided network | ||
* | ||
* @remarks | ||
* β’ This is a pure helper that doesn't require a provider or make any network calls | ||
* β’ The address is looked up from the deployment artifacts maintained by the smart-contracts package | ||
* β’ Use this when you only need the address and don't need to interact with the contract | ||
*/ | ||
export function getRecurringPaymentProxyAddress(network: CurrencyTypes.EvmChainName): string { | ||
const address = erc20RecurringPaymentProxyArtifact.getAddress(network); | ||
|
||
if (!address) { | ||
throw new Error(`ERC20RecurringPaymentProxy not found on ${network}`); | ||
} | ||
|
||
return address; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.