Skip to content

Commit 41e33e8

Browse files
committed
feat: add Tron payment processor support
- Add tron-fee-proxy payment functions - Add Tron utility functions for transaction handling - Add unit tests for Tron payment processing - Update request-client tests for Tron support
1 parent 816af51 commit 41e33e8

File tree

6 files changed

+960
-3
lines changed

6 files changed

+960
-3
lines changed

packages/payment-processor/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export * from './payment/encoder-approval';
2828
export * as Escrow from './payment/erc20-escrow-payment';
2929
export * from './payment/prepared-transaction';
3030
export * from './payment/utils-near';
31+
export * from './payment/utils-tron';
32+
export * from './payment/tron-fee-proxy';
3133
export * from './payment/single-request-forwarder';
3234
export * from './payment/erc20-recurring-payment-proxy';
3335
export * from './payment/erc20-commerce-escrow-wrapper';
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { BigNumber, BigNumberish } from 'ethers';
2+
import { ClientTypes, ExtensionTypes } from '@requestnetwork/types';
3+
import { TronChains } from '@requestnetwork/currency';
4+
5+
import { getAmountToPay, getRequestPaymentValues, validateRequest } from './utils';
6+
import {
7+
TronWeb,
8+
ITronTransactionCallback,
9+
processTronFeeProxyPayment,
10+
approveTrc20,
11+
getTronAllowance,
12+
isTronAccountSolvent,
13+
isValidTronAddress,
14+
getERC20FeeProxyAddress,
15+
} from './utils-tron';
16+
import { validatePaymentReference } from '../utils/validation';
17+
18+
/**
19+
* Checks if the TronWeb instance has sufficient allowance for the payment
20+
*/
21+
export async function hasSufficientTronAllowance(
22+
request: ClientTypes.IRequestData,
23+
tronWeb: TronWeb,
24+
amount?: BigNumberish,
25+
): Promise<boolean> {
26+
const network = request.currencyInfo.network;
27+
if (!network || !TronChains.isChainSupported(network)) {
28+
throw new Error('Request currency network is not a supported Tron network');
29+
}
30+
TronChains.assertChainSupported(network);
31+
32+
const tokenAddress = request.currencyInfo.value;
33+
const { feeAmount } = getRequestPaymentValues(request);
34+
const amountToPay = getAmountToPay(request, amount);
35+
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);
36+
37+
const allowance = await getTronAllowance(tronWeb, tokenAddress, network);
38+
return allowance.gte(totalAmount);
39+
}
40+
41+
/**
42+
* Checks if the payer has sufficient TRC20 token balance
43+
*/
44+
export async function hasSufficientTronBalance(
45+
request: ClientTypes.IRequestData,
46+
tronWeb: TronWeb,
47+
amount?: BigNumberish,
48+
): Promise<boolean> {
49+
const tokenAddress = request.currencyInfo.value;
50+
const { feeAmount } = getRequestPaymentValues(request);
51+
const amountToPay = getAmountToPay(request, amount);
52+
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);
53+
54+
return isTronAccountSolvent(tronWeb, tokenAddress, totalAmount);
55+
}
56+
57+
/**
58+
* Approves the ERC20FeeProxy contract to spend TRC20 tokens for a request payment
59+
*/
60+
export async function approveTronFeeProxyRequest(
61+
request: ClientTypes.IRequestData,
62+
tronWeb: TronWeb,
63+
amount?: BigNumberish,
64+
callback?: ITronTransactionCallback,
65+
): Promise<string> {
66+
const network = request.currencyInfo.network;
67+
if (!network || !TronChains.isChainSupported(network)) {
68+
throw new Error('Request currency network is not a supported Tron network');
69+
}
70+
TronChains.assertChainSupported(network);
71+
72+
validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT);
73+
74+
const tokenAddress = request.currencyInfo.value;
75+
const { feeAmount } = getRequestPaymentValues(request);
76+
const amountToPay = getAmountToPay(request, amount);
77+
const totalAmount = BigNumber.from(amountToPay).add(feeAmount || 0);
78+
79+
return approveTrc20(tronWeb, tokenAddress, network, totalAmount, callback);
80+
}
81+
82+
/**
83+
* Processes a TRC20 fee proxy payment for a Request.
84+
*
85+
* @param request The request to pay
86+
* @param tronWeb The TronWeb instance connected to the payer's wallet
87+
* @param amount Optionally, the amount to pay. Defaults to remaining amount of the request.
88+
* @param feeAmount Optionally, the fee amount to pay. Defaults to the fee amount from the request.
89+
* @param callback Optional callbacks for transaction events
90+
* @returns The transaction hash
91+
*/
92+
export async function payTronFeeProxyRequest(
93+
request: ClientTypes.IRequestData,
94+
tronWeb: TronWeb,
95+
amount?: BigNumberish,
96+
feeAmount?: BigNumberish,
97+
callback?: ITronTransactionCallback,
98+
): Promise<string> {
99+
const network = request.currencyInfo.network;
100+
if (!network || !TronChains.isChainSupported(network)) {
101+
throw new Error('Request currency network is not a supported Tron network');
102+
}
103+
TronChains.assertChainSupported(network);
104+
105+
validateRequest(request, ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_FEE_PROXY_CONTRACT);
106+
107+
const {
108+
paymentReference,
109+
paymentAddress,
110+
feeAddress,
111+
feeAmount: requestFeeAmount,
112+
} = getRequestPaymentValues(request);
113+
114+
validatePaymentReference(paymentReference);
115+
116+
if (!isValidTronAddress(paymentAddress)) {
117+
throw new Error(`Invalid Tron payment address: ${paymentAddress}`);
118+
}
119+
120+
const tokenAddress = request.currencyInfo.value;
121+
const amountToPay = getAmountToPay(request, amount);
122+
const feeToPay = feeAmount ?? requestFeeAmount ?? '0';
123+
124+
// Check allowance
125+
const totalAmount = BigNumber.from(amountToPay).add(feeToPay);
126+
const allowance = await getTronAllowance(tronWeb, tokenAddress, network);
127+
128+
if (allowance.lt(totalAmount)) {
129+
throw new Error(
130+
`Insufficient TRC20 allowance. Required: ${totalAmount.toString()}, Available: ${allowance.toString()}. ` +
131+
`Please call approveTronFeeProxyRequest first.`,
132+
);
133+
}
134+
135+
// Check balance
136+
const hasSufficientBalance = await isTronAccountSolvent(tronWeb, tokenAddress, totalAmount);
137+
if (!hasSufficientBalance) {
138+
throw new Error('Insufficient TRC20 token balance for payment');
139+
}
140+
141+
return processTronFeeProxyPayment(
142+
tronWeb,
143+
network,
144+
tokenAddress,
145+
paymentAddress,
146+
amountToPay,
147+
paymentReference,
148+
feeToPay,
149+
feeAddress || tronWeb.defaultAddress.base58,
150+
callback,
151+
);
152+
}
153+
154+
/**
155+
* Gets information needed to pay a Tron request
156+
*/
157+
export function getTronPaymentInfo(
158+
request: ClientTypes.IRequestData,
159+
amount?: BigNumberish,
160+
): {
161+
proxyAddress: string;
162+
tokenAddress: string;
163+
paymentAddress: string;
164+
amount: string;
165+
paymentReference: string;
166+
feeAmount: string;
167+
feeAddress: string;
168+
} {
169+
const network = request.currencyInfo.network;
170+
if (!network || !TronChains.isChainSupported(network)) {
171+
throw new Error('Request currency network is not a supported Tron network');
172+
}
173+
TronChains.assertChainSupported(network);
174+
175+
const { paymentReference, paymentAddress, feeAddress, feeAmount } =
176+
getRequestPaymentValues(request);
177+
178+
const tokenAddress = request.currencyInfo.value;
179+
const amountToPay = getAmountToPay(request, amount);
180+
const proxyAddress = getERC20FeeProxyAddress(network);
181+
182+
return {
183+
proxyAddress,
184+
tokenAddress,
185+
paymentAddress,
186+
amount: amountToPay.toString(),
187+
paymentReference: paymentReference ?? '',
188+
feeAmount: (feeAmount || '0').toString(),
189+
feeAddress: feeAddress ?? '',
190+
};
191+
}

0 commit comments

Comments
 (0)