Skip to content

Commit 23fd8e3

Browse files
feat: add Tron payment processor support (#1690)
## Description of the changes Added support for Tron blockchain payments in the payment-processor package. This implementation enables TRC20 token payments through the ERC20 Fee Proxy contract on Tron networks. The changes include: - New `tron-fee-proxy.ts` module with functions for paying requests on Tron: - `payTronFeeProxyRequest` - Process payments with TRC20 tokens - `approveTronFeeProxyRequest` - Approve token spending - `hasSufficientTronAllowance` - Check token allowance - `hasSufficientTronBalance` - Verify token balance - `getTronPaymentInfo` - Get payment details - New `utils-tron.ts` module with Tron-specific utilities: - TronWeb type definitions - Address validation - Contract interaction helpers - Token approval and payment processing - Comprehensive test coverage for all new functionality - Exports for the new modules in the package index --- Closes RequestNetwork/private-issues#234
1 parent fe06d59 commit 23fd8e3

7 files changed

Lines changed: 986 additions & 3 deletions

File tree

.circleci/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ references:
4343
name: 'Subgraph deployment and configuration'
4444
working_directory: ~/
4545
command: |
46+
# Wait for Graph node to be ready
47+
for i in $(seq 1 30); do
48+
if curl -s http://localhost:8020 > /dev/null 2>&1; then
49+
echo "Graph node is ready"
50+
break
51+
fi
52+
echo "Waiting for Graph node... ($i/30)"
53+
sleep 2
54+
done
4655
git clone https://github.com/RequestNetwork/storage-subgraph
4756
cd storage-subgraph
4857
yarn

packages/payment-detection/src/erc20/fee-proxy-contract.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
PaymentTypes,
66
RequestLogicTypes,
77
} from '@requestnetwork/types';
8-
import { EvmChains, NearChains, isSameChain } from '@requestnetwork/currency';
8+
import { EvmChains, NearChains, TronChains, isSameChain } from '@requestnetwork/currency';
99
import ProxyInfoRetriever from './proxy-info-retriever';
1010

1111
import { loadCurrencyFromContract } from './currency';
@@ -14,6 +14,7 @@ import { makeGetDeploymentInformation } from '../utils';
1414
import { TheGraphClient, TheGraphInfoRetriever } from '../thegraph';
1515
import { ReferenceBasedDetectorOptions, TGetSubGraphClient } from '../types';
1616
import { NearInfoRetriever } from '../near';
17+
import { TronInfoRetriever } from '../tron/tron-info-retriever';
1718
import { NetworkNotSupported } from '../balance-error';
1819

1920
const PROXY_CONTRACT_ADDRESS_MAP = {
@@ -165,11 +166,13 @@ export class ERC20FeeProxyPaymentDetector<
165166
| TheGraphClient
166167
| TheGraphClient<CurrencyTypes.NearChainName>
167168
| TheGraphClient<CurrencyTypes.TronChainName>,
168-
): TheGraphInfoRetriever | NearInfoRetriever {
169+
): TheGraphInfoRetriever | NearInfoRetriever | TronInfoRetriever {
169170
const graphInfoRetriever = EvmChains.isChainSupported(paymentChain)
170171
? new TheGraphInfoRetriever(subgraphClient as TheGraphClient, this.currencyManager)
171172
: NearChains.isChainSupported(paymentChain) && this.network
172173
? new NearInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.NearChainName>)
174+
: TronChains.isChainSupported(paymentChain)
175+
? new TronInfoRetriever(subgraphClient as TheGraphClient<CurrencyTypes.TronChainName>)
173176
: undefined;
174177
if (!graphInfoRetriever) {
175178
throw new Error(

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)