diff --git a/packages/payment-processor/src/payment/erc20-recurring-payment-proxy.ts b/packages/payment-processor/src/payment/erc20-recurring-payment-proxy.ts index 9fb6a0468..cb18217ef 100644 --- a/packages/payment-processor/src/payment/erc20-recurring-payment-proxy.ts +++ b/packages/payment-processor/src/payment/erc20-recurring-payment-proxy.ts @@ -4,6 +4,7 @@ import { erc20RecurringPaymentProxyArtifact } from '@requestnetwork/smart-contra import { ERC20__factory } from '@requestnetwork/smart-contracts/types'; import { getErc20Allowance } from './erc20'; +const RECURRING_PROXY_V1 = '0.1.0'; const RECURRING_PROXY_V2 = '0.2.0'; const EIP712_DOMAIN_NAME = 'ERC20RecurringPaymentProxy'; const EIP712_DOMAIN_VERSION = '1'; @@ -21,14 +22,31 @@ function getRecurringPaymentProxyInterface(version: string): utils.Interface { return new utils.Interface(erc20RecurringPaymentProxyArtifact.getContractAbi(version)); } +function resolveRecurringPaymentProxyVersion( + network: CurrencyTypes.EvmChainName, + version?: string, +): string { + if (version) { + return version; + } + if ( + erc20RecurringPaymentProxyArtifact.getOptionalDeploymentInformation(network, RECURRING_PROXY_V2) + ) { + return RECURRING_PROXY_V2; + } + return RECURRING_PROXY_V1; +} + function connectRecurringPaymentProxy( network: CurrencyTypes.EvmChainName, provider: Signer | providers.Provider, version?: string, ) { - return version - ? erc20RecurringPaymentProxyArtifact.connect(network, provider, version) - : erc20RecurringPaymentProxyArtifact.connect(network, provider); + return erc20RecurringPaymentProxyArtifact.connect( + network, + provider, + resolveRecurringPaymentProxyVersion(network, version), + ); } /** @@ -39,7 +57,7 @@ function connectRecurringPaymentProxy( * @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'`). - * @param version - Artifact version. Defaults to the artifact last version (`0.1.0`). + * @param version - Artifact version. Defaults to `0.2.0` when deployed, otherwise `0.1.0`. * * @returns A Promise that resolves to the allowance **as a decimal string** (same * units as `token.decimals`). An empty allowance is returned as `"0"`. @@ -83,7 +101,7 @@ export async function getPayerRecurringPaymentAllowance({ * @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 version - Artifact version. Defaults to the artifact last version (`0.1.0`). + * @param version - Artifact version. Defaults to `0.2.0` when deployed, otherwise `0.1.0`. * * @returns Array of transaction objects ready to be sent to the blockchain * @@ -151,7 +169,7 @@ export function encodeRecurringPaymentTrigger({ network: CurrencyTypes.EvmChainName; provider: providers.Provider | Signer; }): string { - const proxyContract = erc20RecurringPaymentProxyArtifact.connect(network, provider); + const proxyContract = connectRecurringPaymentProxy(network, provider, RECURRING_PROXY_V1); return proxyContract.interface.encodeFunctionData('triggerRecurringPayment', [ permitTuple, @@ -197,7 +215,7 @@ export async function triggerRecurringPayment({ signer: Signer; network: CurrencyTypes.EvmChainName; }): Promise { - const proxyAddress = getRecurringPaymentProxyAddress(network); + const proxyAddress = getRecurringPaymentProxyAddress(network, RECURRING_PROXY_V1); const data = encodeRecurringPaymentTrigger({ permitTuple, @@ -479,7 +497,7 @@ async function sendToRecurringProxyV2( * Returns the deployed address of the ERC20RecurringPaymentProxy contract for a given network. * * @param network - The EVM chain name (e.g. 'mainnet', 'sepolia', 'matic') - * @param version - Artifact version. Defaults to the artifact last version (`0.1.0`). + * @param version - Artifact version. Defaults to `0.2.0` when deployed, otherwise `0.1.0`. * * @returns The deployed proxy contract address for the specified network * @@ -495,9 +513,10 @@ export function getRecurringPaymentProxyAddress( network: CurrencyTypes.EvmChainName, version?: string, ): string { - const address = version - ? erc20RecurringPaymentProxyArtifact.getAddress(network, version) - : erc20RecurringPaymentProxyArtifact.getAddress(network); + const address = erc20RecurringPaymentProxyArtifact.getAddress( + network, + resolveRecurringPaymentProxyVersion(network, version), + ); if (!address) { throw new Error(`ERC20RecurringPaymentProxy not found on ${network}`); diff --git a/packages/payment-processor/test/payment/erc-20-recurring-payment.test.ts b/packages/payment-processor/test/payment/erc-20-recurring-payment.test.ts index 7ec35502d..05d783cd9 100644 --- a/packages/payment-processor/test/payment/erc-20-recurring-payment.test.ts +++ b/packages/payment-processor/test/payment/erc-20-recurring-payment.test.ts @@ -42,6 +42,7 @@ const schedulePermit: PaymentTypes.SchedulePermit = { }; const paymentReference = '0x0000000000000000000000000000000000000000000000000000000000000001'; +const RECURRING_PROXY_V1 = '0.1.0'; // Helper function to create EIP-712 signature for SchedulePermit async function createSchedulePermitSignature( @@ -128,6 +129,7 @@ describe('erc20-recurring-payment-proxy', () => { amount, provider, network, + version: RECURRING_PROXY_V1, }); expect(transactions).toHaveLength(1); @@ -148,6 +150,7 @@ describe('erc20-recurring-payment-proxy', () => { amount: '1000000000000000000', provider, network, + version: RECURRING_PROXY_V1, }); }).toThrow('ERC20RecurringPaymentProxy not found on private'); }); @@ -155,7 +158,10 @@ describe('erc20-recurring-payment-proxy', () => { describe('encodeRecurringPaymentTrigger', () => { it('should encode trigger data correctly', async () => { - const proxyAddress = erc20RecurringPaymentProxyArtifact.getAddress(network); + const proxyAddress = erc20RecurringPaymentProxyArtifact.getAddress( + network, + RECURRING_PROXY_V1, + ); const permitSignature = await createSchedulePermitSignature( schedulePermit, wallet, @@ -212,7 +218,7 @@ describe('ERC20 Recurring Payment', () => { }; it('should encode recurring payment trigger', async () => { - const proxyAddress = erc20RecurringPaymentProxyArtifact.getAddress(network); + const proxyAddress = erc20RecurringPaymentProxyArtifact.getAddress(network, RECURRING_PROXY_V1); const permitSignature = await createSchedulePermitSignature(permit, wallet, proxyAddress!); const encoded = encodeRecurringPaymentTrigger({ @@ -390,6 +396,12 @@ describe('erc20-recurring-payment-proxy 0.2.0', () => { 'ERC20RecurringPaymentProxy not found on private', ); }); + + it('falls back to 0.1.0 when version is omitted and 0.2.0 is not deployed', () => { + expect(getRecurringPaymentProxyAddress(network)).toBe( + erc20RecurringPaymentProxyArtifact.getAddress(network, RECURRING_PROXY_V1), + ); + }); }); describe('encodeSetRecurringAllowance', () => { diff --git a/packages/smart-contracts/scripts-create2/constructor-args.ts b/packages/smart-contracts/scripts-create2/constructor-args.ts index 5f02e76c6..28797f8bb 100644 --- a/packages/smart-contracts/scripts-create2/constructor-args.ts +++ b/packages/smart-contracts/scripts-create2/constructor-args.ts @@ -17,6 +17,17 @@ const getRecurringPaymentExecutorWalletAddress = (contract: string): string => { return getEnvVariable('RECURRING_PAYMENT_EXECUTOR_WALLET_ADDRESS', contract); }; +const getRecurringPaymentAdminSafe = ( + contract: string, + network: CurrencyTypes.EvmChainName, +): string => { + try { + return artifacts.safeAdminArtifact.getAddress(network); + } catch { + return getEnvVariable('RECURRING_PAYMENT_ADMIN_ADDRESS', contract); + } +}; + export const getConstructorArgs = ( contract: string, network?: CurrencyTypes.EvmChainName, @@ -94,7 +105,7 @@ export const getConstructorArgs = ( const erc20FeeProxy = artifacts.erc20FeeProxyArtifact; const erc20FeeProxyAddress = erc20FeeProxy.getAddress(network); - const adminSafe = getAdminWalletAddress(contract); + const adminSafe = getRecurringPaymentAdminSafe(contract, network); const executorEOA = getRecurringPaymentExecutorWalletAddress(contract); return [adminSafe, executorEOA, erc20FeeProxyAddress]; diff --git a/packages/smart-contracts/src/lib/artifacts/ERC20RecurringPaymentProxy/index.ts b/packages/smart-contracts/src/lib/artifacts/ERC20RecurringPaymentProxy/index.ts index 70984cc93..1921c519c 100644 --- a/packages/smart-contracts/src/lib/artifacts/ERC20RecurringPaymentProxy/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/ERC20RecurringPaymentProxy/index.ts @@ -45,8 +45,13 @@ export const erc20RecurringPaymentProxyArtifact = new ContractArtifact }, '0.2.0': { abi: ABI_0_2_0, - deployment: {}, + deployment: { + sepolia: { + address: '0xD7b1553ffE25491377a505f97f92cc44427D80A0', + creationBlockNumber: 11570049, + }, + }, }, }, - '0.1.0', + '0.2.0', ); diff --git a/packages/smart-contracts/test/lib/artifact.test.ts b/packages/smart-contracts/test/lib/artifact.test.ts index 4f2eb7c07..86f3b6595 100644 --- a/packages/smart-contracts/test/lib/artifact.test.ts +++ b/packages/smart-contracts/test/lib/artifact.test.ts @@ -59,17 +59,23 @@ describe('Artifact', () => { ); }); - it('keeps the deployed recurring proxy ABI as default while exposing version 0.2.0', () => { + it('uses 0.2.0 as the default recurring proxy ABI while keeping 0.1.0 available', () => { expect( erc20RecurringPaymentProxyArtifact .getContractAbi() - .some(({ name }) => name === 'triggerRecurringPayment'), + .some(({ name }) => name === 'triggerRecurringPaymentBatch'), ).toBe(true); expect( erc20RecurringPaymentProxyArtifact - .getContractAbi('0.2.0') - .some(({ name }) => name === 'triggerRecurringPaymentBatch'), + .getContractAbi('0.1.0') + .some(({ name }) => name === 'triggerRecurringPayment'), ).toBe(true); + expect( + erc20RecurringPaymentProxyArtifact.getOptionalDeploymentInformation('sepolia', '0.2.0'), + ).toEqual({ + address: '0xD7b1553ffE25491377a505f97f92cc44427D80A0', + creationBlockNumber: 11570049, + }); expect( erc20RecurringPaymentProxyArtifact.getOptionalDeploymentInformation('mainnet', '0.2.0'), ).toBeNull();