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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export * as Escrow from './payment/erc20-escrow-payment';
export * from './payment/prepared-transaction';
export * from './payment/utils-near';
export * from './payment/single-request-forwarder';
export * from './payment/erc20-recurring-payment-proxy';

import * as utils from './payment/utils';

Expand Down
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;
}
Comment thread
aimen74 marked this conversation as resolved.

/**
* 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;
}
Loading