diff --git a/packages/currency/src/chains/declarative/data/nile.ts b/packages/currency/src/chains/declarative/data/nile.ts deleted file mode 100644 index e80c5f179..000000000 --- a/packages/currency/src/chains/declarative/data/nile.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const chainId = 'nile'; - -// Nile is Tron's test network -export const testnet = true; - -// Test tokens on Nile testnet -// Note: These are testnet token addresses, not mainnet -export const currencies = { - // Add testnet token addresses as needed -}; diff --git a/packages/currency/src/chains/declarative/data/tron.ts b/packages/currency/src/chains/declarative/data/tron.ts deleted file mode 100644 index 3ad0a105f..000000000 --- a/packages/currency/src/chains/declarative/data/tron.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const chainId = 'tron'; - -// Tron mainnet configuration -export const testnet = false; - -// Common TRC20 tokens on Tron -export const currencies = { - // USDT-TRC20 - the most widely used stablecoin on Tron - TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t: { - name: 'Tether USD', - symbol: 'USDT', - decimals: 6, - }, - // USDC on Tron - TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8: { - name: 'USD Coin', - symbol: 'USDC', - decimals: 6, - }, -}; diff --git a/packages/payment-detection/codegen.yml b/packages/payment-detection/codegen.yml index f67dfd436..455351978 100644 --- a/packages/payment-detection/codegen.yml +++ b/packages/payment-detection/codegen.yml @@ -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: diff --git a/packages/payment-detection/src/thegraph/queries/graphql.config.yml b/packages/payment-detection/src/thegraph/queries/graphql.config.yml index 6d35f9a0a..19b1d64ca 100644 --- a/packages/payment-detection/src/thegraph/queries/graphql.config.yml +++ b/packages/payment-detection/src/thegraph/queries/graphql.config.yml @@ -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 diff --git a/packages/payment-detection/src/thegraph/queries/tron/graphql.config.yml b/packages/payment-detection/src/thegraph/queries/tron/graphql.config.yml index f731d57f3..205b553d3 100644 --- a/packages/payment-detection/src/thegraph/queries/tron/graphql.config.yml +++ b/packages/payment-detection/src/thegraph/queries/tron/graphql.config.yml @@ -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 diff --git a/packages/payment-processor/src/index.ts b/packages/payment-processor/src/index.ts index 4e92546b5..68d2d772e 100644 --- a/packages/payment-processor/src/index.ts +++ b/packages/payment-processor/src/index.ts @@ -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'; diff --git a/packages/payment-processor/src/payment/tron-fee-proxy.ts b/packages/payment-processor/src/payment/trc20-fee-proxy.ts similarity index 100% rename from packages/payment-processor/src/payment/tron-fee-proxy.ts rename to packages/payment-processor/src/payment/trc20-fee-proxy.ts diff --git a/packages/payment-processor/src/payment/utils-tron.ts b/packages/payment-processor/src/payment/utils-tron.ts index f5fa56c19..c35e143c3 100644 --- a/packages/payment-processor/src/payment/utils-tron.ts +++ b/packages/payment-processor/src/payment/utils-tron.ts @@ -14,6 +14,7 @@ export interface TronWeb { getBalance: (address: string) => Promise; sign: (transaction: unknown, privateKey?: string) => Promise; sendRawTransaction: (signedTransaction: unknown) => Promise; + getTransactionInfo: (txHash: string) => Promise; }; contract: ( abi: T, @@ -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 = { [K in ExtractFunctionNames]: (...args: unknown[]) => TronContractMethod; @@ -137,8 +156,81 @@ 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 => { + 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, @@ -146,19 +238,26 @@ export const approveTrc20 = async ( network: CurrencyTypes.TronChainName, amount: BigNumberish, callback?: ITronTransactionCallback, + feeLimit: number = DEFAULT_APPROVAL_FEE_LIMIT, + waitForConfirmation = false, ): Promise => { 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); @@ -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, @@ -179,6 +280,8 @@ export const processTronFeeProxyPayment = async ( feeAmount: BigNumberish, feeAddress: string, callback?: ITronTransactionCallback, + feeLimit: number = DEFAULT_PAYMENT_FEE_LIMIT, + waitForConfirmation = false, ): Promise => { // Validate addresses if (!isValidTronAddress(to)) { @@ -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); diff --git a/packages/payment-processor/test/payment/tron-fee-proxy.test.ts b/packages/payment-processor/test/payment/trc20-fee-proxy.test.ts similarity index 99% rename from packages/payment-processor/test/payment/tron-fee-proxy.test.ts rename to packages/payment-processor/test/payment/trc20-fee-proxy.test.ts index a3a3e3d70..1e4b2914b 100644 --- a/packages/payment-processor/test/payment/tron-fee-proxy.test.ts +++ b/packages/payment-processor/test/payment/trc20-fee-proxy.test.ts @@ -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 */ diff --git a/packages/smart-contracts/deployments/tron/mainnet.json b/packages/smart-contracts/deployments/tron/mainnet.json index b77db46af..f1755f518 100644 --- a/packages/smart-contracts/deployments/tron/mainnet.json +++ b/packages/smart-contracts/deployments/tron/mainnet.json @@ -7,6 +7,7 @@ "contracts": { "ERC20FeeProxy": { "address": "TCUDPYnS9dH3WvFEaE7wN7vnDa51J4R4fd", + "hexAddress": "411b6ca35d39842cf8fbe49000653a1505412da659", "creationBlockNumber": 79216121 } } diff --git a/packages/smart-contracts/deployments/tron/nile.json b/packages/smart-contracts/deployments/tron/nile.json index 6107d03c1..77257b8bc 100644 --- a/packages/smart-contracts/deployments/tron/nile.json +++ b/packages/smart-contracts/deployments/tron/nile.json @@ -7,6 +7,7 @@ "contracts": { "ERC20FeeProxy": { "address": "THK5rNmrvCujhmrXa5DB1dASepwXTr9cJs", + "hexAddress": "41508b3b4059c40bb3aac5da5ac006ccdd9c4dc957", "creationBlockNumber": 63208782 } }