Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ references:
name: 'Subgraph deployment and configuration'
working_directory: ~/
command: |
# Wait for Graph node to be ready
for i in $(seq 1 30); do
if curl -s http://localhost:8020 > /dev/null 2>&1; then
echo "Graph node is ready"
break
fi
echo "Waiting for Graph node... ($i/30)"
sleep 2
done
git clone https://github.com/RequestNetwork/storage-subgraph
cd storage-subgraph
yarn
Expand Down
2 changes: 2 additions & 0 deletions packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ 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/utils-tron';
export * from './payment/tron-fee-proxy';
export * from './payment/single-request-forwarder';
export * from './payment/erc20-recurring-payment-proxy';
export * from './payment/erc20-commerce-escrow-wrapper';
Expand Down
191 changes: 191 additions & 0 deletions packages/payment-processor/src/payment/tron-fee-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { BigNumber, BigNumberish } from 'ethers';
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
import { TronChains } from '@requestnetwork/currency';

import { getAmountToPay, getRequestPaymentValues, validateRequest } from './utils';
import {
TronWeb,
ITronTransactionCallback,
processTronFeeProxyPayment,
approveTrc20,
getTronAllowance,
isTronAccountSolvent,
isValidTronAddress,
getERC20FeeProxyAddress,
} from './utils-tron';
import { validatePaymentReference } from '../utils/validation';

/**
* Checks if the TronWeb instance has sufficient allowance for the payment
*/
export async function hasSufficientTronAllowance(
request: ClientTypes.IRequestData,
tronWeb: TronWeb,
amount?: BigNumberish,
): Promise<boolean> {
const network = request.currencyInfo.network;
if (!network || !TronChains.isChainSupported(network)) {
throw new Error('Request currency network is not a supported Tron network');
}
TronChains.assertChainSupported(network);

const tokenAddress = request.currencyInfo.value;
const { feeAmount } = getRequestPaymentValues(request);
const amountToPay = getAmountToPay(request, amount);
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);

const allowance = await getTronAllowance(tronWeb, tokenAddress, network);
return allowance.gte(totalAmount);
}

/**
* Checks if the payer has sufficient TRC20 token balance
*/
export async function hasSufficientTronBalance(
request: ClientTypes.IRequestData,
tronWeb: TronWeb,
amount?: BigNumberish,
): Promise<boolean> {
const tokenAddress = request.currencyInfo.value;
const { feeAmount } = getRequestPaymentValues(request);
const amountToPay = getAmountToPay(request, amount);
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);

return isTronAccountSolvent(tronWeb, tokenAddress, totalAmount);
}

/**
* Approves the ERC20FeeProxy contract to spend TRC20 tokens for a request payment
*/
export async function approveTronFeeProxyRequest(
request: ClientTypes.IRequestData,
tronWeb: TronWeb,
amount?: BigNumberish,
callback?: ITronTransactionCallback,
): Promise<string> {
const network = request.currencyInfo.network;
if (!network || !TronChains.isChainSupported(network)) {
throw new Error('Request currency network is not a supported Tron network');
}
TronChains.assertChainSupported(network);

validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT);

const tokenAddress = request.currencyInfo.value;
const { feeAmount } = getRequestPaymentValues(request);
const amountToPay = getAmountToPay(request, amount);
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);

return approveTrc20(tronWeb, tokenAddress, network, totalAmount, callback);
}

/**
* Processes a TRC20 fee proxy payment for a Request.
*
* @param request The request to pay
* @param tronWeb The TronWeb instance connected to the payer's wallet
* @param amount Optionally, the amount to pay. Defaults to remaining amount of the request.
* @param feeAmount Optionally, the fee amount to pay. Defaults to the fee amount from the request.
* @param callback Optional callbacks for transaction events
* @returns The transaction hash
*/
export async function payTronFeeProxyRequest(
request: ClientTypes.IRequestData,
tronWeb: TronWeb,
amount?: BigNumberish,
feeAmount?: BigNumberish,
callback?: ITronTransactionCallback,
): Promise<string> {
const network = request.currencyInfo.network;
if (!network || !TronChains.isChainSupported(network)) {
throw new Error('Request currency network is not a supported Tron network');
}
TronChains.assertChainSupported(network);

validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT);

const {
paymentReference,
paymentAddress,
feeAddress,
feeAmount: requestFeeAmount,
} = getRequestPaymentValues(request);

validatePaymentReference(paymentReference);

if (!isValidTronAddress(paymentAddress)) {
throw new Error(`Invalid Tron payment address: ${paymentAddress}`);
}

const tokenAddress = request.currencyInfo.value;
const amountToPay = getAmountToPay(request, amount);
const feeToPay = feeAmount ?? requestFeeAmount ?? '0';

// Check allowance
const totalAmount = BigNumber.from(amountToPay).add(feeToPay);
const allowance = await getTronAllowance(tronWeb, tokenAddress, network);

if (allowance.lt(totalAmount)) {
throw new Error(
`Insufficient TRC20 allowance. Required: ${totalAmount.toString()}, Available: ${allowance.toString()}. ` +
`Please call approveTronFeeProxyRequest first.`,
);
}

// Check balance
const hasSufficientBalance = await isTronAccountSolvent(tronWeb, tokenAddress, totalAmount);
if (!hasSufficientBalance) {
throw new Error('Insufficient TRC20 token balance for payment');
}

return processTronFeeProxyPayment(
tronWeb,
network,
tokenAddress,
paymentAddress,
amountToPay,
paymentReference,
feeToPay,
feeAddress || tronWeb.defaultAddress.base58,
callback,
);
}

/**
* Gets information needed to pay a Tron request
*/
export function getTronPaymentInfo(
request: ClientTypes.IRequestData,
amount?: BigNumberish,
): {
proxyAddress: string;
tokenAddress: string;
paymentAddress: string;
amount: string;
paymentReference: string;
feeAmount: string;
feeAddress: string;
} {
const network = request.currencyInfo.network;
if (!network || !TronChains.isChainSupported(network)) {
throw new Error('Request currency network is not a supported Tron network');
}
TronChains.assertChainSupported(network);

const { paymentReference, paymentAddress, feeAddress, feeAmount } =
getRequestPaymentValues(request);

const tokenAddress = request.currencyInfo.value;
const amountToPay = getAmountToPay(request, amount);
const proxyAddress = getERC20FeeProxyAddress(network);

return {
proxyAddress,
tokenAddress,
paymentAddress,
amount: amountToPay.toString(),
paymentReference: paymentReference ?? '',
feeAmount: (feeAmount || '0').toString(),
feeAddress: feeAddress ?? '',
};
}
Loading