Skip to content

Commit

Permalink
chore: rename single request proxy (#1488)
Browse files Browse the repository at this point in the history
  • Loading branch information
aimensahnoun authored Nov 12, 2024
1 parent 0d77e49 commit 9af0ebd
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 179 deletions.
2 changes: 1 addition & 1 deletion packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export * from './payment/encoder-approval';
export * as Escrow from './payment/erc20-escrow-payment';
export * from './payment/prepared-transaction';
export * from './payment/utils-near';
export * from './payment/single-request-proxy';
export * from './payment/single-request-forwarder';

import * as utils from './payment/utils';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ import {
getPaymentNetworkExtension,
} from '@requestnetwork/payment-detection';
import { ClientTypes, ExtensionTypes, CurrencyTypes } from '@requestnetwork/types';
import { singleRequestProxyFactoryArtifact } from '@requestnetwork/smart-contracts';
import { singleRequestForwarderFactoryArtifact } from '@requestnetwork/smart-contracts';
import { IERC20__factory } from '@requestnetwork/smart-contracts/types';

/**
* Deploys a Single Request Proxy contract for a given request.
* Deploys a Single Request Forwarder 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.
* @returns A Promise that resolves to the address of the deployed Single Request Proxy contract.
* @returns A Promise that resolves to the address of the deployed Single Request Forwarder 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.
* This function supports deploying forwarders for ERC20_FEE_PROXY_CONTRACT and ETH_FEE_PROXY_CONTRACT payment networks.
* It uses the SingleRequestForwarderFactory contract to create either an ERC20 or Ethereum Single Request Forwarder.
* 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.
* The factory address is automatically determined based on the payment chain using the singleRequestForwarderFactoryArtifact.
*/
export async function deploySingleRequestProxy(
export async function deploySingleRequestForwarder(
request: ClientTypes.IRequestData,
signer: Signer,
): Promise<string> {
Expand All @@ -42,13 +42,13 @@ export async function deploySingleRequestProxy(
}

// Use artifact's default address for the payment chain
const singleRequestProxyFactory = singleRequestProxyFactoryArtifact.connect(
const singleRequestForwarderFactory = singleRequestForwarderFactoryArtifact.connect(
paymentChain as CurrencyTypes.EvmChainName,
signer,
);

if (!singleRequestProxyFactory.address) {
throw new Error(`SingleRequestProxyFactory not found on chain ${paymentChain}`);
if (!singleRequestForwarderFactory.address) {
throw new Error(`SingleRequestForwarderFactory not found on chain ${paymentChain}`);
}

const salt = requestPaymentNetwork?.values?.salt;
Expand All @@ -73,15 +73,15 @@ export async function deploySingleRequestProxy(

if (isERC20) {
const tokenAddress = request.currencyInfo.value;
tx = await singleRequestProxyFactory.createERC20SingleRequestProxy(
tx = await singleRequestForwarderFactory.createERC20SingleRequestProxy(
paymentRecipient,
tokenAddress,
paymentReference,
feeAddress,
feeAmount,
);
} else {
tx = await singleRequestProxyFactory.createEthereumSingleRequestProxy(
tx = await singleRequestForwarderFactory.createEthereumSingleRequestProxy(
paymentRecipient,
paymentReference,
feeAddress,
Expand All @@ -92,134 +92,137 @@ export async function deploySingleRequestProxy(
const receipt = await tx.wait();

const event = receipt.events?.find(
(e) =>
(e: ethers.Event) =>
e.event ===
(isERC20 ? 'ERC20SingleRequestProxyCreated' : 'EthereumSingleRequestProxyCreated'),
);

if (!event) {
throw new Error('Single request proxy creation event not found');
throw new Error('Single request forwarder creation event not found');
}

const proxyAddress = event.args?.proxyAddress || event.args?.[0];
const forwarderAddress = event.args?.proxyAddress || event.args?.[0];

if (!proxyAddress) {
throw new Error('Proxy address not found in event data');
if (!forwarderAddress) {
throw new Error('Forwarder address not found in event data');
}

return proxyAddress;
return forwarderAddress;
}

/**
* Validates that a contract is a SingleRequestProxy by checking required methods
* Validates that a contract is a SingleRequestForwarder 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
* @throws {Error} If the contract is not a valid SingleRequestForwarder
*/
async function validateSingleRequestProxy(proxyAddress: string, signer: Signer): Promise<void> {
const proxyInterface = new ethers.utils.Interface([
async function validateSingleRequestForwarder(
forwarderAddress: string,
signer: Signer,
): Promise<void> {
const forwarderInterface = 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);
const forwarderContract = new Contract(forwarderAddress, forwarderInterface, signer);

try {
await Promise.all([
proxyContract.payee(),
proxyContract.paymentReference(),
proxyContract.feeAddress(),
proxyContract.feeAmount(),
forwarderContract.payee(),
forwarderContract.paymentReference(),
forwarderContract.feeAddress(),
forwarderContract.feeAmount(),
]);
} catch (error) {
throw new Error('Invalid SingleRequestProxy contract');
throw new Error('Invalid SingleRequestForwarder contract');
}
}

/**
* Executes a payment through an ERC20SingleRequestProxy contract
* @param proxyAddress - The address of the SingleRequestProxy contract
* Executes a payment through an ERC20SingleRequestForwarder contract
* @param forwarderAddress - The address of the SingleRequestForwarder 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
* @throws {Error} If the contract is not an ERC20SingleRequestForwarder
*/
export async function payWithERC20SingleRequestProxy(
proxyAddress: string,
export async function payWithERC20SingleRequestForwarder(
forwarderAddress: string,
signer: Signer,
amount: string,
): Promise<void> {
if (!amount || ethers.BigNumber.from(amount).lte(0)) {
throw new Error('Amount must be a positive number');
}

const proxyInterface = new ethers.utils.Interface([
const forwarderInterface = new ethers.utils.Interface([
'function tokenAddress() view returns (address)',
]);

const proxyContract = new Contract(proxyAddress, proxyInterface, signer);
const forwarderContract = new Contract(forwarderAddress, forwarderInterface, signer);

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();
// Attempt to fetch the token address from the forwarder contract, to determine if it's an ERC20 SingleRequestForwarder.
tokenAddress = await forwarderContract.tokenAddress();
} catch {
throw new Error('Contract is not an ERC20SingleRequestProxy');
throw new Error('Contract is not an ERC20SingleRequestForwarder');
}

const erc20Contract = IERC20__factory.connect(tokenAddress, signer);

// Transfer tokens to the proxy
const transferTx = await erc20Contract.transfer(proxyAddress, amount);
// Transfer tokens to the forwarder
const transferTx = await erc20Contract.transfer(forwarderAddress, amount);
await transferTx.wait();

// Trigger the proxy's receive function to finalize payment
const triggerTx = await signer.sendTransaction({
to: proxyAddress,
to: forwarderAddress,
value: ethers.constants.Zero,
});
await triggerTx.wait();
}

/**
* Executes a payment through an EthereumSingleRequestProxy contract
* @param proxyAddress - The address of the SingleRequestProxy contract
* Executes a payment through an EthereumSingleRequestForwarder contract
* @param forwarderAddress - The address of the SingleRequestForwarder 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
* @throws {Error} If the contract is not an EthereumSingleRequestForwarder
*/
export async function payWithEthereumSingleRequestProxy(
proxyAddress: string,
export async function payWithEthereumSingleRequestForwarder(
forwarderAddress: string,
signer: Signer,
amount: string,
): Promise<void> {
if (!amount || ethers.BigNumber.from(amount).lte(0)) {
throw new Error('Amount must be a positive number');
}

const proxyInterface = new ethers.utils.Interface([
const forwarderInterface = new ethers.utils.Interface([
'function tokenAddress() view returns (address)',
]);

const proxyContract = new Contract(proxyAddress, proxyInterface, signer);
const forwarderContract = new Contract(forwarderAddress, forwarderInterface, signer);

try {
// Attempt to fetch the token address from the proxy contract, to determine if it's an Ethereum SingleRequestProxy.
await proxyContract.tokenAddress();
// Attempt to fetch the token address from the forwarder contract, to determine if it's an Ethereum SingleRequestForwarder.
await forwarderContract.tokenAddress();

// If the token address is fetched, it means the contract is an ERC20SingleRequestProxy.
throw new Error('Contract is not an EthereumSingleRequestProxy');
// If the token address is fetched, it means the contract is an ERC20SingleRequestForwarder.
throw new Error('Contract is not an EthereumSingleRequestForwarder');
} 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.
// If the token address is not fetched, it means the contract is an EthereumSingleRequestForwarder.
if (error.message === 'Contract is not an EthereumSingleRequestForwarder') {
// If the error message is 'Contract is not an EthereumSingleRequestForwarder', throw the error.
throw error;
}
}

const tx = await signer.sendTransaction({
to: proxyAddress,
to: forwarderAddress,
value: amount,
});
await tx.wait();
Expand All @@ -228,48 +231,48 @@ export async function payWithEthereumSingleRequestProxy(
/**
* Executes a payment through a Single Request Proxy contract.
*
* @param singleRequestProxyAddress - The address of the deployed Single Request Proxy contract.
* @param singleRequestForwarderAddress - The address of the deployed Single Request Forwarder 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 SingleRequestProxy contract is invalid.
* @throws {Error} If the proxy contract type cannot be determined, or if any transaction fails.
* @throws {Error} If the SingleRequestForwarder contract is invalid.
* @throws {Error} If the forwarder 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.
* For ERC20 payments, it first transfers the tokens to the forwarder contract and then triggers the payment.
* For Ethereum payments, it directly sends the Ether to the forwarder contract.
* The function automatically detects whether the proxy is for ERC20 or Ethereum based on the contract interface.
*/
export async function payRequestWithSingleRequestProxy(
singleRequestProxyAddress: string,
export async function payRequestWithSingleRequestForwarder(
singleRequestForwarderAddress: string,
signer: Signer,
amount: string,
): Promise<void> {
if (!amount || ethers.BigNumber.from(amount).lte(0)) {
throw new Error('Amount must be a positive number');
}

// Validate the SingleRequestProxy contract
await validateSingleRequestProxy(singleRequestProxyAddress, signer);
// Validate the SingleRequestForwarder contract
await validateSingleRequestForwarder(singleRequestForwarderAddress, signer);

const proxyInterface = new ethers.utils.Interface([
const forwarderInterface = new ethers.utils.Interface([
'function tokenAddress() view returns (address)',
]);

const proxyContract = new Contract(singleRequestProxyAddress, proxyInterface, signer);
const forwarderContract = new Contract(singleRequestForwarderAddress, forwarderInterface, signer);

let isERC20: boolean;
try {
await proxyContract.tokenAddress();
await forwarderContract.tokenAddress();
isERC20 = true;
} catch {
isERC20 = false;
}

if (isERC20) {
await payWithERC20SingleRequestProxy(singleRequestProxyAddress, signer, amount);
await payWithERC20SingleRequestForwarder(singleRequestForwarderAddress, signer, amount);
} else {
await payWithEthereumSingleRequestProxy(singleRequestProxyAddress, signer, amount);
await payWithEthereumSingleRequestForwarder(singleRequestForwarderAddress, signer, amount);
}
}
Loading

0 comments on commit 9af0ebd

Please sign in to comment.