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

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