-
Notifications
You must be signed in to change notification settings - Fork 91
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
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β¦
aimen74 e946bbd
feat: integrate ERC20RecurringPaymentProxy deployment into test scripβ¦
aimen74 41b25ca
fix(ERC20RecurringPaymentProxy): correct execution index validation aβ¦
aimen74 ab83d32
feat(ERC20RecurringPaymentProxy): implement Ownable pattern for enhanβ¦
aimen74 c50569e
test(ERC20RecurringPaymentProxy): add comprehensive test suite for ERβ¦
aimen74 5f534ba
refactor(ERC20RecurringPaymentProxy.test): streamline imports and remβ¦
aimen74 f738b0a
test(ERC20RecurringPaymentProxy): enhance test suite with execution sβ¦
aimen74 0733e35
test(ERC20RecurringPaymentProxy): update signature generation to use β¦
aimen74 5e38fb9
test(ERC20RecurringPaymentProxy): improve signature generation to supβ¦
aimen74 318a8c5
test(ERC20RecurringPaymentProxy): add snapshot management for consistβ¦
aimen74 97349ab
test:skip sequential test to debug failing CI
aimen74 b59f3ea
test(ERC20RecurringPaymentProxy): re-enable sequential payment executβ¦
aimen74 0e306bd
feat: setup necessary scripts for ERC20RecurringPaymentProxy deployment
aimen74 1baf1a6
feat(ERC20RecurringPaymentProxy): add function to retrieve ERC-20 allβ¦
aimen74 15ac8f1
feat(ERC20RecurringPaymentProxy): add function to encode transaction β¦
aimen74 ab384c9
feat(ERC20RecurringPaymentProxy): implement functions for decreasing β¦
aimen74 345d4f3
test(ERC20RecurringPaymentProxy): add comprehensive test suite for reβ¦
aimen74 e1b7d99
test(ERC20RecurringPaymentProxy): refactor allowance spy implementatiβ¦
aimen74 2f8fd85
test(ERC20RecurringPaymentProxy): enhance tests for encoding approvalβ¦
aimen74 2b80af8
test(ERC20RecurringPaymentProxy): remove redundant allowance tests anβ¦
aimen74 001b91c
test(ERC20RecurringPaymentProxy): update test suite to use dynamic waβ¦
aimen74 e21ad15
fix(ERC20RecurringPaymentProxy): update error message to specify netwβ¦
aimen74 ad40443
chore(ERC20RecurringPaymentProxy): remove unused overrides parameter β¦
aimen74 67dd8e5
refactor(ERC20RecurringPaymentProxy): simplify _hashSchedule functionβ¦
aimen74 3f56cc4
docs(ERC20RecurringPaymentProxy): update documentation to reflect chaβ¦
aimen74 ac33544
refactor(ERC20RecurringPaymentProxy): rename gasFee to executorFee inβ¦
aimen74 4597b8d
feat(ERC20RecurringPaymentProxy): implement USDT-specific approval anβ¦
aimen74 012578a
refactor(ERC20RecurringPaymentProxy): consolidate approval methods inβ¦
aimen74 7a9c2a2
test(ERC20RecurringPaymentProxy): add afterEach hook to restore mocksβ¦
aimen74 e8f35fd
Merge branch 'master' into feat/ERC20-recurring-payment-proxy
aimen74 187b0f1
Merge branch 'master' of github.com:RequestNetwork/requestNetwork intβ¦
aimen74 8863dce
Merge branch 'feat/ERC20-recurring-payment-proxy' of github.com:Requeβ¦
aimen74 360de5f
refactor(constructor-args): extract environment variable retrieval inβ¦
aimen74 1e259e4
feat(ERC20RecurringPaymentProxy): add strictOrder parameter to scheduβ¦
aimen74 3f11e0b
feat(payment-types): add strictOrder property to SchedulePermit interβ¦
aimen74 a2a8e66
test(erc-20-recurring-payment): add strictOrder property to SchedulePβ¦
aimen74 c066310
fix(ERC20RecurringPaymentProxy): correct Solidity version declaration
aimen74 9d45f29
test(ERC20RecurringPaymentProxy): comment out out-of-order execution β¦
aimen74 f6ed717
refactor(ERC20RecurringPaymentProxy): rename execution functions and β¦
aimen74 098c985
test(ERC20RecurringPaymentProxy): update test cases to use BigNumber β¦
aimen74 6b390ae
refactor(ERC20RecurringPaymentProxy): reorganize signer variable declβ¦
aimen74 02c0f27
test(ERC20RecurringPaymentProxy): update tests to expect generic reveβ¦
aimen74 f676170
refactor(ERC20RecurringPaymentProxy): adjust signing logic to improveβ¦
aimen74 799ea21
refactor(ERC20RecurringPaymentProxy): rename firstExec to firstPaymenβ¦
aimen74 ef71174
refactor(ERC20RecurringPaymentProxy): update SchedulePermit to use Biβ¦
aimen74 7efe903
refactor(ERC20RecurringPaymentProxy): rename triggerRecurringPayment β¦
aimen74 905fd0f
refactor(ERC20RecurringPaymentProxy): rename executor-related terms tβ¦
aimen74 55f9e8b
refactor(ERC20RecurringPaymentProxy): rename firstExec to firstPaymenβ¦
aimen74 cd19cae
feat(ERC20RecurringPaymentProxy): add new recurring payment proxy funβ¦
aimen74 c5b0e1a
fix(ERC20RecurringPaymentProxy): change return type of triggerRecurriβ¦
aimen74 96984d8
chore: update dependencies and configuration for hardhat-verify integβ¦
aimen74 37f106e
chore: pin hardhat-verify dependency version to 2.0.14 for consistency
aimen74 3fa1f5c
chore: update typescript version to 4.8.4 and adjust dependencies in β¦
aimen74 69a41b0
feat(ERC20RecurringPaymentProxy): implement EIP-712 signature generatβ¦
aimen74 fc87fd2
feat(ERC20RecurringPaymentProxy): add base contract address and creatβ¦
aimen74 9c3dfa6
test(ERC20RecurringPayment): skip recurring payment test for further β¦
aimen74 a641515
test(ERC20RecurringPayment): implement and enhance recurring payment β¦
aimen74 0a8eb3a
fix(ERC20RecurringPaymentProxy): update documentation for triggerRecuβ¦
aimen74 0bea365
test(ERC20RecurringPaymentProxy): re-enable out of order execution teβ¦
aimen74 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.