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
10 changes: 0 additions & 10 deletions packages/currency/src/chains/declarative/data/nile.ts

This file was deleted.

20 changes: 0 additions & 20 deletions packages/currency/src/chains/declarative/data/tron.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/payment-detection/codegen.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
overwrite: true
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest'
schema: 'https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest'
documents: src/thegraph/queries/*.graphql
generates:
src/thegraph/generated/graphql.ts:
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
schema: https://api.studio.thegraph.com/query/67444/request-payments-sepolia/version/latest
schema: https://api.studio.thegraph.com/query/67444/request-payments-base/version/latest
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Using local schema until the subgraph is deployed to The Graph Studio
schema: ../../../../../substreams-tron/schema.graphql
# Local schema for TRON payment queries
schema: ./schema.graphql
2 changes: 1 addition & 1 deletion packages/payment-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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/trc20-fee-proxy';
export * from './payment/single-request-forwarder';
export * from './payment/erc20-recurring-payment-proxy';
export * from './payment/erc20-commerce-escrow-wrapper';
Expand Down
112 changes: 110 additions & 2 deletions packages/payment-processor/src/payment/utils-tron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface TronWeb {
getBalance: (address: string) => Promise<number>;
sign: (transaction: unknown, privateKey?: string) => Promise<unknown>;
sendRawTransaction: (signedTransaction: unknown) => Promise<TronTransactionResult>;
getTransactionInfo: (txHash: string) => Promise<TronTransactionInfo>;
};
contract: <T extends readonly unknown[]>(
abi: T,
Expand All @@ -36,6 +37,24 @@ export interface TronWeb {
fromSun: (amount: number) => number;
}

/**
* Transaction info returned by getTransactionInfo
*/
export interface TronTransactionInfo {
id?: string;
blockNumber?: number;
blockTimeStamp?: number;
contractResult?: string[];
receipt?: {
result?: string;
energy_usage?: number;
energy_usage_total?: number;
net_usage?: number;
};
result?: string;
resMessage?: string;
}

// Generic contract instance type that provides method typing based on ABI
export type TronContractInstance<T> = {
[K in ExtractFunctionNames<T>]: (...args: unknown[]) => TronContractMethod;
Expand Down Expand Up @@ -137,28 +156,108 @@ export const getTronAllowance = async (
}
};

/** Default fee limit for TRC20 approval (100 TRX in SUN) */
export const DEFAULT_APPROVAL_FEE_LIMIT = 100_000_000;

/** Default fee limit for TRC20 fee proxy payment (150 TRX in SUN) */
export const DEFAULT_PAYMENT_FEE_LIMIT = 150_000_000;

/** Maximum retries when waiting for transaction confirmation */
const MAX_CONFIRMATION_RETRIES = 10;

/** Delay between retries in milliseconds */
const CONFIRMATION_RETRY_DELAY = 3000;

/**
* Waits for a transaction to be confirmed and validates its success
* @param tronWeb - TronWeb instance
* @param txHash - Transaction hash to validate
* @returns The transaction info if successful
* @throws Error if transaction failed or couldn't be confirmed
*/
export const waitForTransactionConfirmation = async (
tronWeb: TronWeb,
txHash: string,
): Promise<TronTransactionInfo> => {
for (let i = 0; i < MAX_CONFIRMATION_RETRIES; i++) {
try {
const txInfo = await tronWeb.trx.getTransactionInfo(txHash);

// If we have receipt info, the transaction is confirmed
if (txInfo.receipt) {
// Check if the transaction was successful
if (txInfo.receipt.result && txInfo.receipt.result !== 'SUCCESS') {
const errorMsg = txInfo.resMessage
? Buffer.from(txInfo.resMessage, 'hex').toString('utf8')
: `Transaction failed with result: ${txInfo.receipt.result}`;
throw new Error(errorMsg);
}

// Check contractResult for revert
if (txInfo.contractResult && txInfo.contractResult.length > 0) {
const result = txInfo.contractResult[0];
// Empty result or success
if (
result === '' ||
result === '0000000000000000000000000000000000000000000000000000000000000001'
) {
return txInfo;
}
// Non-empty result that's not success could indicate an error
// But some contracts return data, so we check receipt.result primarily
}

return txInfo;
}

// Transaction not yet confirmed, wait and retry
await new Promise((resolve) => setTimeout(resolve, CONFIRMATION_RETRY_DELAY));
} catch (error) {
// If it's our own error (from failed transaction), rethrow
if ((error as Error).message.includes('Transaction failed')) {
throw error;
}
// Otherwise, wait and retry (network error, tx not found yet, etc.)
await new Promise((resolve) => setTimeout(resolve, CONFIRMATION_RETRY_DELAY));
}
}

throw new Error(
`Transaction ${txHash} confirmation timeout after ${MAX_CONFIRMATION_RETRIES} retries`,
);
};

/**
* Approves the ERC20FeeProxy contract to spend TRC20 tokens
* @param feeLimit - Optional fee limit in SUN (1 TRX = 1,000,000 SUN). Defaults to 100 TRX.
* @param waitForConfirmation - If true, waits for transaction confirmation and validates success. Defaults to false.
*/
export const approveTrc20 = async (
tronWeb: TronWeb,
tokenAddress: string,
network: CurrencyTypes.TronChainName,
amount: BigNumberish,
callback?: ITronTransactionCallback,
feeLimit: number = DEFAULT_APPROVAL_FEE_LIMIT,
waitForConfirmation = false,
): Promise<string> => {
const proxyAddress = getERC20FeeProxyAddress(network);
const contract = await tronWeb.contract(TRC20_ABI, tokenAddress);

try {
const result = await contract.approve(proxyAddress, amount.toString()).send({
feeLimit: 100000000, // 100 TRX fee limit
feeLimit,
shouldPollResponse: true,
});

const txHash = result.txid || result.transaction?.txID || '';
callback?.onHash?.(txHash);

if (waitForConfirmation && txHash) {
const txInfo = await waitForTransactionConfirmation(tronWeb, txHash);
callback?.onConfirmation?.(txInfo);
}

return txHash;
} catch (error) {
callback?.onError?.(error as Error);
Expand All @@ -168,6 +267,8 @@ export const approveTrc20 = async (

/**
* Processes a TRC20 fee proxy payment on Tron
* @param feeLimit - Optional fee limit in SUN (1 TRX = 1,000,000 SUN). Defaults to 150 TRX.
* @param waitForConfirmation - If true, waits for transaction confirmation and validates success. Defaults to false.
*/
export const processTronFeeProxyPayment = async (
tronWeb: TronWeb,
Expand All @@ -179,6 +280,8 @@ export const processTronFeeProxyPayment = async (
feeAmount: BigNumberish,
feeAddress: string,
callback?: ITronTransactionCallback,
feeLimit: number = DEFAULT_PAYMENT_FEE_LIMIT,
waitForConfirmation = false,
): Promise<string> => {
// Validate addresses
if (!isValidTronAddress(to)) {
Expand Down Expand Up @@ -213,13 +316,18 @@ export const processTronFeeProxyPayment = async (
feeAddress,
)
.send({
feeLimit: 150000000, // 150 TRX fee limit for proxy call
feeLimit,
shouldPollResponse: true,
});

const txHash = result.txid || result.transaction?.txID || '';
callback?.onHash?.(txHash);

if (waitForConfirmation && txHash) {
const txInfo = await waitForTransactionConfirmation(tronWeb, txHash);
callback?.onConfirmation?.(txInfo);
}

return txHash;
} catch (error) {
callback?.onError?.(error as Error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
hasSufficientTronAllowance,
hasSufficientTronBalance,
getTronPaymentInfo,
} from '../../src/payment/tron-fee-proxy';
} from '../../src/payment/trc20-fee-proxy';
import { BigNumber } from 'ethers';

/* eslint-disable @typescript-eslint/no-unused-expressions */
Expand Down
1 change: 1 addition & 0 deletions packages/smart-contracts/deployments/tron/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"contracts": {
"ERC20FeeProxy": {
"address": "TCUDPYnS9dH3WvFEaE7wN7vnDa51J4R4fd",
"hexAddress": "411b6ca35d39842cf8fbe49000653a1505412da659",
"creationBlockNumber": 79216121
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/smart-contracts/deployments/tron/nile.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"contracts": {
"ERC20FeeProxy": {
"address": "THK5rNmrvCujhmrXa5DB1dASepwXTr9cJs",
"hexAddress": "41508b3b4059c40bb3aac5da5ac006ccdd9c4dc957",
"creationBlockNumber": 63208782
}
}
Expand Down