Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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),
);
}

/**
Expand All @@ -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"`.
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -197,7 +215,7 @@ export async function triggerRecurringPayment({
signer: Signer;
network: CurrencyTypes.EvmChainName;
}): Promise<providers.TransactionResponse> {
const proxyAddress = getRecurringPaymentProxyAddress(network);
const proxyAddress = getRecurringPaymentProxyAddress(network, RECURRING_PROXY_V1);

const data = encodeRecurringPaymentTrigger({
permitTuple,
Expand Down Expand Up @@ -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
*
Expand All @@ -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}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -128,6 +129,7 @@ describe('erc20-recurring-payment-proxy', () => {
amount,
provider,
network,
version: RECURRING_PROXY_V1,
});

expect(transactions).toHaveLength(1);
Expand All @@ -148,14 +150,18 @@ describe('erc20-recurring-payment-proxy', () => {
amount: '1000000000000000000',
provider,
network,
version: RECURRING_PROXY_V1,
});
}).toThrow('ERC20RecurringPaymentProxy not found on private');
});
});

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,
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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', () => {
Expand Down
13 changes: 12 additions & 1 deletion packages/smart-contracts/scripts-create2/constructor-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
Comment thread
greptile-apps[bot] marked this conversation as resolved.

export const getConstructorArgs = (
contract: string,
network?: CurrencyTypes.EvmChainName,
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ export const erc20RecurringPaymentProxyArtifact = new ContractArtifact<Contract>
},
'0.2.0': {
abi: ABI_0_2_0,
deployment: {},
deployment: {
sepolia: {
address: '0xD7b1553ffE25491377a505f97f92cc44427D80A0',
creationBlockNumber: 11570049,
},
},
},
},
'0.1.0',
'0.2.0',
Comment thread
greptile-apps[bot] marked this conversation as resolved.
);
14 changes: 10 additions & 4 deletions packages/smart-contracts/test/lib/artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down