Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: rename single request proxy #1488

Merged
merged 8 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

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,
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
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];
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

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');
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
}

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.
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
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;
}
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

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