From 8fee6fe9caa4a808ca43faa8d1d524212043b3bb Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 22 Oct 2024 13:51:16 +0300 Subject: [PATCH 01/29] feat: create `deploySingleRequestProxy` functionality in the sdk --- .../src/payment/single-request-proxy.ts | 105 +++++++ .../SingleRequestProxyFactory/0.1.0.json | 286 ++++++++++++++++++ .../SingleRequestProxyFactory/index.ts | 20 ++ .../src/lib/artifacts/index.ts | 1 + 4 files changed, 412 insertions(+) create mode 100644 packages/payment-processor/src/payment/single-request-proxy.ts create mode 100644 packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json create mode 100644 packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts new file mode 100644 index 000000000..749ca1dfd --- /dev/null +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -0,0 +1,105 @@ +import { Signer } from 'ethers'; +import { PaymentReferenceCalculator } from 'payment-detection/dist'; +import { getPaymentNetworkExtension } from 'payment-detection/src/utils'; +import { SingleRequestProxyFactory__factory } from 'smart-contracts/dist/src/types'; +import { singleRequestProxyFactoryArtifact } from 'smart-contracts/src/lib/artifacts'; +import { VMChainName } from 'types/dist/currency-types'; +import { ClientTypes, ExtensionTypes } from 'types/src'; + +interface TestOptions { + factoryAddress?: string; +} + +export async function deploySingleRequestProxy( + request: ClientTypes.IRequestData, + signer: Signer, + testOptions?: TestOptions, +): Promise { + const requestPaymentNetwork = getPaymentNetworkExtension(request); + + // Check if the payment network is supported, only ERC20_FEE_PROXY_CONTRACT and ETH_FEE_PROXY_CONTRACT are supported + if ( + !requestPaymentNetwork || + (requestPaymentNetwork.id !== ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT && + requestPaymentNetwork.id !== ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT) + ) { + throw new Error('Unsupported payment network'); + } + + let factoryAddress: string; + + // Get factory address, if provided in test options, otherwise get it from the artifacts + if (testOptions?.factoryAddress) { + factoryAddress = testOptions.factoryAddress; + } else { + const paymentChain = request.currencyInfo.network; + if (!paymentChain) { + throw new Error('Payment chain not found'); + } + factoryAddress = singleRequestProxyFactoryArtifact.getAddress(paymentChain as VMChainName); + } + + if (!factoryAddress) { + throw new Error('Single request proxy factory address not found'); + } + + const signleRequestProxyFactory = SingleRequestProxyFactory__factory.connect( + factoryAddress, + signer, + ); + + const payee = request.payee?.value; + if (!payee) { + throw new Error('Payee not found'); + } + + const salt = requestPaymentNetwork?.values?.salt; + const feeAddress = requestPaymentNetwork?.values?.feeAddress; + const feeAmount = requestPaymentNetwork?.values?.feeAmount; + + if (!salt || !feeAddress || !feeAmount) { + throw new Error('Invalid payment network values'); + } + + const paymentReference = `0x${PaymentReferenceCalculator.calculate( + request.requestId, + salt, + payee, + )}`; + + const isERC20 = + requestPaymentNetwork.id === ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT; + + let tx; + + if (isERC20) { + const tokenAddress = request.currencyInfo.value; + tx = await signleRequestProxyFactory.createERC20SingleRequestProxy( + payee, + tokenAddress, + paymentReference, + feeAddress, + feeAmount, + ); + } else { + tx = await signleRequestProxyFactory.createEthereumSingleRequestProxy( + payee, + paymentReference, + feeAddress, + feeAmount, + ); + } + + const receipt = await tx.wait(); + const event = receipt.events?.find( + (e) => + e.event === + (isERC20 ? 'ERC20SingleRequestProxyCreated' : 'EthereumSingleRequestProxyCreated'), + ); + + if (!event) { + throw new Error('Single request proxy creation event not found'); + } + + return event.args?.proxyAddress; +} diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json new file mode 100644 index 000000000..53073d8e4 --- /dev/null +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json @@ -0,0 +1,286 @@ +{ + "abi": [ + [ + { + "inputs": [ + { + "internalType": "address", + "name": "_ethereumFeeProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_erc20FeeProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newERC20FeeProxy", + "type": "address" + } + ], + "name": "ERC20FeeProxyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proxyAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "payee", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "paymentReference", + "type": "bytes" + } + ], + "name": "ERC20SingleRequestProxyCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newEthereumFeeProxy", + "type": "address" + } + ], + "name": "EthereumFeeProxyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proxyAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "payee", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "paymentReference", + "type": "bytes" + } + ], + "name": "EthereumSingleRequestProxyCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_payee", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_paymentReference", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_feeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_feeAmount", + "type": "uint256" + } + ], + "name": "createERC20SingleRequestProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_payee", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_paymentReference", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_feeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_feeAmount", + "type": "uint256" + } + ], + "name": "createEthereumSingleRequestProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "erc20FeeProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ethereumFeeProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newERC20FeeProxy", + "type": "address" + } + ], + "name": "setERC20FeeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newEthereumFeeProxy", + "type": "address" + } + ], + "name": "setEthereumFeeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] + ] +} diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts new file mode 100644 index 000000000..0b6fa491a --- /dev/null +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts @@ -0,0 +1,20 @@ +import { ContractArtifact } from '../../ContractArtifact'; + +import { abi as ABI_0_1_0 } from './0.1.0.json'; + +import type { SingleRequestProxyFactory } from '../../../types'; + +export const singleRequestProxyFactoryArtifact = new ContractArtifact( + { + '0.1.0': { + abi: ABI_0_1_0, + deployment: { + sepolia: { + address: '0x38faB0379D2D5e8120980597F1b0a443A8Ebc2AD', + creationBlockNumber: 6922054, + }, + }, + }, + }, + '0.1.0', +); diff --git a/packages/smart-contracts/src/lib/artifacts/index.ts b/packages/smart-contracts/src/lib/artifacts/index.ts index 280f1b04a..20597971a 100644 --- a/packages/smart-contracts/src/lib/artifacts/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/index.ts @@ -14,6 +14,7 @@ export * from './ERC20EscrowToPay'; export * from './BatchPayments'; export * from './BatchNoConversionPayments'; export * from './BatchConversionPayments'; +export * from './SingleRequestProxyFactory'; /** * Request Storage */ From 6ef577b8d26890758adaf708814d3758f18c9f66 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Wed, 23 Oct 2024 14:56:37 +0300 Subject: [PATCH 02/29] fix: update `SingleRequestFactory` abi --- .../SingleRequestProxyFactory/0.1.0.json | 562 +++++++++--------- 1 file changed, 280 insertions(+), 282 deletions(-) diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json index 53073d8e4..59d1284fb 100644 --- a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/0.1.0.json @@ -1,286 +1,284 @@ { "abi": [ - [ - { - "inputs": [ - { - "internalType": "address", - "name": "_ethereumFeeProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "_erc20FeeProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newERC20FeeProxy", - "type": "address" - } - ], - "name": "ERC20FeeProxyUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "proxyAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "payee", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes", - "name": "paymentReference", - "type": "bytes" - } - ], - "name": "ERC20SingleRequestProxyCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newEthereumFeeProxy", - "type": "address" - } - ], - "name": "EthereumFeeProxyUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "proxyAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "payee", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes", - "name": "paymentReference", - "type": "bytes" - } - ], - "name": "EthereumSingleRequestProxyCreated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_payee", - "type": "address" - }, - { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" - }, - { - "internalType": "bytes", - "name": "_paymentReference", - "type": "bytes" - }, - { - "internalType": "address", - "name": "_feeAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_feeAmount", - "type": "uint256" - } - ], - "name": "createERC20SingleRequestProxy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_payee", - "type": "address" - }, - { - "internalType": "bytes", - "name": "_paymentReference", - "type": "bytes" - }, - { - "internalType": "address", - "name": "_feeAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_feeAmount", - "type": "uint256" - } - ], - "name": "createEthereumSingleRequestProxy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "erc20FeeProxy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ethereumFeeProxy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newERC20FeeProxy", - "type": "address" - } - ], - "name": "setERC20FeeProxy", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newEthereumFeeProxy", - "type": "address" - } - ], - "name": "setEthereumFeeProxy", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] + { + "inputs": [ + { + "internalType": "address", + "name": "_ethereumFeeProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_erc20FeeProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newERC20FeeProxy", + "type": "address" + } + ], + "name": "ERC20FeeProxyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proxyAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "payee", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "paymentReference", + "type": "bytes" + } + ], + "name": "ERC20SingleRequestProxyCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newEthereumFeeProxy", + "type": "address" + } + ], + "name": "EthereumFeeProxyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "proxyAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "payee", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "paymentReference", + "type": "bytes" + } + ], + "name": "EthereumSingleRequestProxyCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_payee", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenAddress", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_paymentReference", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_feeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_feeAmount", + "type": "uint256" + } + ], + "name": "createERC20SingleRequestProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_payee", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_paymentReference", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_feeAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_feeAmount", + "type": "uint256" + } + ], + "name": "createEthereumSingleRequestProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "erc20FeeProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ethereumFeeProxy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newERC20FeeProxy", + "type": "address" + } + ], + "name": "setERC20FeeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newEthereumFeeProxy", + "type": "address" + } + ], + "name": "setEthereumFeeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } ] } From 05c1d5c10a0e1cd955dcabaf68a36875096f7ea5 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Wed, 23 Oct 2024 14:57:14 +0300 Subject: [PATCH 03/29] fix: read proxy address from event correctly --- packages/payment-processor/src/index.ts | 1 + .../src/payment/single-request-proxy.ts | 52 +++++++++++-------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/payment-processor/src/index.ts b/packages/payment-processor/src/index.ts index 00779e34b..838afbe10 100644 --- a/packages/payment-processor/src/index.ts +++ b/packages/payment-processor/src/index.ts @@ -28,5 +28,6 @@ export * as Escrow from './payment/erc20-escrow-payment'; export * from './payment/prepared-transaction'; export * from './payment/utils-near'; import * as utils from './payment/utils'; +export * from './payment/single-request-proxy'; export { utils }; diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 749ca1dfd..080f8b95b 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -1,10 +1,11 @@ import { Signer } from 'ethers'; -import { PaymentReferenceCalculator } from 'payment-detection/dist'; -import { getPaymentNetworkExtension } from 'payment-detection/src/utils'; -import { SingleRequestProxyFactory__factory } from 'smart-contracts/dist/src/types'; -import { singleRequestProxyFactoryArtifact } from 'smart-contracts/src/lib/artifacts'; -import { VMChainName } from 'types/dist/currency-types'; -import { ClientTypes, ExtensionTypes } from 'types/src'; +import { + PaymentReferenceCalculator, + getPaymentNetworkExtension, +} from '@requestnetwork/payment-detection'; +import { ClientTypes, ExtensionTypes, CurrencyTypes } from '@requestnetwork/types'; +import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; +import { Contract } from 'ethers'; interface TestOptions { factoryAddress?: string; @@ -26,28 +27,27 @@ export async function deploySingleRequestProxy( throw new Error('Unsupported payment network'); } - let factoryAddress: string; + let singleRequestProxyFactory; - // Get factory address, if provided in test options, otherwise get it from the artifacts if (testOptions?.factoryAddress) { - factoryAddress = testOptions.factoryAddress; + // Use custom address for testing, assuming it's on the local test network + singleRequestProxyFactory = new Contract( + testOptions.factoryAddress, + singleRequestProxyFactoryArtifact.getContractAbi(), + signer, + ); } else { const paymentChain = request.currencyInfo.network; if (!paymentChain) { throw new Error('Payment chain not found'); } - factoryAddress = singleRequestProxyFactoryArtifact.getAddress(paymentChain as VMChainName); - } - - if (!factoryAddress) { - throw new Error('Single request proxy factory address not found'); + // Use artifact's default address for the payment chain + singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect( + paymentChain as CurrencyTypes.EvmChainName, + signer, + ); } - const signleRequestProxyFactory = SingleRequestProxyFactory__factory.connect( - factoryAddress, - signer, - ); - const payee = request.payee?.value; if (!payee) { throw new Error('Payee not found'); @@ -74,7 +74,7 @@ export async function deploySingleRequestProxy( if (isERC20) { const tokenAddress = request.currencyInfo.value; - tx = await signleRequestProxyFactory.createERC20SingleRequestProxy( + tx = await singleRequestProxyFactory.createERC20SingleRequestProxy( payee, tokenAddress, paymentReference, @@ -82,7 +82,7 @@ export async function deploySingleRequestProxy( feeAmount, ); } else { - tx = await signleRequestProxyFactory.createEthereumSingleRequestProxy( + tx = await singleRequestProxyFactory.createEthereumSingleRequestProxy( payee, paymentReference, feeAddress, @@ -91,8 +91,9 @@ export async function deploySingleRequestProxy( } const receipt = await tx.wait(); + const event = receipt.events?.find( - (e) => + (e: { event: string }) => e.event === (isERC20 ? 'ERC20SingleRequestProxyCreated' : 'EthereumSingleRequestProxyCreated'), ); @@ -101,5 +102,10 @@ export async function deploySingleRequestProxy( throw new Error('Single request proxy creation event not found'); } - return event.args?.proxyAddress; + const proxyAddress = event.args?.[0]; + if (!proxyAddress) { + throw new Error('Proxy address not found in event args'); + } + + return proxyAddress; } From de3492a5cb99630599bab783316fc30f4a0406b2 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Wed, 23 Oct 2024 14:57:35 +0300 Subject: [PATCH 04/29] feat: update `inMemoryRequest` to work with `SingleRequestProxy` --- .../src/api/request-network.ts | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/request-client.js/src/api/request-network.ts b/packages/request-client.js/src/api/request-network.ts index cdadae3e7..0677d5fb4 100644 --- a/packages/request-client.js/src/api/request-network.ts +++ b/packages/request-client.js/src/api/request-network.ts @@ -81,8 +81,9 @@ export default class RequestNetwork { parameters: Types.ICreateRequestParameters, options?: Types.ICreateRequestOptions, ): Promise { - const { requestParameters, topics, paymentNetwork } = - await this.prepareRequestParameters(parameters); + const { requestParameters, topics, paymentNetwork } = await this.prepareRequestParameters( + parameters, + ); const requestLogicCreateResult = await this.requestLogic.createRequest( requestParameters, @@ -140,12 +141,11 @@ export default class RequestNetwork { 'Cannot persist request when skipPersistence is enabled. To persist the request, create a new instance of RequestNetwork without skipPersistence being set to true.', ); } - const result: DataAccessTypes.IReturnPersistTransaction = - await this.dataAccess.persistTransaction( - request.inMemoryInfo.transactionData, - request.requestId, - request.inMemoryInfo.topics, - ); + const result: DataAccessTypes.IReturnPersistTransaction = await this.dataAccess.persistTransaction( + request.inMemoryInfo.transactionData, + request.requestId, + request.inMemoryInfo.topics, + ); return result; } @@ -162,8 +162,9 @@ export default class RequestNetwork { encryptionParams: EncryptionTypes.IEncryptionParameters[], options?: Types.ICreateRequestOptions, ): Promise { - const { requestParameters, topics, paymentNetwork } = - await this.prepareRequestParameters(parameters); + const { requestParameters, topics, paymentNetwork } = await this.prepareRequestParameters( + parameters, + ); const requestLogicCreateResult = await this.requestLogic.createEncryptedRequest( requestParameters, @@ -227,8 +228,9 @@ export default class RequestNetwork { disableEvents?: boolean; }, ): Promise { - const requestAndMeta: RequestLogicTypes.IReturnGetRequestFromId = - await this.requestLogic.getRequestFromId(requestId); + const requestAndMeta: RequestLogicTypes.IReturnGetRequestFromId = await this.requestLogic.getRequestFromId( + requestId, + ); // if no request found, throw a human readable message: if (!requestAndMeta.result.request && !requestAndMeta.result.pending) { @@ -309,8 +311,10 @@ export default class RequestNetwork { options?: { disablePaymentDetection?: boolean; disableEvents?: boolean }, ): Promise { // Gets all the requests indexed by the value of the identity - const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = - await this.requestLogic.getRequestsByTopic(topic, updatedBetween); + const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = await this.requestLogic.getRequestsByTopic( + topic, + updatedBetween, + ); // From the requests of the request-logic layer creates the request objects and gets the payment networks const requestPromises = requestsAndMeta.result.requests.map( async (requestFromLogic: { @@ -322,8 +326,9 @@ export default class RequestNetwork { ? requestFromLogic.request : (requestFromLogic.pending as RequestLogicTypes.IRequest); - const paymentNetwork = - this.paymentNetworkFactory.getPaymentNetworkFromRequest(requestState); + const paymentNetwork = this.paymentNetworkFactory.getPaymentNetworkFromRequest( + requestState, + ); // create the request object const request = new Request( @@ -361,8 +366,10 @@ export default class RequestNetwork { options?: { disablePaymentDetection?: boolean; disableEvents?: boolean }, ): Promise { // Gets all the requests indexed by the value of the identity - const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = - await this.requestLogic.getRequestsByMultipleTopics(topics, updatedBetween); + const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = await this.requestLogic.getRequestsByMultipleTopics( + topics, + updatedBetween, + ); // From the requests of the request-logic layer creates the request objects and gets the payment networks const requestPromises = requestsAndMeta.result.requests.map( @@ -375,8 +382,9 @@ export default class RequestNetwork { ? requestFromLogic.request : (requestFromLogic.pending as RequestLogicTypes.IRequest); - const paymentNetwork = - this.paymentNetworkFactory.getPaymentNetworkFromRequest(requestState); + const paymentNetwork = this.paymentNetworkFactory.getPaymentNetworkFromRequest( + requestState, + ); // create the request object const request = new Request( @@ -420,7 +428,9 @@ export default class RequestNetwork { * @param parameters Parameters to create a request * @returns the parameters, ready for request creation, the topics, and the paymentNetwork */ - private async prepareRequestParameters(parameters: Types.ICreateRequestParameters): Promise<{ + private async prepareRequestParameters( + parameters: Types.ICreateRequestParameters, + ): Promise<{ requestParameters: RequestLogicTypes.ICreateParameters; topics: any[]; paymentNetwork: PaymentTypes.IPaymentNetwork | null; @@ -504,10 +514,9 @@ export default class RequestNetwork { requestId: string, ): ClientTypes.IRequestData { const requestData = JSON.parse(transactionData.data as string).data; - const originalExtensionsData = requestData.parameters.extensionsData; const newExtensions: RequestLogicTypes.IExtensionStates = {}; - for (const extension of originalExtensionsData) { + for (const extension of requestData.parameters.extensionsData) { if (extension.id !== ExtensionTypes.OTHER_ID.CONTENT_DATA) { newExtensions[extension.id] = { events: [ @@ -516,6 +525,8 @@ export default class RequestNetwork { parameters: { paymentAddress: extension.parameters.paymentAddress, salt: extension.parameters.salt, + feeAddress: extension.parameters.feeAddress, + feeAmount: extension.parameters.feeAmount, }, timestamp: requestData.parameters.timestamp, }, @@ -529,6 +540,8 @@ export default class RequestNetwork { sentPaymentAmount: '0', sentRefundAmount: '0', paymentAddress: extension.parameters.paymentAddress, + feeAddress: extension.parameters.feeAddress, + feeAmount: extension.parameters.feeAmount, }, version: extension.version, }; @@ -536,27 +549,21 @@ export default class RequestNetwork { } return { - requestId: requestId, - currency: requestData.parameters.currency.type, + ...requestData.parameters, + requestId, meta: null, balance: null, - expectedAmount: requestData.parameters.expectedAmount, - contentData: requestData.parameters.extensionsData.find( - (ext: ExtensionTypes.IAction) => ext.id === ExtensionTypes.OTHER_ID.CONTENT_DATA, - )?.parameters.content, + currency: requestData.parameters.currency.type, currencyInfo: { type: requestData.parameters.currency.type, network: requestData.parameters.currency.network, value: requestData.parameters.currency.value || '', }, + contentData: requestData.parameters.extensionsData.find( + (ext: ExtensionTypes.IAction) => ext.id === ExtensionTypes.OTHER_ID.CONTENT_DATA, + )?.parameters.content, pending: null, extensions: newExtensions, - extensionsData: requestData.parameters.extensionsData, - timestamp: requestData.parameters.timestamp, - version: requestData.parameters.version, - creator: requestData.parameters.creator, - state: requestData.parameters.state, - events: requestData.parameters.events, }; } } From e907a79af2ae6cb8a1f3dc4bcc4a502ef86235fa Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Wed, 23 Oct 2024 15:45:15 +0300 Subject: [PATCH 05/29] feat: add method to execute payments through SingleRequestProxy --- .../src/payment/single-request-proxy.ts | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 080f8b95b..1141e8b9f 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -1,16 +1,30 @@ -import { Signer } from 'ethers'; +import { Contract, Signer, ethers } from 'ethers'; import { PaymentReferenceCalculator, getPaymentNetworkExtension, } from '@requestnetwork/payment-detection'; import { ClientTypes, ExtensionTypes, CurrencyTypes } from '@requestnetwork/types'; import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; -import { Contract } from 'ethers'; +import { IERC20__factory } from '@requestnetwork/smart-contracts/types'; interface TestOptions { factoryAddress?: string; } +/** + * Deploys a Single Request Proxy contract for a given request. + * + * @param request - The request data object containing payment network and currency information. + * @param signer - The Ethereum signer used to deploy the contract. + * @param testOptions - Optional test configuration, including a custom factory address. + * @returns A Promise that resolves to the address of the deployed Single Request Proxy contract. + * @throws {Error} If the payment network is unsupported, payment chain is not found, payee is not found, or if there are invalid payment network values. + * + * @remarks + * This function supports deploying proxies for ERC20_FEE_PROXY_CONTRACT and ETH_FEE_PROXY_CONTRACT payment networks. + * It uses the SingleRequestProxyFactory contract to create either an ERC20 or Ethereum Single Request Proxy. + * The function calculates the payment reference and handles the deployment transaction, including waiting for confirmation. + */ export async function deploySingleRequestProxy( request: ClientTypes.IRequestData, signer: Signer, @@ -109,3 +123,62 @@ export async function deploySingleRequestProxy( return proxyAddress; } + +/** + * Executes a payment through a Single Request Proxy contract. + * + * @param singleRequestProxyAddress - The address of the deployed Single Request Proxy contract. + * @param signer - The Ethereum signer used to execute the payment transaction. + * @param amount - The amount to be paid, as a string representation of the value. + * @returns A Promise that resolves when the payment transaction is confirmed. + * @throws {Error} If the proxy contract type cannot be determined, or if any transaction fails. + * + * @remarks + * This function supports both ERC20 and Ethereum payments. + * For ERC20 payments, it first transfers the tokens to the proxy contract and then triggers the payment. + * For Ethereum payments, it directly sends the Ether to the proxy contract. + * The function automatically detects whether the proxy is for ERC20 or Ethereum based on the contract interface. + */ +export async function payWithSingleRequestProxy( + singleRequestProxyAddress: string, + signer: Signer, + amount: string, +): Promise { + const proxyContract = new Contract( + singleRequestProxyAddress, + ['function tokenAddress() view returns (address)'], + signer, + ); + + let isERC20: boolean; + let tokenAddress: string | null = null; + try { + tokenAddress = await proxyContract.tokenAddress(); + isERC20 = true; + } catch { + isERC20 = false; + } + + if (isERC20 && tokenAddress) { + // ERC20 payment + const erc20Contract = IERC20__factory.connect(tokenAddress, signer); + + // Transfer tokens to the proxy + const transferTx = await erc20Contract.transfer(singleRequestProxyAddress, amount); + await transferTx.wait(); + + // Trigger the receive function with 0 ETH + const triggerTx = await signer.sendTransaction({ + to: singleRequestProxyAddress, + value: ethers.constants.Zero, + }); + await triggerTx.wait(); + } else { + // Ethereum payment + const tx = await signer.sendTransaction({ + to: singleRequestProxyAddress, + value: amount, + }); + await tx.wait(); + } +} From d5dacc81a94228ac47e51de0064139fe5d4f004d Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Fri, 25 Oct 2024 14:26:37 +0300 Subject: [PATCH 06/29] test: add initial test --- .../src/payment/single-request-proxy.ts | 2 +- .../test/payment/single-request-proxy.test.ts | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 packages/payment-processor/test/payment/single-request-proxy.test.ts diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 1141e8b9f..054d0a3b9 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -139,7 +139,7 @@ export async function deploySingleRequestProxy( * For Ethereum payments, it directly sends the Ether to the proxy contract. * The function automatically detects whether the proxy is for ERC20 or Ethereum based on the contract interface. */ -export async function payWithSingleRequestProxy( +export async function payRequestWithSingleRequestProxy( singleRequestProxyAddress: string, signer: Signer, amount: string, diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts new file mode 100644 index 000000000..68aea2291 --- /dev/null +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -0,0 +1,88 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { Signer } from 'ethers'; +import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; +import { + ClientTypes, + ExtensionTypes, + RequestLogicTypes, + IdentityTypes, +} from '@requestnetwork/types'; +import { SingleRequestProxyFactory } from '@requestnetwork/smart-contracts/types'; + +describe('Single Request Proxy', () => { + let owner: Signer; + let payee: Signer; + let feeRecipient: Signer; + let singleRequestProxyFactory: SingleRequestProxyFactory; + let ownerAddress: string; + let payeeAddress: string; + let feeRecipientAddress: string; + + beforeEach(async () => { + [owner, payee, feeRecipient] = await ethers.getSigners(); + ownerAddress = await owner.getAddress(); + payeeAddress = await payee.getAddress(); + feeRecipientAddress = await feeRecipient.getAddress(); + + // Deploy SingleRequestProxyFactory + const SingleRequestProxyFactoryFactory = await ethers.getContractFactory( + 'SingleRequestProxyFactory', + ); + singleRequestProxyFactory = await SingleRequestProxyFactoryFactory.deploy( + ethers.constants.AddressZero, // Ethereum Fee Proxy address (not needed for this test) + ethers.constants.AddressZero, // ERC20 Fee Proxy address (not needed for this test) + ownerAddress, + ); + await singleRequestProxyFactory.deployed(); + }); + + it('should deploy EthereumSingleRequestProxy', async () => { + const validRequest: ClientTypes.IRequestData = { + requestId: 'abcd1234', + currency: 'ETH', + currencyInfo: { + type: RequestLogicTypes.CURRENCY.ETH, + value: 'ETH', + network: 'private', + }, + expectedAmount: '1000000000000000000', // 1 ETH + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: payeeAddress, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: ownerAddress, + }, + timestamp: Date.now(), + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { + id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + salt: 'salt1234', + paymentAddress: payeeAddress, + feeAddress: feeRecipientAddress, + feeAmount: '10000000000000000', // 0.01 ETH + }, + version: '0.1.0', + }, + }, + }; + + const proxyAddress = await deploySingleRequestProxy(validRequest, owner); + + expect(proxyAddress).to.be.properAddress; + + // Verify the deployed proxy + const EthereumSingleRequestProxy = await ethers.getContractFactory( + 'EthereumSingleRequestProxy', + ); + const deployedProxy = EthereumSingleRequestProxy.attach(proxyAddress); + + expect(await deployedProxy.payee()).to.equal(payeeAddress); + expect(await deployedProxy.feeAddress()).to.equal(feeRecipientAddress); + expect(await deployedProxy.feeAmount()).to.equal('10000000000000000'); + }); +}); From 85563be8632e1d55f0d5f89328590a18489d45ca Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Fri, 25 Oct 2024 14:56:44 +0300 Subject: [PATCH 07/29] feat: add `SingleRequestProxyFactory` to local deployment --- .../test/payment/single-request-proxy.test.ts | 88 ------------------- .../scripts/test-deploy-main-payments.ts | 11 +++ 2 files changed, 11 insertions(+), 88 deletions(-) delete mode 100644 packages/payment-processor/test/payment/single-request-proxy.test.ts diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts deleted file mode 100644 index 68aea2291..000000000 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ethers } from 'hardhat'; -import { expect } from 'chai'; -import { Signer } from 'ethers'; -import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; -import { - ClientTypes, - ExtensionTypes, - RequestLogicTypes, - IdentityTypes, -} from '@requestnetwork/types'; -import { SingleRequestProxyFactory } from '@requestnetwork/smart-contracts/types'; - -describe('Single Request Proxy', () => { - let owner: Signer; - let payee: Signer; - let feeRecipient: Signer; - let singleRequestProxyFactory: SingleRequestProxyFactory; - let ownerAddress: string; - let payeeAddress: string; - let feeRecipientAddress: string; - - beforeEach(async () => { - [owner, payee, feeRecipient] = await ethers.getSigners(); - ownerAddress = await owner.getAddress(); - payeeAddress = await payee.getAddress(); - feeRecipientAddress = await feeRecipient.getAddress(); - - // Deploy SingleRequestProxyFactory - const SingleRequestProxyFactoryFactory = await ethers.getContractFactory( - 'SingleRequestProxyFactory', - ); - singleRequestProxyFactory = await SingleRequestProxyFactoryFactory.deploy( - ethers.constants.AddressZero, // Ethereum Fee Proxy address (not needed for this test) - ethers.constants.AddressZero, // ERC20 Fee Proxy address (not needed for this test) - ownerAddress, - ); - await singleRequestProxyFactory.deployed(); - }); - - it('should deploy EthereumSingleRequestProxy', async () => { - const validRequest: ClientTypes.IRequestData = { - requestId: 'abcd1234', - currency: 'ETH', - currencyInfo: { - type: RequestLogicTypes.CURRENCY.ETH, - value: 'ETH', - network: 'private', - }, - expectedAmount: '1000000000000000000', // 1 ETH - payee: { - type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, - value: payeeAddress, - }, - payer: { - type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, - value: ownerAddress, - }, - timestamp: Date.now(), - extensions: { - [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { - id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, - type: ExtensionTypes.TYPE.PAYMENT_NETWORK, - values: { - salt: 'salt1234', - paymentAddress: payeeAddress, - feeAddress: feeRecipientAddress, - feeAmount: '10000000000000000', // 0.01 ETH - }, - version: '0.1.0', - }, - }, - }; - - const proxyAddress = await deploySingleRequestProxy(validRequest, owner); - - expect(proxyAddress).to.be.properAddress; - - // Verify the deployed proxy - const EthereumSingleRequestProxy = await ethers.getContractFactory( - 'EthereumSingleRequestProxy', - ); - const deployedProxy = EthereumSingleRequestProxy.attach(proxyAddress); - - expect(await deployedProxy.payee()).to.equal(payeeAddress); - expect(await deployedProxy.feeAddress()).to.equal(feeRecipientAddress); - expect(await deployedProxy.feeAmount()).to.equal('10000000000000000'); - }); -}); diff --git a/packages/smart-contracts/scripts/test-deploy-main-payments.ts b/packages/smart-contracts/scripts/test-deploy-main-payments.ts index d2a9b041d..204e13d68 100644 --- a/packages/smart-contracts/scripts/test-deploy-main-payments.ts +++ b/packages/smart-contracts/scripts/test-deploy-main-payments.ts @@ -87,6 +87,16 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment): const { address: EthereumFeeProxyAddress } = await deployOne(args, hre, 'EthereumFeeProxy'); console.log('EthereumFeeProxy Contract deployed: ' + EthereumFeeProxyAddress); + // Deploy SingleRequestProxyFactory contract + const { address: SingleRequestProxyFactoryAddress } = await deployOne( + args, + hre, + 'SingleRequestProxyFactory', + { + constructorArguments: [EthereumFeeProxyAddress, ERC20FeeProxyAddress, deployer.address], + }, + ); + // ---------------------------------- console.log('Contracts deployed'); console.log(` @@ -103,6 +113,7 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment): ERC20Alpha: ${erc20AlphaInstance.address} FakeSwapRouter: ${FakeSwapRouterAddress} SwapToPay: ${ERC20SwapToPayAddress} + SingleRequestProxyFactory: ${SingleRequestProxyFactoryAddress} `); return { DAIAddress: erc20AlphaInstance.address, From 4024f7bdff420e6a190ac16a5ec4bc800ec5fac7 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Fri, 25 Oct 2024 15:01:03 +0300 Subject: [PATCH 08/29] feat: add artifact information for private network --- .../src/payment/single-request-proxy.ts | 36 ++++++------------- .../SingleRequestProxyFactory/index.ts | 4 +++ 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 054d0a3b9..707c2284b 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -7,16 +7,11 @@ import { ClientTypes, ExtensionTypes, CurrencyTypes } from '@requestnetwork/type import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; import { IERC20__factory } from '@requestnetwork/smart-contracts/types'; -interface TestOptions { - factoryAddress?: string; -} - /** * Deploys a Single Request Proxy contract for a given request. * * @param request - The request data object containing payment network and currency information. * @param signer - The Ethereum signer used to deploy the contract. - * @param testOptions - Optional test configuration, including a custom factory address. * @returns A Promise that resolves to the address of the deployed Single Request Proxy contract. * @throws {Error} If the payment network is unsupported, payment chain is not found, payee is not found, or if there are invalid payment network values. * @@ -24,11 +19,11 @@ interface TestOptions { * This function supports deploying proxies for ERC20_FEE_PROXY_CONTRACT and ETH_FEE_PROXY_CONTRACT payment networks. * It uses the SingleRequestProxyFactory contract to create either an ERC20 or Ethereum Single Request Proxy. * The function calculates the payment reference and handles the deployment transaction, including waiting for confirmation. + * The factory address is automatically determined based on the payment chain using the singleRequestProxyFactoryArtifact. */ export async function deploySingleRequestProxy( request: ClientTypes.IRequestData, signer: Signer, - testOptions?: TestOptions, ): Promise { const requestPaymentNetwork = getPaymentNetworkExtension(request); @@ -41,26 +36,15 @@ export async function deploySingleRequestProxy( throw new Error('Unsupported payment network'); } - let singleRequestProxyFactory; - - if (testOptions?.factoryAddress) { - // Use custom address for testing, assuming it's on the local test network - singleRequestProxyFactory = new Contract( - testOptions.factoryAddress, - singleRequestProxyFactoryArtifact.getContractAbi(), - signer, - ); - } else { - const paymentChain = request.currencyInfo.network; - if (!paymentChain) { - throw new Error('Payment chain not found'); - } - // Use artifact's default address for the payment chain - singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect( - paymentChain as CurrencyTypes.EvmChainName, - signer, - ); + const paymentChain = request.currencyInfo.network; + if (!paymentChain) { + throw new Error('Payment chain not found'); } + // Use artifact's default address for the payment chain + const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect( + paymentChain as CurrencyTypes.EvmChainName, + signer, + ); const payee = request.payee?.value; if (!payee) { @@ -107,7 +91,7 @@ export async function deploySingleRequestProxy( const receipt = await tx.wait(); const event = receipt.events?.find( - (e: { event: string }) => + (e: ethers.Event) => e.event === (isERC20 ? 'ERC20SingleRequestProxyCreated' : 'EthereumSingleRequestProxyCreated'), ); diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts index 0b6fa491a..4979028c8 100644 --- a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts @@ -9,6 +9,10 @@ export const singleRequestProxyFactoryArtifact = new ContractArtifact Date: Fri, 25 Oct 2024 15:22:20 +0300 Subject: [PATCH 09/29] test: update tests to use private network --- .../test/payment/single-request-proxy.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/payment-processor/test/payment/single-request-proxy.test.ts diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts new file mode 100644 index 000000000..c1ee6d518 --- /dev/null +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -0,0 +1,96 @@ +// @ts-nocheck +import { providers, Wallet } from 'ethers'; +import { + ClientTypes, + ExtensionTypes, + IdentityTypes, + RequestLogicTypes, +} from '@requestnetwork/types'; +import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; +import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; + +const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; +const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; +const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; +const provider = new providers.JsonRpcProvider('http://localhost:8545'); +const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); + +const validRequest: ClientTypes.IRequestData = { + balance: { + balance: '0', + events: [], + }, + contentData: {}, + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: wallet.address, + }, + currency: 'ETH-private', + currencyInfo: { + network: 'private', + type: RequestLogicTypes.CURRENCY.ETH, + value: RequestLogicTypes.CURRENCY.ETH, + }, + events: [], + expectedAmount: '100', + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { + events: [], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress, + feeAmount: '2', + paymentAddress, + salt: 'salt', + }, + version: '0.1.0', + }, + }, + extensionsData: [], + meta: { + transactionManagerMeta: {}, + }, + pending: null, + requestId: 'abcd', + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, + version: '2.0.3', +}; + +describe('deploySingleRequestProxy', () => { + it('should deploy EthereumSingleRequestProxy and emit event', async () => { + const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); + + // Get the initial event count + const initialEventCount = await provider.getBlockNumber(); + + const proxyAddress = await deploySingleRequestProxy(validRequest, wallet); + + expect(proxyAddress).toBeDefined(); + expect(typeof proxyAddress).toBe('string'); + expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); + + // Get the latest events + const latestBlock = await provider.getBlockNumber(); + const events = await singleRequestProxyFactory.queryFilter( + singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), + initialEventCount, + latestBlock, + ); + + // Check if the event was emitted with the correct parameters + const event = events.find((e) => e.args?.proxyAddress === proxyAddress); + expect(event).toBeDefined(); + expect(event?.args?.payee).toBe(paymentAddress); + expect(event?.args?.paymentReference).toBeDefined(); + }); + + it('should throw an error if the request has no extension', async () => { + const invalidRequest = { ...validRequest, extensions: {} }; + + await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( + 'Unsupported payment network', + ); + }); +}); From 0068fb81ea90e7ce30c0fe43cb9c3af37b3a58f2 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Fri, 25 Oct 2024 15:47:54 +0300 Subject: [PATCH 10/29] fix: format issue --- .../src/api/request-network.ts | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/packages/request-client.js/src/api/request-network.ts b/packages/request-client.js/src/api/request-network.ts index 0677d5fb4..267fb2d57 100644 --- a/packages/request-client.js/src/api/request-network.ts +++ b/packages/request-client.js/src/api/request-network.ts @@ -81,9 +81,8 @@ export default class RequestNetwork { parameters: Types.ICreateRequestParameters, options?: Types.ICreateRequestOptions, ): Promise { - const { requestParameters, topics, paymentNetwork } = await this.prepareRequestParameters( - parameters, - ); + const { requestParameters, topics, paymentNetwork } = + await this.prepareRequestParameters(parameters); const requestLogicCreateResult = await this.requestLogic.createRequest( requestParameters, @@ -141,11 +140,12 @@ export default class RequestNetwork { 'Cannot persist request when skipPersistence is enabled. To persist the request, create a new instance of RequestNetwork without skipPersistence being set to true.', ); } - const result: DataAccessTypes.IReturnPersistTransaction = await this.dataAccess.persistTransaction( - request.inMemoryInfo.transactionData, - request.requestId, - request.inMemoryInfo.topics, - ); + const result: DataAccessTypes.IReturnPersistTransaction = + await this.dataAccess.persistTransaction( + request.inMemoryInfo.transactionData, + request.requestId, + request.inMemoryInfo.topics, + ); return result; } @@ -162,9 +162,8 @@ export default class RequestNetwork { encryptionParams: EncryptionTypes.IEncryptionParameters[], options?: Types.ICreateRequestOptions, ): Promise { - const { requestParameters, topics, paymentNetwork } = await this.prepareRequestParameters( - parameters, - ); + const { requestParameters, topics, paymentNetwork } = + await this.prepareRequestParameters(parameters); const requestLogicCreateResult = await this.requestLogic.createEncryptedRequest( requestParameters, @@ -228,9 +227,8 @@ export default class RequestNetwork { disableEvents?: boolean; }, ): Promise { - const requestAndMeta: RequestLogicTypes.IReturnGetRequestFromId = await this.requestLogic.getRequestFromId( - requestId, - ); + const requestAndMeta: RequestLogicTypes.IReturnGetRequestFromId = + await this.requestLogic.getRequestFromId(requestId); // if no request found, throw a human readable message: if (!requestAndMeta.result.request && !requestAndMeta.result.pending) { @@ -311,10 +309,8 @@ export default class RequestNetwork { options?: { disablePaymentDetection?: boolean; disableEvents?: boolean }, ): Promise { // Gets all the requests indexed by the value of the identity - const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = await this.requestLogic.getRequestsByTopic( - topic, - updatedBetween, - ); + const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = + await this.requestLogic.getRequestsByTopic(topic, updatedBetween); // From the requests of the request-logic layer creates the request objects and gets the payment networks const requestPromises = requestsAndMeta.result.requests.map( async (requestFromLogic: { @@ -326,9 +322,8 @@ export default class RequestNetwork { ? requestFromLogic.request : (requestFromLogic.pending as RequestLogicTypes.IRequest); - const paymentNetwork = this.paymentNetworkFactory.getPaymentNetworkFromRequest( - requestState, - ); + const paymentNetwork = + this.paymentNetworkFactory.getPaymentNetworkFromRequest(requestState); // create the request object const request = new Request( @@ -366,10 +361,8 @@ export default class RequestNetwork { options?: { disablePaymentDetection?: boolean; disableEvents?: boolean }, ): Promise { // Gets all the requests indexed by the value of the identity - const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = await this.requestLogic.getRequestsByMultipleTopics( - topics, - updatedBetween, - ); + const requestsAndMeta: RequestLogicTypes.IReturnGetRequestsByTopic = + await this.requestLogic.getRequestsByMultipleTopics(topics, updatedBetween); // From the requests of the request-logic layer creates the request objects and gets the payment networks const requestPromises = requestsAndMeta.result.requests.map( @@ -382,9 +375,8 @@ export default class RequestNetwork { ? requestFromLogic.request : (requestFromLogic.pending as RequestLogicTypes.IRequest); - const paymentNetwork = this.paymentNetworkFactory.getPaymentNetworkFromRequest( - requestState, - ); + const paymentNetwork = + this.paymentNetworkFactory.getPaymentNetworkFromRequest(requestState); // create the request object const request = new Request( @@ -428,9 +420,7 @@ export default class RequestNetwork { * @param parameters Parameters to create a request * @returns the parameters, ready for request creation, the topics, and the paymentNetwork */ - private async prepareRequestParameters( - parameters: Types.ICreateRequestParameters, - ): Promise<{ + private async prepareRequestParameters(parameters: Types.ICreateRequestParameters): Promise<{ requestParameters: RequestLogicTypes.ICreateParameters; topics: any[]; paymentNetwork: PaymentTypes.IPaymentNetwork | null; From 085dc4c32a6be6b66340267417290f565587552b Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Fri, 25 Oct 2024 15:55:28 +0300 Subject: [PATCH 11/29] chore: remove single request proxy tests --- .../test/payment/single-request-proxy.test.ts | 96 ------------------- 1 file changed, 96 deletions(-) delete mode 100644 packages/payment-processor/test/payment/single-request-proxy.test.ts diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts deleted file mode 100644 index c1ee6d518..000000000 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -// @ts-nocheck -import { providers, Wallet } from 'ethers'; -import { - ClientTypes, - ExtensionTypes, - IdentityTypes, - RequestLogicTypes, -} from '@requestnetwork/types'; -import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; -import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; - -const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; -const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; -const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; -const provider = new providers.JsonRpcProvider('http://localhost:8545'); -const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); - -const validRequest: ClientTypes.IRequestData = { - balance: { - balance: '0', - events: [], - }, - contentData: {}, - creator: { - type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, - value: wallet.address, - }, - currency: 'ETH-private', - currencyInfo: { - network: 'private', - type: RequestLogicTypes.CURRENCY.ETH, - value: RequestLogicTypes.CURRENCY.ETH, - }, - events: [], - expectedAmount: '100', - extensions: { - [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { - events: [], - id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, - type: ExtensionTypes.TYPE.PAYMENT_NETWORK, - values: { - feeAddress, - feeAmount: '2', - paymentAddress, - salt: 'salt', - }, - version: '0.1.0', - }, - }, - extensionsData: [], - meta: { - transactionManagerMeta: {}, - }, - pending: null, - requestId: 'abcd', - state: RequestLogicTypes.STATE.CREATED, - timestamp: 0, - version: '2.0.3', -}; - -describe('deploySingleRequestProxy', () => { - it('should deploy EthereumSingleRequestProxy and emit event', async () => { - const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); - - // Get the initial event count - const initialEventCount = await provider.getBlockNumber(); - - const proxyAddress = await deploySingleRequestProxy(validRequest, wallet); - - expect(proxyAddress).toBeDefined(); - expect(typeof proxyAddress).toBe('string'); - expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); - - // Get the latest events - const latestBlock = await provider.getBlockNumber(); - const events = await singleRequestProxyFactory.queryFilter( - singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), - initialEventCount, - latestBlock, - ); - - // Check if the event was emitted with the correct parameters - const event = events.find((e) => e.args?.proxyAddress === proxyAddress); - expect(event).toBeDefined(); - expect(event?.args?.payee).toBe(paymentAddress); - expect(event?.args?.paymentReference).toBeDefined(); - }); - - it('should throw an error if the request has no extension', async () => { - const invalidRequest = { ...validRequest, extensions: {} }; - - await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( - 'Unsupported payment network', - ); - }); -}); From eec04861fddc7f7f2aa32ee1b1b0b0177dfe74cf Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Sun, 27 Oct 2024 18:12:14 +0300 Subject: [PATCH 12/29] chore: remove single request proxy factory deployment from private network --- .../scripts/test-deploy-main-payments.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/smart-contracts/scripts/test-deploy-main-payments.ts b/packages/smart-contracts/scripts/test-deploy-main-payments.ts index 204e13d68..e4c2b9c31 100644 --- a/packages/smart-contracts/scripts/test-deploy-main-payments.ts +++ b/packages/smart-contracts/scripts/test-deploy-main-payments.ts @@ -88,14 +88,14 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment): console.log('EthereumFeeProxy Contract deployed: ' + EthereumFeeProxyAddress); // Deploy SingleRequestProxyFactory contract - const { address: SingleRequestProxyFactoryAddress } = await deployOne( - args, - hre, - 'SingleRequestProxyFactory', - { - constructorArguments: [EthereumFeeProxyAddress, ERC20FeeProxyAddress, deployer.address], - }, - ); + // const { address: SingleRequestProxyFactoryAddress } = await deployOne( + // args, + // hre, + // 'SingleRequestProxyFactory', + // { + // constructorArguments: [EthereumFeeProxyAddress, ERC20FeeProxyAddress, deployer.address], + // }, + // ); // ---------------------------------- console.log('Contracts deployed'); @@ -113,7 +113,6 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment): ERC20Alpha: ${erc20AlphaInstance.address} FakeSwapRouter: ${FakeSwapRouterAddress} SwapToPay: ${ERC20SwapToPayAddress} - SingleRequestProxyFactory: ${SingleRequestProxyFactoryAddress} `); return { DAIAddress: erc20AlphaInstance.address, From 6305682dc5fd8b933d3c084e06083eb3233c8779 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Sun, 27 Oct 2024 18:34:23 +0300 Subject: [PATCH 13/29] refactor: update test deployment order --- .../scripts/test-deploy-all.ts | 2 ++ .../scripts/test-deploy-main-payments.ts | 10 ------ .../test-deploy-single-request-proxy.ts | 33 +++++++++++++++++++ .../SingleRequestProxyFactory/index.ts | 2 +- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 packages/smart-contracts/scripts/test-deploy-single-request-proxy.ts diff --git a/packages/smart-contracts/scripts/test-deploy-all.ts b/packages/smart-contracts/scripts/test-deploy-all.ts index 567b0ccc2..f1a680751 100644 --- a/packages/smart-contracts/scripts/test-deploy-all.ts +++ b/packages/smart-contracts/scripts/test-deploy-all.ts @@ -7,6 +7,7 @@ import { deployBatchPayment } from './test-deploy-batch-erc-eth-deployment'; import { deploySuperFluid } from './test-deploy-superfluid'; import { deployBatchConversionPayment } from './test-deploy-batch-conversion-deployment'; import { deployERC20TransferableReceivable } from './test-deploy-erc20-transferable-receivable'; +import { deploySingleRequestProxyFactory } from './test-deploy-single-request-proxy'; // Deploys, set up the contracts export default async function deploy(_args: any, hre: HardhatRuntimeEnvironment): Promise { @@ -18,4 +19,5 @@ export default async function deploy(_args: any, hre: HardhatRuntimeEnvironment) await deploySuperFluid(hre); await deployBatchConversionPayment(_args, hre); await deployERC20TransferableReceivable(_args, hre, mainPaymentAddresses); + await deploySingleRequestProxyFactory(_args, hre, mainPaymentAddresses); } diff --git a/packages/smart-contracts/scripts/test-deploy-main-payments.ts b/packages/smart-contracts/scripts/test-deploy-main-payments.ts index e4c2b9c31..d2a9b041d 100644 --- a/packages/smart-contracts/scripts/test-deploy-main-payments.ts +++ b/packages/smart-contracts/scripts/test-deploy-main-payments.ts @@ -87,16 +87,6 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment): const { address: EthereumFeeProxyAddress } = await deployOne(args, hre, 'EthereumFeeProxy'); console.log('EthereumFeeProxy Contract deployed: ' + EthereumFeeProxyAddress); - // Deploy SingleRequestProxyFactory contract - // const { address: SingleRequestProxyFactoryAddress } = await deployOne( - // args, - // hre, - // 'SingleRequestProxyFactory', - // { - // constructorArguments: [EthereumFeeProxyAddress, ERC20FeeProxyAddress, deployer.address], - // }, - // ); - // ---------------------------------- console.log('Contracts deployed'); console.log(` diff --git a/packages/smart-contracts/scripts/test-deploy-single-request-proxy.ts b/packages/smart-contracts/scripts/test-deploy-single-request-proxy.ts new file mode 100644 index 000000000..ddeade920 --- /dev/null +++ b/packages/smart-contracts/scripts/test-deploy-single-request-proxy.ts @@ -0,0 +1,33 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { deployOne } from './deploy-one'; + +interface FeeProxyAddresses { + ERC20FeeProxyAddress: string; + ETHFeeProxyAddress: string; +} + +export async function deploySingleRequestProxyFactory( + args: any, + hre: HardhatRuntimeEnvironment, + feeProxyAddresses: FeeProxyAddresses, +) { + try { + const [deployer] = await hre.ethers.getSigners(); + const { address: SingleRequestProxyFactoryAddress } = await deployOne( + args, + hre, + 'SingleRequestProxyFactory', + { + constructorArguments: [ + feeProxyAddresses.ETHFeeProxyAddress, + feeProxyAddresses.ERC20FeeProxyAddress, + deployer.address, + ], + }, + ); + + console.log(`SingleRequestProxyFactory Contract deployed: ${SingleRequestProxyFactoryAddress}`); + } catch (e) { + console.error(e); + } +} diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts index 4979028c8..418122c47 100644 --- a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts @@ -10,7 +10,7 @@ export const singleRequestProxyFactoryArtifact = new ContractArtifact Date: Sun, 27 Oct 2024 18:43:06 +0300 Subject: [PATCH 14/29] test: initiale single request proxy test --- .../test/payment/single-request-proxy.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/payment-processor/test/payment/single-request-proxy.test.ts diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts new file mode 100644 index 000000000..c1ee6d518 --- /dev/null +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -0,0 +1,96 @@ +// @ts-nocheck +import { providers, Wallet } from 'ethers'; +import { + ClientTypes, + ExtensionTypes, + IdentityTypes, + RequestLogicTypes, +} from '@requestnetwork/types'; +import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; +import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; + +const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; +const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; +const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; +const provider = new providers.JsonRpcProvider('http://localhost:8545'); +const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); + +const validRequest: ClientTypes.IRequestData = { + balance: { + balance: '0', + events: [], + }, + contentData: {}, + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: wallet.address, + }, + currency: 'ETH-private', + currencyInfo: { + network: 'private', + type: RequestLogicTypes.CURRENCY.ETH, + value: RequestLogicTypes.CURRENCY.ETH, + }, + events: [], + expectedAmount: '100', + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { + events: [], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress, + feeAmount: '2', + paymentAddress, + salt: 'salt', + }, + version: '0.1.0', + }, + }, + extensionsData: [], + meta: { + transactionManagerMeta: {}, + }, + pending: null, + requestId: 'abcd', + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, + version: '2.0.3', +}; + +describe('deploySingleRequestProxy', () => { + it('should deploy EthereumSingleRequestProxy and emit event', async () => { + const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); + + // Get the initial event count + const initialEventCount = await provider.getBlockNumber(); + + const proxyAddress = await deploySingleRequestProxy(validRequest, wallet); + + expect(proxyAddress).toBeDefined(); + expect(typeof proxyAddress).toBe('string'); + expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); + + // Get the latest events + const latestBlock = await provider.getBlockNumber(); + const events = await singleRequestProxyFactory.queryFilter( + singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), + initialEventCount, + latestBlock, + ); + + // Check if the event was emitted with the correct parameters + const event = events.find((e) => e.args?.proxyAddress === proxyAddress); + expect(event).toBeDefined(); + expect(event?.args?.payee).toBe(paymentAddress); + expect(event?.args?.paymentReference).toBeDefined(); + }); + + it('should throw an error if the request has no extension', async () => { + const invalidRequest = { ...validRequest, extensions: {} }; + + await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( + 'Unsupported payment network', + ); + }); +}); From df6af29aa23d61fcfd15b8504f2f9b315e3bc495 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Sun, 27 Oct 2024 19:02:37 +0300 Subject: [PATCH 15/29] fix: fix valid request object --- .../test/payment/single-request-proxy.test.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index c1ee6d518..da4e5e57a 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -10,7 +10,7 @@ import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; -const paymentAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; +const paymentAddress = '0x1234567890123456789012345678901234567890'; const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; const provider = new providers.JsonRpcProvider('http://localhost:8545'); const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); @@ -25,6 +25,14 @@ const validRequest: ClientTypes.IRequestData = { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, value: wallet.address, }, + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: '0x1234567890123456789012345678901234567890', + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: '0x9876543210987654321098765432109876543210', + }, currency: 'ETH-private', currencyInfo: { network: 'private', From 8df842f0b07d8c57c2ead1a4baf6ae9b511a36e3 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 14:17:01 +0300 Subject: [PATCH 16/29] test: full test suite for `deploySingleRequestProxy` for Ethereum proxy --- .../test/payment/single-request-proxy.test.ts | 131 +++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index da4e5e57a..105f807ba 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -1,21 +1,26 @@ -// @ts-nocheck import { providers, Wallet } from 'ethers'; import { ClientTypes, ExtensionTypes, IdentityTypes, RequestLogicTypes, + CurrencyTypes, } from '@requestnetwork/types'; import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; const paymentAddress = '0x1234567890123456789012345678901234567890'; +const payerAddress = '0x91087544a744f5ffd8213323a36e073a13320714'; const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; const provider = new providers.JsonRpcProvider('http://localhost:8545'); const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); +const erc20ContractAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40'; -const validRequest: ClientTypes.IRequestData = { +export const baseRequest: Omit< + ClientTypes.IRequestData, + 'currency' | 'currencyInfo' | 'extensions' | 'version' +> = { balance: { balance: '0', events: [], @@ -27,53 +32,143 @@ const validRequest: ClientTypes.IRequestData = { }, payee: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, - value: '0x1234567890123456789012345678901234567890', + value: paymentAddress, }, payer: { type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, - value: '0x9876543210987654321098765432109876543210', + value: payerAddress, }, + events: [], + expectedAmount: '100', + extensionsData: [], + meta: { + transactionManagerMeta: {}, + }, + pending: null, + requestId: 'abcd', + state: RequestLogicTypes.STATE.CREATED, + timestamp: 0, +}; + +export const ethRequest: ClientTypes.IRequestData = { + ...baseRequest, currency: 'ETH-private', currencyInfo: { network: 'private', type: RequestLogicTypes.CURRENCY.ETH, value: RequestLogicTypes.CURRENCY.ETH, }, - events: [], - expectedAmount: '100', extensions: { [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { events: [], id: ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT, type: ExtensionTypes.TYPE.PAYMENT_NETWORK, values: { - feeAddress, + feeAddress: feeAddress, feeAmount: '2', - paymentAddress, + paymentAddress: paymentAddress, salt: 'salt', }, version: '0.1.0', }, }, - extensionsData: [], - meta: { - transactionManagerMeta: {}, - }, - pending: null, - requestId: 'abcd', - state: RequestLogicTypes.STATE.CREATED, - timestamp: 0, version: '2.0.3', }; +export const erc20Request: ClientTypes.IRequestData = { + ...baseRequest, + currency: 'DAI', + currencyInfo: { + network: 'private', + type: RequestLogicTypes.CURRENCY.ERC20, + value: erc20ContractAddress, + }, + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT]: { + events: [], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress: feeAddress, + feeAmount: '2', + paymentAddress: paymentAddress, + salt: 'salt', + }, + version: '0.1.0', + }, + }, + version: '1.0', +}; + describe('deploySingleRequestProxy', () => { + it('should throw error if payment network not supported', async () => { + // Create a request with an unsupported payment network + const invalidPaymentRequestNetwork = { + ...baseRequest, + currency: 'ETH-private', + currencyInfo: { + network: 'private' as CurrencyTypes.ChainName, + type: RequestLogicTypes.CURRENCY.ETH, + value: RequestLogicTypes.CURRENCY.ETH, + }, + version: '2.0.3', + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ANY_DECLARATIVE]: { + events: [], + id: ExtensionTypes.PAYMENT_NETWORK_ID.ANY_DECLARATIVE, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: {}, + version: '0.1.0', + }, + }, + }; + + await expect(deploySingleRequestProxy(invalidPaymentRequestNetwork, wallet)).rejects.toThrow( + 'Unsupported payment network', + ); + }); + + it('should throw error if request has no network', async () => { + const invalidRequestWithoutNetwork = { ...ethRequest, currencyInfo: {} }; + + // @ts-expect-error: Request with empty currencyInfo + await expect(deploySingleRequestProxy(invalidRequestWithoutNetwork, wallet)).rejects.toThrow( + 'Payment chain not found', + ); + }); + + it('should throw error if request has no payee', async () => { + const invalidRequestWithoutPayee = { ...ethRequest, payee: {} }; + + // @ts-expect-error: Request with empty payee + await expect(deploySingleRequestProxy(invalidRequestWithoutPayee, wallet)).rejects.toThrow( + 'Payee not found', + ); + }); + + it('should throw error if request has no network values', async () => { + const invalidRequestWithoutNetworkValues = { + ...ethRequest, + extensions: { + [ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT]: { + ...ethRequest.extensions[ExtensionTypes.PAYMENT_NETWORK_ID.ETH_FEE_PROXY_CONTRACT], + values: {}, + }, + }, + }; + + await expect( + deploySingleRequestProxy(invalidRequestWithoutNetworkValues, wallet), + ).rejects.toThrow('Invalid payment network values'); + }); + it('should deploy EthereumSingleRequestProxy and emit event', async () => { const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); // Get the initial event count const initialEventCount = await provider.getBlockNumber(); - const proxyAddress = await deploySingleRequestProxy(validRequest, wallet); + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); expect(proxyAddress).toBeDefined(); expect(typeof proxyAddress).toBe('string'); @@ -95,7 +190,7 @@ describe('deploySingleRequestProxy', () => { }); it('should throw an error if the request has no extension', async () => { - const invalidRequest = { ...validRequest, extensions: {} }; + const invalidRequest = { ...ethRequest, extensions: {} }; await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( 'Unsupported payment network', From 1fd4723363d03259b0c073719d19136d26934661 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 14:34:03 +0300 Subject: [PATCH 17/29] test: add "ERC20SingleRequestProxy" test --- .../test/payment/single-request-proxy.test.ts | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 105f807ba..ccce2e379 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -162,6 +162,14 @@ describe('deploySingleRequestProxy', () => { ).rejects.toThrow('Invalid payment network values'); }); + it('should throw an error if the request has no extension', async () => { + const invalidRequest = { ...ethRequest, extensions: {} }; + + await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( + 'Unsupported payment network', + ); + }); + it('should deploy EthereumSingleRequestProxy and emit event', async () => { const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); @@ -189,11 +197,30 @@ describe('deploySingleRequestProxy', () => { expect(event?.args?.paymentReference).toBeDefined(); }); - it('should throw an error if the request has no extension', async () => { - const invalidRequest = { ...ethRequest, extensions: {} }; + it('should deploy ERC20SingleRequestProxy and emit event', async () => { + const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); - await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( - 'Unsupported payment network', + // Get the initial event count + const initialEventCount = await provider.getBlockNumber(); + + const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); + + expect(proxyAddress).toBeDefined(); + expect(typeof proxyAddress).toBe('string'); + expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); + + // Get the latest events + const latestBlock = await provider.getBlockNumber(); + const events = await singleRequestProxyFactory.queryFilter( + singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), + initialEventCount, + latestBlock, ); + + // Check if the event was emitted with the correct parameters + const event = events.find((e) => e.args?.proxyAddress === proxyAddress); + expect(event).toBeDefined(); + expect(event?.args?.payee).toBe(paymentAddress); + expect(event?.args?.paymentReference).toBeDefined(); }); }); From ff74af21da382133f8b3e6deaadc9db7e498e872 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 14:47:18 +0300 Subject: [PATCH 18/29] feat: add more checks to validate singleRequestProxy --- .../src/payment/single-request-proxy.ts | 22 ++++++++++++++++++- .../test/payment/single-request-proxy.test.ts | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 707c2284b..d4881e272 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -115,6 +115,7 @@ export async function deploySingleRequestProxy( * @param signer - The Ethereum signer used to execute the payment transaction. * @param amount - The amount to be paid, as a string representation of the value. * @returns A Promise that resolves when the payment transaction is confirmed. + * @throws {Error} If the SingleRequestProxy contract is invalid. * @throws {Error} If the proxy contract type cannot be determined, or if any transaction fails. * * @remarks @@ -128,12 +129,31 @@ export async function payRequestWithSingleRequestProxy( signer: Signer, amount: string, ): Promise { + // Create contract interface with all required methods to validate SingleRequestProxy const proxyContract = new Contract( singleRequestProxyAddress, - ['function tokenAddress() view returns (address)'], + [ + 'function payee() view returns (address)', + 'function paymentReference() view returns (bytes)', + 'function feeAddress() view returns (address)', + 'function feeAmount() view returns (uint256)', + 'function tokenAddress() view returns (address)', + ], signer, ); + // Validate that this is a SingleRequestProxy by checking required methods + try { + await Promise.all([ + proxyContract.payee(), + proxyContract.paymentReference(), + proxyContract.feeAddress(), + proxyContract.feeAmount(), + ]); + } catch (error) { + throw new Error('Invalid SingleRequestProxy contract'); + } + let isERC20: boolean; let tokenAddress: string | null = null; try { diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index ccce2e379..1fb028929 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -212,7 +212,7 @@ describe('deploySingleRequestProxy', () => { // Get the latest events const latestBlock = await provider.getBlockNumber(); const events = await singleRequestProxyFactory.queryFilter( - singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), + singleRequestProxyFactory.filters.ERC20SingleRequestProxyCreated(), initialEventCount, latestBlock, ); From 7a0cebffc0683cd7fccb8381954703e1885908e6 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 15:15:08 +0300 Subject: [PATCH 19/29] test: add tests for paying with single request proxy --- .../test/payment/single-request-proxy.test.ts | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 1fb028929..0409a6a5a 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -1,13 +1,17 @@ -import { providers, Wallet } from 'ethers'; +import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; +import { TestERC20__factory } from '@requestnetwork/smart-contracts/types'; import { ClientTypes, + CurrencyTypes, ExtensionTypes, IdentityTypes, RequestLogicTypes, - CurrencyTypes, } from '@requestnetwork/types'; -import { deploySingleRequestProxy } from '../../src/payment/single-request-proxy'; -import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts'; +import { providers, Wallet } from 'ethers'; +import { + deploySingleRequestProxy, + payRequestWithSingleRequestProxy, +} from '../../src/payment/single-request-proxy'; const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; const paymentAddress = '0x1234567890123456789012345678901234567890'; @@ -223,4 +227,40 @@ describe('deploySingleRequestProxy', () => { expect(event?.args?.payee).toBe(paymentAddress); expect(event?.args?.paymentReference).toBeDefined(); }); + + it('should throw error when trying to pay with invalid single request proxy', async () => { + const invalidProxy = '0x1234567890123456789012345678901234567890'; + + await expect(payRequestWithSingleRequestProxy(invalidProxy, wallet, '100')).rejects.toThrow( + 'Invalid SingleRequestProxy contract', + ); + }); + + it('should pay with EthereumSingleRequestProxy', async () => { + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); + + const walletBalanceBefore = await provider.getBalance(wallet.address); + + await payRequestWithSingleRequestProxy(proxyAddress, wallet, '1000'); + + const walletBalanceAfter = await provider.getBalance(wallet.address); + + expect(walletBalanceAfter.toBigInt()).toBeLessThan(walletBalanceBefore.toBigInt()); + }); + + it('should pay with ERC20SingleRequestProxy', async () => { + const amount = '1000'; + + const testERC20 = await new TestERC20__factory(wallet).deploy(1000); + + const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); + + const initialProxyBalance = await testERC20.balanceOf(wallet.address); + + await payRequestWithSingleRequestProxy(proxyAddress, wallet, amount); + + const finalProxyBalance = await testERC20.balanceOf(wallet.address); + + expect(finalProxyBalance.toBigInt()).toBeLessThan(initialProxyBalance.toBigInt()); + }); }); From 635cc6f5d27cd61dba7671ba0c589349c0507b9f Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 15:38:47 +0300 Subject: [PATCH 20/29] fix: ERC20 payment test --- .../test/payment/single-request-proxy.test.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 0409a6a5a..0f1ac1ee6 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -249,11 +249,16 @@ describe('deploySingleRequestProxy', () => { }); it('should pay with ERC20SingleRequestProxy', async () => { - const amount = '1000'; + const amount = '200'; const testERC20 = await new TestERC20__factory(wallet).deploy(1000); - const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); + const updatedERC20Request = { + ...erc20Request, + currencyInfo: { ...erc20Request.currencyInfo, value: testERC20.address }, + }; + + const proxyAddress = await deploySingleRequestProxy(updatedERC20Request, wallet); const initialProxyBalance = await testERC20.balanceOf(wallet.address); From 0029ac4d8a6e0c3fec213898f5b85b869ec42bc3 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 16:34:48 +0300 Subject: [PATCH 21/29] fix: use paymentRecipient instead of payee identity address --- .../src/payment/single-request-proxy.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index d4881e272..af0fc04b5 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -46,23 +46,19 @@ export async function deploySingleRequestProxy( signer, ); - const payee = request.payee?.value; - if (!payee) { - throw new Error('Payee not found'); - } - const salt = requestPaymentNetwork?.values?.salt; const feeAddress = requestPaymentNetwork?.values?.feeAddress; const feeAmount = requestPaymentNetwork?.values?.feeAmount; + const paymentRecipient = requestPaymentNetwork?.values?.paymentAddress; - if (!salt || !feeAddress || !feeAmount) { + if (!salt || !feeAddress || !feeAmount || !paymentRecipient) { throw new Error('Invalid payment network values'); } const paymentReference = `0x${PaymentReferenceCalculator.calculate( request.requestId, salt, - payee, + paymentRecipient, )}`; const isERC20 = @@ -73,7 +69,7 @@ export async function deploySingleRequestProxy( if (isERC20) { const tokenAddress = request.currencyInfo.value; tx = await singleRequestProxyFactory.createERC20SingleRequestProxy( - payee, + paymentRecipient, tokenAddress, paymentReference, feeAddress, @@ -81,7 +77,7 @@ export async function deploySingleRequestProxy( ); } else { tx = await singleRequestProxyFactory.createEthereumSingleRequestProxy( - payee, + paymentRecipient, paymentReference, feeAddress, feeAmount, From 485d6b43c47f26adefbda0c44f10752f549f9ecc Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 20:28:13 +0300 Subject: [PATCH 22/29] Retrigger CI From 954e4432b2f5c9a507bcc6a8a8c89ac5ded0cb42 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Mon, 28 Oct 2024 20:39:31 +0300 Subject: [PATCH 23/29] fix: fix payee test --- .../test/payment/single-request-proxy.test.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 0f1ac1ee6..21285cca8 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -141,15 +141,6 @@ describe('deploySingleRequestProxy', () => { ); }); - it('should throw error if request has no payee', async () => { - const invalidRequestWithoutPayee = { ...ethRequest, payee: {} }; - - // @ts-expect-error: Request with empty payee - await expect(deploySingleRequestProxy(invalidRequestWithoutPayee, wallet)).rejects.toThrow( - 'Payee not found', - ); - }); - it('should throw error if request has no network values', async () => { const invalidRequestWithoutNetworkValues = { ...ethRequest, From 5ca23a8e1375d3aa2e2e74a35adc6689726d0d79 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 29 Oct 2024 00:29:37 +0300 Subject: [PATCH 24/29] refactor: update single request proxy SDK --- packages/payment-processor/src/index.ts | 3 +- .../src/payment/single-request-proxy.ts | 32 +++++++++++-------- .../test/payment/single-request-proxy.test.ts | 26 +++++++++------ .../contracts/SingleRequestProxyFactory.sol | 8 ++--- .../SingleRequestProxyFactory/index.ts | 4 +-- 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/packages/payment-processor/src/index.ts b/packages/payment-processor/src/index.ts index 838afbe10..178c46ec2 100644 --- a/packages/payment-processor/src/index.ts +++ b/packages/payment-processor/src/index.ts @@ -27,7 +27,8 @@ export * from './payment/encoder-approval'; export * as Escrow from './payment/erc20-escrow-payment'; export * from './payment/prepared-transaction'; export * from './payment/utils-near'; -import * as utils from './payment/utils'; export * from './payment/single-request-proxy'; +import * as utils from './payment/utils'; + export { utils }; diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index af0fc04b5..7b815a0cf 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -40,12 +40,17 @@ export async function deploySingleRequestProxy( if (!paymentChain) { throw new Error('Payment chain not found'); } + // Use artifact's default address for the payment chain const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect( paymentChain as CurrencyTypes.EvmChainName, signer, ); + if (!singleRequestProxyFactory.address) { + throw new Error(`SingleRequestProxyFactory not found on chain ${paymentChain}`); + } + const salt = requestPaymentNetwork?.values?.salt; const feeAddress = requestPaymentNetwork?.values?.feeAddress; const feeAmount = requestPaymentNetwork?.values?.feeAmount; @@ -125,18 +130,19 @@ export async function payRequestWithSingleRequestProxy( signer: Signer, amount: string, ): Promise { - // Create contract interface with all required methods to validate SingleRequestProxy - const proxyContract = new Contract( - singleRequestProxyAddress, - [ - 'function payee() view returns (address)', - 'function paymentReference() view returns (bytes)', - 'function feeAddress() view returns (address)', - 'function feeAmount() view returns (uint256)', - 'function tokenAddress() view returns (address)', - ], - signer, - ); + if (!amount || ethers.BigNumber.from(amount).lte(0)) { + throw new Error('Amount must be a positive number'); + } + + const proxyInterface = new ethers.utils.Interface([ + 'function payee() view returns (address)', + 'function paymentReference() view returns (bytes)', + 'function feeAddress() view returns (address)', + 'function feeAmount() view returns (uint256)', + 'function tokenAddress() view returns (address)', + ]); + + const proxyContract = new Contract(singleRequestProxyAddress, proxyInterface, signer); // Validate that this is a SingleRequestProxy by checking required methods try { @@ -167,7 +173,7 @@ export async function payRequestWithSingleRequestProxy( const transferTx = await erc20Contract.transfer(singleRequestProxyAddress, amount); await transferTx.wait(); - // Trigger the receive function with 0 ETH + // This step finalizes the payment by calling the proxy's receive function const triggerTx = await signer.sendTransaction({ to: singleRequestProxyAddress, value: ethers.constants.Zero, diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 21285cca8..b8203cb0d 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -21,7 +21,7 @@ const provider = new providers.JsonRpcProvider('http://localhost:8545'); const wallet = Wallet.fromMnemonic(mnemonic).connect(provider); const erc20ContractAddress = '0x9FBDa871d559710256a2502A2517b794B482Db40'; -export const baseRequest: Omit< +const baseRequest: Omit< ClientTypes.IRequestData, 'currency' | 'currencyInfo' | 'extensions' | 'version' > = { @@ -54,7 +54,7 @@ export const baseRequest: Omit< timestamp: 0, }; -export const ethRequest: ClientTypes.IRequestData = { +const ethRequest: ClientTypes.IRequestData = { ...baseRequest, currency: 'ETH-private', currencyInfo: { @@ -79,7 +79,7 @@ export const ethRequest: ClientTypes.IRequestData = { version: '2.0.3', }; -export const erc20Request: ClientTypes.IRequestData = { +const erc20Request: ClientTypes.IRequestData = { ...baseRequest, currency: 'DAI', currencyInfo: { @@ -107,7 +107,7 @@ export const erc20Request: ClientTypes.IRequestData = { describe('deploySingleRequestProxy', () => { it('should throw error if payment network not supported', async () => { // Create a request with an unsupported payment network - const invalidPaymentRequestNetwork = { + const invalidRequestUnsupportedPaymentNetwork = { ...baseRequest, currency: 'ETH-private', currencyInfo: { @@ -127,9 +127,9 @@ describe('deploySingleRequestProxy', () => { }, }; - await expect(deploySingleRequestProxy(invalidPaymentRequestNetwork, wallet)).rejects.toThrow( - 'Unsupported payment network', - ); + await expect( + deploySingleRequestProxy(invalidRequestUnsupportedPaymentNetwork, wallet), + ).rejects.toThrow('Unsupported payment network'); }); it('should throw error if request has no network', async () => { @@ -158,9 +158,9 @@ describe('deploySingleRequestProxy', () => { }); it('should throw an error if the request has no extension', async () => { - const invalidRequest = { ...ethRequest, extensions: {} }; + const invalidRequestWithoutExtensions = { ...ethRequest, extensions: {} }; - await expect(deploySingleRequestProxy(invalidRequest, wallet)).rejects.toThrow( + await expect(deploySingleRequestProxy(invalidRequestWithoutExtensions, wallet)).rejects.toThrow( 'Unsupported payment network', ); }); @@ -227,6 +227,14 @@ describe('deploySingleRequestProxy', () => { ); }); + it('should throw error when amount is not a positive number', async () => { + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); + + await expect(payRequestWithSingleRequestProxy(proxyAddress, wallet, '1000')).rejects.toThrow( + 'Amount must be a positive number', + ); + }); + it('should pay with EthereumSingleRequestProxy', async () => { const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); diff --git a/packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol b/packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol index 441e3739e..6ac89e9d9 100644 --- a/packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol +++ b/packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol @@ -19,14 +19,14 @@ contract SingleRequestProxyFactory is Ownable { address public erc20FeeProxy; event EthereumSingleRequestProxyCreated( - address indexed proxyAddress, - address indexed payee, + address proxyAddress, + address payee, bytes indexed paymentReference ); event ERC20SingleRequestProxyCreated( - address indexed proxyAddress, - address indexed payee, + address proxyAddress, + address payee, address tokenAddress, bytes indexed paymentReference ); diff --git a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts index 418122c47..43493b04b 100644 --- a/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/SingleRequestProxyFactory/index.ts @@ -14,8 +14,8 @@ export const singleRequestProxyFactoryArtifact = new ContractArtifact Date: Tue, 29 Oct 2024 01:09:40 +0300 Subject: [PATCH 25/29] fix: fix event handling --- .../src/payment/single-request-proxy.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 7b815a0cf..424502fc3 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -91,19 +91,16 @@ export async function deploySingleRequestProxy( const receipt = await tx.wait(); - const event = receipt.events?.find( - (e: ethers.Event) => - e.event === - (isERC20 ? 'ERC20SingleRequestProxyCreated' : 'EthereumSingleRequestProxyCreated'), - ); + const event = receipt.events?.[0]; if (!event) { throw new Error('Single request proxy creation event not found'); } - const proxyAddress = event.args?.[0]; + const proxyAddress = ethers.utils.defaultAbiCoder.decode(['address', 'address'], event.data)[0]; + if (!proxyAddress) { - throw new Error('Proxy address not found in event args'); + throw new Error('Proxy address not found in event data'); } return proxyAddress; From 57665b0263b3cf79162c15b50e2e527d4a002d7d Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 29 Oct 2024 01:20:46 +0300 Subject: [PATCH 26/29] fix: wrong payment test --- .../payment-processor/test/payment/single-request-proxy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index b8203cb0d..714d66295 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -230,7 +230,7 @@ describe('deploySingleRequestProxy', () => { it('should throw error when amount is not a positive number', async () => { const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); - await expect(payRequestWithSingleRequestProxy(proxyAddress, wallet, '1000')).rejects.toThrow( + await expect(payRequestWithSingleRequestProxy(proxyAddress, wallet, '0')).rejects.toThrow( 'Amount must be a positive number', ); }); From 9cef13e6f9cab68e0d9bd48a90bfc66c085ba94e Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 29 Oct 2024 02:13:36 +0300 Subject: [PATCH 27/29] refactor: update tests ot use new event format --- .../test/payment/single-request-proxy.test.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 714d66295..61092037f 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -7,7 +7,7 @@ import { IdentityTypes, RequestLogicTypes, } from '@requestnetwork/types'; -import { providers, Wallet } from 'ethers'; +import { providers, Wallet, utils } from 'ethers'; import { deploySingleRequestProxy, payRequestWithSingleRequestProxy, @@ -168,16 +168,16 @@ describe('deploySingleRequestProxy', () => { it('should deploy EthereumSingleRequestProxy and emit event', async () => { const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); - // Get the initial event count const initialEventCount = await provider.getBlockNumber(); + const walletAddress = await wallet.getAddress(); + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); expect(proxyAddress).toBeDefined(); expect(typeof proxyAddress).toBe('string'); expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); - // Get the latest events const latestBlock = await provider.getBlockNumber(); const events = await singleRequestProxyFactory.queryFilter( singleRequestProxyFactory.filters.EthereumSingleRequestProxyCreated(), @@ -185,26 +185,26 @@ describe('deploySingleRequestProxy', () => { latestBlock, ); - // Check if the event was emitted with the correct parameters - const event = events.find((e) => e.args?.proxyAddress === proxyAddress); - expect(event).toBeDefined(); - expect(event?.args?.payee).toBe(paymentAddress); - expect(event?.args?.paymentReference).toBeDefined(); + expect(events.length).toBeGreaterThan(0); + + const eventData = utils.defaultAbiCoder.decode(['address', 'address'], events[0].data); + + expect(eventData[0]).toBe(proxyAddress); }); it('should deploy ERC20SingleRequestProxy and emit event', async () => { const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect('private', wallet); - // Get the initial event count const initialEventCount = await provider.getBlockNumber(); + const walletAddress = await wallet.getAddress(); + const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); expect(proxyAddress).toBeDefined(); expect(typeof proxyAddress).toBe('string'); expect(proxyAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); - // Get the latest events const latestBlock = await provider.getBlockNumber(); const events = await singleRequestProxyFactory.queryFilter( singleRequestProxyFactory.filters.ERC20SingleRequestProxyCreated(), @@ -212,11 +212,11 @@ describe('deploySingleRequestProxy', () => { latestBlock, ); - // Check if the event was emitted with the correct parameters - const event = events.find((e) => e.args?.proxyAddress === proxyAddress); - expect(event).toBeDefined(); - expect(event?.args?.payee).toBe(paymentAddress); - expect(event?.args?.paymentReference).toBeDefined(); + expect(events.length).toBeGreaterThan(0); + + const eventData = utils.defaultAbiCoder.decode(['address', 'address'], events[0].data); + + expect(eventData[0]).toBe(proxyAddress); }); it('should throw error when trying to pay with invalid single request proxy', async () => { From 41d2499b44e60d75818c698921f9a1d7aa6a41d4 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 29 Oct 2024 11:23:11 +0300 Subject: [PATCH 28/29] refactor: split payment funciton into smaller functions --- .../src/payment/single-request-proxy.ts | 154 +++++++++++++----- .../test/payment/single-request-proxy.test.ts | 70 ++++++++ 2 files changed, 186 insertions(+), 38 deletions(-) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index 424502fc3..ac46436ae 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -106,6 +106,115 @@ export async function deploySingleRequestProxy( return proxyAddress; } +/** + * Validates that a contract is a SingleRequestProxy by checking required methods + * @param proxyAddress - The address of the contract to validate + * @param signer - The Ethereum signer used to interact with the contract + * @throws {Error} If the contract is not a valid SingleRequestProxy + */ +async function validateSingleRequestProxy(proxyAddress: string, signer: Signer): Promise { + const proxyInterface = new ethers.utils.Interface([ + 'function payee() view returns (address)', + 'function paymentReference() view returns (bytes)', + 'function feeAddress() view returns (address)', + 'function feeAmount() view returns (uint256)', + ]); + + const proxyContract = new Contract(proxyAddress, proxyInterface, signer); + + try { + await Promise.all([ + proxyContract.payee(), + proxyContract.paymentReference(), + proxyContract.feeAddress(), + proxyContract.feeAmount(), + ]); + } catch (error) { + throw new Error('Invalid SingleRequestProxy contract'); + } +} + +/** + * Executes a payment through an ERC20SingleRequestProxy contract + * @param proxyAddress - The address of the SingleRequestProxy contract + * @param signer - The Ethereum signer used to execute the payment transaction + * @param amount - The amount to be paid + * @throws {Error} If the contract is not an ERC20SingleRequestProxy + */ +export async function payWithERC20SingleRequestProxy( + proxyAddress: string, + signer: Signer, + amount: string, +): Promise { + if (!amount || ethers.BigNumber.from(amount).lte(0)) { + throw new Error('Amount must be a positive number'); + } + + const proxyInterface = new ethers.utils.Interface([ + 'function tokenAddress() view returns (address)', + ]); + + const proxyContract = new Contract(proxyAddress, proxyInterface, signer); + + let tokenAddress: string; + try { + tokenAddress = await proxyContract.tokenAddress(); + } catch { + throw new Error('Contract is not an ERC20SingleRequestProxy'); + } + + const erc20Contract = IERC20__factory.connect(tokenAddress, signer); + + // Transfer tokens to the proxy + const transferTx = await erc20Contract.transfer(proxyAddress, amount); + await transferTx.wait(); + + // Trigger the proxy's receive function to finalize payment + const triggerTx = await signer.sendTransaction({ + to: proxyAddress, + value: ethers.constants.Zero, + }); + await triggerTx.wait(); +} + +/** + * Executes a payment through an EthereumSingleRequestProxy contract + * @param proxyAddress - The address of the SingleRequestProxy contract + * @param signer - The Ethereum signer used to execute the payment transaction + * @param amount - The amount to be paid + * @throws {Error} If the contract is an ERC20SingleRequestProxy + */ +export async function payWithEthereumSingleRequestProxy( + proxyAddress: string, + signer: Signer, + amount: string, +): Promise { + if (!amount || ethers.BigNumber.from(amount).lte(0)) { + throw new Error('Amount must be a positive number'); + } + + const proxyInterface = new ethers.utils.Interface([ + 'function tokenAddress() view returns (address)', + ]); + + const proxyContract = new Contract(proxyAddress, proxyInterface, signer); + + try { + await proxyContract.tokenAddress(); + throw new Error('Contract is not an EthereumSingleRequestProxy'); + } catch (error) { + if (error.message === 'Contract is not an EthereumSingleRequestProxy') { + throw error; + } + } + + const tx = await signer.sendTransaction({ + to: proxyAddress, + value: amount, + }); + await tx.wait(); +} + /** * Executes a payment through a Single Request Proxy contract. * @@ -131,57 +240,26 @@ export async function payRequestWithSingleRequestProxy( throw new Error('Amount must be a positive number'); } + // Validate the SingleRequestProxy contract + await validateSingleRequestProxy(singleRequestProxyAddress, signer); + const proxyInterface = new ethers.utils.Interface([ - 'function payee() view returns (address)', - 'function paymentReference() view returns (bytes)', - 'function feeAddress() view returns (address)', - 'function feeAmount() view returns (uint256)', 'function tokenAddress() view returns (address)', ]); const proxyContract = new Contract(singleRequestProxyAddress, proxyInterface, signer); - // Validate that this is a SingleRequestProxy by checking required methods - try { - await Promise.all([ - proxyContract.payee(), - proxyContract.paymentReference(), - proxyContract.feeAddress(), - proxyContract.feeAmount(), - ]); - } catch (error) { - throw new Error('Invalid SingleRequestProxy contract'); - } - let isERC20: boolean; - let tokenAddress: string | null = null; try { - tokenAddress = await proxyContract.tokenAddress(); + await proxyContract.tokenAddress(); isERC20 = true; } catch { isERC20 = false; } - if (isERC20 && tokenAddress) { - // ERC20 payment - const erc20Contract = IERC20__factory.connect(tokenAddress, signer); - - // Transfer tokens to the proxy - const transferTx = await erc20Contract.transfer(singleRequestProxyAddress, amount); - await transferTx.wait(); - - // This step finalizes the payment by calling the proxy's receive function - const triggerTx = await signer.sendTransaction({ - to: singleRequestProxyAddress, - value: ethers.constants.Zero, - }); - await triggerTx.wait(); + if (isERC20) { + await payWithERC20SingleRequestProxy(singleRequestProxyAddress, signer, amount); } else { - // Ethereum payment - const tx = await signer.sendTransaction({ - to: singleRequestProxyAddress, - value: amount, - }); - await tx.wait(); + await payWithEthereumSingleRequestProxy(singleRequestProxyAddress, signer, amount); } } diff --git a/packages/payment-processor/test/payment/single-request-proxy.test.ts b/packages/payment-processor/test/payment/single-request-proxy.test.ts index 61092037f..6f5cf0f27 100644 --- a/packages/payment-processor/test/payment/single-request-proxy.test.ts +++ b/packages/payment-processor/test/payment/single-request-proxy.test.ts @@ -11,6 +11,8 @@ import { providers, Wallet, utils } from 'ethers'; import { deploySingleRequestProxy, payRequestWithSingleRequestProxy, + payWithEthereumSingleRequestProxy, + payWithERC20SingleRequestProxy, } from '../../src/payment/single-request-proxy'; const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; @@ -268,3 +270,71 @@ describe('deploySingleRequestProxy', () => { expect(finalProxyBalance.toBigInt()).toBeLessThan(initialProxyBalance.toBigInt()); }); }); + +describe('payWithEthereumSingleRequestProxy', () => { + it('should throw error when amount is not a positive number', async () => { + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); + + await expect(payWithEthereumSingleRequestProxy(proxyAddress, wallet, '0')).rejects.toThrow( + 'Amount must be a positive number', + ); + }); + + it('should throw error when contract is an ERC20SingleRequestProxy', async () => { + const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); + + await expect(payWithEthereumSingleRequestProxy(proxyAddress, wallet, '1000')).rejects.toThrow( + 'Contract is not an EthereumSingleRequestProxy', + ); + }); + + it('should successfully pay with ETH', async () => { + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); + const amount = '1000'; + + const walletBalanceBefore = await provider.getBalance(wallet.address); + + await payWithEthereumSingleRequestProxy(proxyAddress, wallet, amount); + + const walletBalanceAfter = await provider.getBalance(wallet.address); + + expect(walletBalanceAfter.toBigInt()).toBeLessThan(walletBalanceBefore.toBigInt()); + }); +}); + +describe('payWithERC20SingleRequestProxy', () => { + it('should throw error when amount is not a positive number', async () => { + const proxyAddress = await deploySingleRequestProxy(erc20Request, wallet); + + await expect(payWithERC20SingleRequestProxy(proxyAddress, wallet, '0')).rejects.toThrow( + 'Amount must be a positive number', + ); + }); + + it('should throw error when contract is not an ERC20SingleRequestProxy', async () => { + const proxyAddress = await deploySingleRequestProxy(ethRequest, wallet); + + await expect(payWithERC20SingleRequestProxy(proxyAddress, wallet, '1000')).rejects.toThrow( + 'Contract is not an ERC20SingleRequestProxy', + ); + }); + + it('should successfully pay with ERC20 tokens', async () => { + const amount = '200'; + const testERC20 = await new TestERC20__factory(wallet).deploy(1000); + + const updatedERC20Request = { + ...erc20Request, + currencyInfo: { ...erc20Request.currencyInfo, value: testERC20.address }, + }; + + const proxyAddress = await deploySingleRequestProxy(updatedERC20Request, wallet); + const initialBalance = await testERC20.balanceOf(wallet.address); + + await payWithERC20SingleRequestProxy(proxyAddress, wallet, amount); + + const finalBalance = await testERC20.balanceOf(wallet.address); + + expect(finalBalance.toBigInt()).toBeLessThan(initialBalance.toBigInt()); + }); +}); From 958ebf186763bc9185fe3886fb771481ff1dfde8 Mon Sep 17 00:00:00 2001 From: Aimen Sahnoun Date: Tue, 29 Oct 2024 12:01:30 +0300 Subject: [PATCH 29/29] chore: add comments --- .../payment-processor/src/payment/single-request-proxy.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/payment-processor/src/payment/single-request-proxy.ts b/packages/payment-processor/src/payment/single-request-proxy.ts index ac46436ae..eb5e4f62b 100644 --- a/packages/payment-processor/src/payment/single-request-proxy.ts +++ b/packages/payment-processor/src/payment/single-request-proxy.ts @@ -158,6 +158,7 @@ export async function payWithERC20SingleRequestProxy( let tokenAddress: string; try { + // Attempt to fetch the token address from the proxy contract, to determine if it's an ERC20 SingleRequestProxy. tokenAddress = await proxyContract.tokenAddress(); } catch { throw new Error('Contract is not an ERC20SingleRequestProxy'); @@ -200,10 +201,15 @@ export async function payWithEthereumSingleRequestProxy( const proxyContract = new Contract(proxyAddress, proxyInterface, signer); try { + // Attempt to fetch the token address from the proxy contract, to determine if it's an Ethereum SingleRequestProxy. await proxyContract.tokenAddress(); + + // If the token address is fetched, it means the contract is an ERC20SingleRequestProxy. throw new Error('Contract is not an EthereumSingleRequestProxy'); } catch (error) { + // If the token address is not fetched, it means the contract is an EthereumSingleRequestProxy. if (error.message === 'Contract is not an EthereumSingleRequestProxy') { + // If the error message is 'Contract is not an EthereumSingleRequestProxy', throw the error. throw error; } }