Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8b78bb7
feat: thorchain tron support
gomesalexandre Dec 2, 2025
9aca0d9
fix: add executeTronTransaction and improve fee estimation
gomesalexandre Dec 2, 2025
19320c9
fix: simplify TronChainAdapter memo handling
gomesalexandre Dec 2, 2025
a7dc51d
fix: increase feeLimit for TRC20 with memo and use consistent contrac…
gomesalexandre Dec 2, 2025
ef93c07
fix: preserve fee_limit in TRC20 transactions with memo using txLocal
gomesalexandre Dec 2, 2025
cbc6d39
fix: remove txLocal from addUpdateData - may be causing validation is…
gomesalexandre Dec 2, 2025
c69fd5e
fix: revert to standard 100 TRX feeLimit matching SwapKit and Thorchain
gomesalexandre Dec 2, 2025
3fc2255
docs: document TRON fee estimation issues and add TODOs
gomesalexandre Dec 2, 2025
ac80cfb
Merge branch 'develop' into feat_thor_tron
gomesalexandre Dec 2, 2025
5870e6f
docs: add comprehensive Thorchain TRON integration documentation
gomesalexandre Dec 2, 2025
9947ff9
fix: use actual sender address for TRON fee estimation
gomesalexandre Dec 3, 2025
d1f14a2
Merge remote-tracking branch 'origin/develop' into feat_thor_tron
gomesalexandre Dec 3, 2025
857d335
fix: revert Number() removal - TronWeb TypeScript requires number type
gomesalexandre Dec 3, 2025
f37fd68
refactor: use bnOrZero().toNumber() instead of Number() cast
gomesalexandre Dec 3, 2025
81561ee
fix: accurate TRON fee estimation for TRC20 transfers
gomesalexandre Dec 3, 2025
45d3653
fix: use sender address for accurate TRON TRC20 energy estimation
gomesalexandre Dec 3, 2025
3b73ebb
docs: comprehensive TRON fees explainer
gomesalexandre Dec 3, 2025
49dfa23
chore: cleanup TRON fee fix - remove docs and console.warn
gomesalexandre Dec 3, 2025
9d0d9c5
chore: remove console logs from useSendDetails
gomesalexandre Dec 3, 2025
c63f0b0
Merge branch 'develop' into feat_thor_tron
NeOMakinG Dec 4, 2025
ebab87e
Merge branch 'feat_thor_tron' into fix_tron_estimates
gomesalexandre Dec 4, 2025
6b1e3ba
Merge branch 'develop' into fix_tron_estimates
gomesalexandre Dec 4, 2025
ae4f809
fix: Add chainSpecific to Tron getFeeData calls and fix memo bandwidt…
gomesalexandre Dec 4, 2025
646b480
feat: add TRON gas estimates for Near Intents swapper
gomesalexandre Dec 4, 2025
d2b888b
fix: remove SVG module script MIME type error in splashscreen
gomesalexandre Dec 4, 2025
a516022
debug: add comprehensive TRON transaction logging
gomesalexandre Dec 4, 2025
f002da8
debug: add TRON account balance check before transaction
gomesalexandre Dec 4, 2025
99a75fe
debug: JSON.stringify all console.log objects
gomesalexandre Dec 4, 2025
bb3be02
fix: use actual sender address for TRON bandwidth estimation
gomesalexandre Dec 4, 2025
0af2671
debug: check if recipient address needs activation (1 TRX cost)
gomesalexandre Dec 4, 2025
63ecbea
fix: include 1 TRX account activation fee in TRON gas estimates
gomesalexandre Dec 4, 2025
4f59e9f
feat: add TRON gas estimation for Sun.io swapper
gomesalexandre Dec 4, 2025
1138892
feat: add TRON support to Relay swapper quote validation
gomesalexandre Dec 4, 2025
7d32639
fix: properly estimate Sun.io TRX swap gas costs
gomesalexandre Dec 4, 2025
618fc0b
fix: use 2k energy estimate for Sun.io TRX swaps
gomesalexandre Dec 4, 2025
a8c0090
fix: use 2k energy estimate for Sun.io TRC-20 swaps
gomesalexandre Dec 4, 2025
b5f8142
fix: increase Sun.io bandwidth estimate to 1100 bytes
gomesalexandre Dec 4, 2025
a8568e2
chore: remove debug logging from TRON implementations
gomesalexandre Dec 4, 2025
0b52f51
Revert "fix: remove SVG module script MIME type error in splashscreen"
gomesalexandre Dec 4, 2025
bdf495c
chore: merge develop into feat_tron_all_swapper_estimates
gomesalexandre Dec 5, 2025
6d24430
fix: account activation fee should apply to all Sun.io swaps
gomesalexandre Dec 5, 2025
791ab22
Merge branch 'develop' into feat_tron_all_swapper_estimates
NeOMakinG Dec 5, 2025
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
118 changes: 102 additions & 16 deletions packages/chain-adapters/src/tron/TronChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,18 +219,24 @@ export class ChainAdapter implements IChainAdapter<KnownChainIds.TronMainnet> {

txData = txData.transaction
} else {
const requestBody = {
owner_address: from,
to_address: to,
amount: Number(value),
visible: true,
}

const response = await fetch(`${this.rpcUrl}/wallet/createtransaction`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
owner_address: from,
to_address: to,
amount: Number(value),
visible: true,
}),
body: JSON.stringify(requestBody),
})

txData = await response.json()

if (txData.Error) {
throw new Error(`TronGrid API error: ${txData.Error}`)
}
}

// Add memo if provided
Expand Down Expand Up @@ -361,27 +367,107 @@ export class ChainAdapter implements IChainAdapter<KnownChainIds.TronMainnet> {
// This causes UI to show wrong fees and transactions to fail on-chain
// See TRON_FEE_ESTIMATION_ISSUES.md for detailed analysis and fix
async getFeeData(
_input: GetFeeDataInput<KnownChainIds.TronMainnet>,
input: GetFeeDataInput<KnownChainIds.TronMainnet>,
): Promise<FeeDataEstimate<KnownChainIds.TronMainnet>> {
try {
// TODO: Use _input.chainSpecific.contractAddress to detect TRC20
// TODO: Call estimateTRC20TransferFee() for TRC20 tokens
// TODO: Build actual transaction with memo to get accurate bandwidth
// TODO: Add 1 TRX memo fee if _input.chainSpecific.memo present
const { fast, average, slow, estimatedBandwidth } =
await this.providers.http.getPriorityFees()
const { to, value, chainSpecific: { from, contractAddress, memo } = {} } = input

// Get live network prices from chain parameters
const tronWeb = new TronWeb({ fullHost: this.rpcUrl })
const params = await tronWeb.trx.getChainParameters()
const bandwidthPrice = params.find(p => p.key === 'getTransactionFee')?.value ?? 1000
const energyPrice = params.find(p => p.key === 'getEnergyFee')?.value ?? 100

let energyFee = 0
let bandwidthFee = 0

if (contractAddress) {
// TRC20: Estimate energy using existing method
try {
// Use sender address if available, otherwise use recipient for estimation
const estimationFrom = from || to
const energyEstimate = await this.providers.http.estimateTRC20TransferFee({
contractAddress,
from: estimationFrom,
to,
amount: value,
})
energyFee = Number(energyEstimate)

// Apply 1.5x safety margin for dynamic energy spikes
energyFee = Math.ceil(energyFee * 1.5)
} catch (err) {
// Fallback: Conservative estimate for new address (130k energy)
energyFee = 130000 * energyPrice
}

// TRC20 transfers use ~276 bytes bandwidth
bandwidthFee = 276 * bandwidthPrice
} else {
// TRX transfer: Build actual transaction to get precise bandwidth
try {
// Use actual sender if available, otherwise use recipient for estimation
const estimationFrom = from || to
const baseTx = await tronWeb.transactionBuilder.sendTrx(to, Number(value), estimationFrom)

// Add memo if provided to get accurate size
const finalTx = memo
? await tronWeb.transactionBuilder.addUpdateData(baseTx, memo, 'utf8')
: baseTx

// Calculate bandwidth from actual transaction size
const rawDataBytes = finalTx.raw_data_hex ? finalTx.raw_data_hex.length / 2 : 133
const signatureBytes = 65
const totalBytes = rawDataBytes + signatureBytes

bandwidthFee = totalBytes * bandwidthPrice
} catch (err) {
// Fallback bandwidth estimate: Base tx + memo bytes
const baseBytes = 198
const memoBytes = memo ? Buffer.from(memo, 'utf8').length : 0
const totalBytes = baseBytes + memoBytes
bandwidthFee = totalBytes * bandwidthPrice
}
}

// Check if recipient address needs activation (1 TRX cost)
let accountActivationFee = 0
try {
const recipientInfoResponse = await fetch(`${this.rpcUrl}/wallet/getaccount`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
address: to,
visible: true,
}),
})
const recipientInfo = await recipientInfoResponse.json()
const recipientExists = recipientInfo && Object.keys(recipientInfo).length > 1

// If recipient doesn't exist, add 1 TRX activation fee
if (!recipientExists && !contractAddress) {
accountActivationFee = 1_000_000 // 1 TRX = 1,000,000 sun
}
} catch (err) {
// Don't fail on this check - continue with 0 activation fee
}

const totalFee = energyFee + bandwidthFee + accountActivationFee

// Calculate bandwidth for display
const estimatedBandwidth = String(Math.ceil(bandwidthFee / bandwidthPrice))

return {
fast: {
txFee: fast,
txFee: String(totalFee),
chainSpecific: { bandwidth: estimatedBandwidth },
},
average: {
txFee: average,
txFee: String(totalFee),
chainSpecific: { bandwidth: estimatedBandwidth },
},
slow: {
txFee: slow,
txFee: String(totalFee),
chainSpecific: { bandwidth: estimatedBandwidth },
},
}
Expand Down
6 changes: 6 additions & 0 deletions packages/chain-adapters/src/tron/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export type BuildTxInput = {
memo?: string
}

export type GetFeeDataInput = {
from?: string
contractAddress?: string
memo?: string
}

export interface TronUnsignedTx {
txID: string
raw_data: {
Expand Down
1 change: 1 addition & 0 deletions packages/chain-adapters/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ type ChainSpecificGetFeeDataInput<T> = ChainSpecific<
[KnownChainIds.LitecoinMainnet]: utxo.GetFeeDataInput
[KnownChainIds.SolanaMainnet]: solana.GetFeeDataInput
[KnownChainIds.SuiMainnet]: sui.GetFeeDataInput
[KnownChainIds.TronMainnet]: tron.GetFeeDataInput
}
>
export type GetFeeDataInput<T extends ChainId> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,14 @@ export const getTradeQuote = async (

case CHAIN_NAMESPACE.Tron: {
const sellAdapter = deps.assertGetTronChainAdapter(sellAsset.chainId)
const contractAddress = contractAddressOrUndefined(sellAsset.assetId)
const feeData = await sellAdapter.getFeeData({
to: depositAddress,
value: sellAmount,
chainSpecific: {
from: sendAddress,
contractAddress,
},
})

return { networkFeeCryptoBaseUnit: feeData.fast.txFee }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,14 @@ export const getTradeRate = async (
case CHAIN_NAMESPACE.Tron: {
try {
const sellAdapter = deps.assertGetTronChainAdapter(sellAsset.chainId)
const contractAddress = contractAddressOrUndefined(sellAsset.assetId)
const feeData = await sellAdapter.getFeeData({
to: depositAddress,
value: sellAmount,
chainSpecific: {
from: sendAddress,
contractAddress,
},
})

return feeData.fast.txFee
Expand Down
12 changes: 12 additions & 0 deletions packages/swapper/src/swappers/RelaySwapper/utils/getTrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
isRelayError,
isRelayQuoteEvmItemData,
isRelayQuoteSolanaItemData,
isRelayQuoteTronItemData,
isRelayQuoteUtxoItemData,
} from '../utils/types'
import { fetchRelayTrade } from './fetchRelayTrade'
Expand Down Expand Up @@ -625,6 +626,17 @@ export async function getTrade<T extends 'quote' | 'rate'>({
}
}

if (isRelayQuoteTronItemData(selectedItem.data)) {
return {
allowanceContract: '',
solanaTransactionMetadata: undefined,
relayTransactionMetadata: {
relayId: quote.steps[0].requestId,
to: selectedItem.data?.parameter?.contract_address,
},
}
}

throw new Error('Relay quote step contains no data')
})()

Expand Down
43 changes: 39 additions & 4 deletions packages/swapper/src/swappers/RelaySwapper/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,21 @@ export type RelayQuoteSolanaItemData = {
addressLookupTableAddresses: string[]
}

export type RelayQuoteTronItemData = {
type?: string
parameter?: {
owner_address?: string
contract_address?: string
data?: string
}
}

export type RelayQuoteItem = {
data?: RelayQuoteEvmItemData | RelayQuoteUtxoItemData | RelayQuoteSolanaItemData
data?:
| RelayQuoteEvmItemData
| RelayQuoteUtxoItemData
| RelayQuoteSolanaItemData
| RelayQuoteTronItemData
}

export type RelayQuoteStep = {
Expand All @@ -140,23 +153,45 @@ export type RelayQuote = {
}

export const isRelayQuoteUtxoItemData = (
item: RelayQuoteUtxoItemData | RelayQuoteEvmItemData | RelayQuoteSolanaItemData,
item:
| RelayQuoteUtxoItemData
| RelayQuoteEvmItemData
| RelayQuoteSolanaItemData
| RelayQuoteTronItemData,
): item is RelayQuoteUtxoItemData => {
return 'psbt' in item
}

export const isRelayQuoteEvmItemData = (
item: RelayQuoteUtxoItemData | RelayQuoteEvmItemData | RelayQuoteSolanaItemData,
item:
| RelayQuoteUtxoItemData
| RelayQuoteEvmItemData
| RelayQuoteSolanaItemData
| RelayQuoteTronItemData,
): item is RelayQuoteEvmItemData => {
return 'to' in item && 'data' in item && 'value' in item
}

export const isRelayQuoteSolanaItemData = (
item: RelayQuoteUtxoItemData | RelayQuoteEvmItemData | RelayQuoteSolanaItemData,
item:
| RelayQuoteUtxoItemData
| RelayQuoteEvmItemData
| RelayQuoteSolanaItemData
| RelayQuoteTronItemData,
): item is RelayQuoteSolanaItemData => {
return 'instructions' in item
}

export const isRelayQuoteTronItemData = (
item:
| RelayQuoteUtxoItemData
| RelayQuoteEvmItemData
| RelayQuoteSolanaItemData
| RelayQuoteTronItemData,
): item is RelayQuoteTronItemData => {
return 'type' in item && 'parameter' in item
}

export type RelaySolanaInstruction = {
keys: {
pubkey: string
Expand Down
Loading