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 0000000000..749ca1dfdd --- /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 0000000000..53073d8e42 --- /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 0000000000..0b6fa491aa --- /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 280f1b04a9..20597971ad 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 */