Skip to content

Instantly share code, notes, and snippets.

@sedhu-orbitx
Last active September 25, 2025 06:18
Show Gist options
  • Select an option

  • Save sedhu-orbitx/c4bd67ce95e87231f7647ccd38ee5185 to your computer and use it in GitHub Desktop.

Select an option

Save sedhu-orbitx/c4bd67ce95e87231f7647ccd38ee5185 to your computer and use it in GitHub Desktop.
/**
* Composed MultiHop Transfer Implementation following tron-exmaple.ts pattern
* Handles single-transaction multi-hop transfers: Tron β†’ Arbitrum β†’ Polygon
* Uses composed message pattern for seamless multi-hop transfers
* Uses TronWeb for Tron operations and ethers.js for EVM chains
*
* Based on LayerZero UI Bridge SDK pattern and USDT0 documentation
*/
export const TRON_OFT_ABI = [
{
inputs: [
{
internalType: 'uint32',
name: '_arbitrumEid',
type: 'uint32',
},
{
internalType: 'uint32',
name: '_celoEid',
type: 'uint32',
},
{
internalType: 'uint32',
name: '_ethEid',
type: 'uint32',
},
{
internalType: 'uint32',
name: '_tonEid',
type: 'uint32',
},
{
internalType: 'uint32',
name: '_tronEid',
type: 'uint32',
},
{
internalType: 'address',
name: '_token',
type: 'address',
},
{
internalType: 'address',
name: '_lzEndpoint',
type: 'address',
},
{
internalType: 'address',
name: '_delegate',
type: 'address',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [
{
internalType: 'address',
name: 'target',
type: 'address',
},
],
name: 'AddressEmptyCode',
type: 'error',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
],
name: 'AddressInsufficientBalance',
type: 'error',
},
{
inputs: [],
name: 'ComposeNotSupported',
type: 'error',
},
{
inputs: [],
name: 'FailedInnerCall',
type: 'error',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
internalType: 'uint256',
name: 'credits',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'amountWithdraw',
type: 'uint256',
},
],
name: 'InsufficientCredits',
type: 'error',
},
{
inputs: [],
name: 'InsufficientFeeBalance',
type: 'error',
},
{
inputs: [],
name: 'InvalidAmount',
type: 'error',
},
{
inputs: [],
name: 'InvalidDelegate',
type: 'error',
},
{
inputs: [],
name: 'InvalidEid',
type: 'error',
},
{
inputs: [],
name: 'InvalidEndpointCall',
type: 'error',
},
{
inputs: [
{
internalType: 'uint16',
name: 'feeBps',
type: 'uint16',
},
],
name: 'InvalidFeeBps',
type: 'error',
},
{
inputs: [],
name: 'InvalidLocalDecimals',
type: 'error',
},
{
inputs: [
{
internalType: 'uint16',
name: 'msgType',
type: 'uint16',
},
],
name: 'InvalidMsgType',
type: 'error',
},
{
inputs: [
{
internalType: 'bytes',
name: 'options',
type: 'bytes',
},
],
name: 'InvalidOptions',
type: 'error',
},
{
inputs: [],
name: 'LzTokenUnavailable',
type: 'error',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
],
name: 'NoPeer',
type: 'error',
},
{
inputs: [
{
internalType: 'uint256',
name: 'msgValue',
type: 'uint256',
},
],
name: 'NotEnoughNative',
type: 'error',
},
{
inputs: [
{
internalType: 'address',
name: 'addr',
type: 'address',
},
],
name: 'OnlyEndpoint',
type: 'error',
},
{
inputs: [],
name: 'OnlyLpAdminOrOwner',
type: 'error',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'sender',
type: 'bytes32',
},
],
name: 'OnlyPeer',
type: 'error',
},
{
inputs: [],
name: 'OnlyPlanner',
type: 'error',
},
{
inputs: [
{
internalType: 'address',
name: 'owner',
type: 'address',
},
],
name: 'OwnableInvalidOwner',
type: 'error',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
],
name: 'OwnableUnauthorizedAccount',
type: 'error',
},
{
inputs: [
{
internalType: 'address',
name: 'token',
type: 'address',
},
],
name: 'SafeERC20FailedOperation',
type: 'error',
},
{
inputs: [
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
],
name: 'SlippageExceeded',
type: 'error',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
indexed: false,
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
indexed: false,
internalType: 'bytes',
name: 'message',
type: 'bytes',
},
],
name: 'CreditsReceived',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
indexed: false,
internalType: 'uint64',
name: 'creditsArbitrum',
type: 'uint64',
},
{
indexed: false,
internalType: 'uint64',
name: 'creditsCelo',
type: 'uint64',
},
{
indexed: false,
internalType: 'uint64',
name: 'creditsEth',
type: 'uint64',
},
{
indexed: false,
internalType: 'uint64',
name: 'creditsTon',
type: 'uint64',
},
{
indexed: false,
internalType: 'uint64',
name: 'creditsTron',
type: 'uint64',
},
],
name: 'CreditsSent',
type: 'event',
},
{
anonymous: false,
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
internalType: 'uint16',
name: 'msgType',
type: 'uint16',
},
{
internalType: 'bytes',
name: 'options',
type: 'bytes',
},
],
indexed: false,
internalType: 'struct EnforcedOptionParam[]',
name: '_enforcedOptions',
type: 'tuple[]',
},
],
name: 'EnforcedOptionSet',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint16',
name: 'feeBps',
type: 'uint16',
},
],
name: 'FeeBpsSet',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'FeesWithdrawn',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'LocalDeposit',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'LocalWithdrawn',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'address',
name: 'lpAdmin',
type: 'address',
},
],
name: 'LpAdminSet',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
indexed: false,
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
indexed: true,
internalType: 'address',
name: 'toAddress',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amountReceivedLD',
type: 'uint256',
},
],
name: 'OFTReceived',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
indexed: false,
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
indexed: true,
internalType: 'address',
name: 'fromAddress',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amountSentLD',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'amountReceivedLD',
type: 'uint256',
},
],
name: 'OFTSent',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'previousOwner',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'newOwner',
type: 'address',
},
],
name: 'OwnershipTransferred',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
indexed: false,
internalType: 'bytes32',
name: 'peer',
type: 'bytes32',
},
],
name: 'PeerSet',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'address',
name: 'planner',
type: 'address',
},
],
name: 'PlannerSet',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
indexed: false,
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
indexed: false,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'RemoteWithdrawReceived',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
indexed: false,
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
],
name: 'RemoteWithdrawn',
type: 'event',
},
{
inputs: [],
name: 'ARBITRUM_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'BPS_DENOMINATOR',
outputs: [
{
internalType: 'uint16',
name: '',
type: 'uint16',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'CELO_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'ETH_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'LOCAL_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'SEND_CREDITS',
outputs: [
{
internalType: 'uint16',
name: '',
type: 'uint16',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'SEND_OFT',
outputs: [
{
internalType: 'uint16',
name: '',
type: 'uint16',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'TON_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'TRON_EID',
outputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'WITHDRAW_REMOTE',
outputs: [
{
internalType: 'uint16',
name: '',
type: 'uint16',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'sender',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
],
internalType: 'struct Origin',
name: 'origin',
type: 'tuple',
},
],
name: 'allowInitializePath',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'approvalRequired',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: '_eid',
type: 'uint32',
},
{
internalType: 'uint16',
name: '_msgType',
type: 'uint16',
},
{
internalType: 'bytes',
name: '_extraOptions',
type: 'bytes',
},
],
name: 'combineOptions',
outputs: [
{
internalType: 'bytes',
name: '',
type: 'bytes',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
],
name: 'credits',
outputs: [
{
internalType: 'uint256',
name: 'credits',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: '_amount',
type: 'uint256',
},
],
name: 'depositLocal',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'endpoint',
outputs: [
{
internalType: 'contract ILayerZeroEndpointV2',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
internalType: 'uint16',
name: 'msgType',
type: 'uint16',
},
],
name: 'enforcedOptions',
outputs: [
{
internalType: 'bytes',
name: 'enforcedOption',
type: 'bytes',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'feeBalance',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'feeBps',
outputs: [
{
internalType: 'uint16',
name: '',
type: 'uint16',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'sender',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
],
internalType: 'struct Origin',
name: '',
type: 'tuple',
},
{
internalType: 'bytes',
name: '',
type: 'bytes',
},
{
internalType: 'address',
name: '_sender',
type: 'address',
},
],
name: 'isComposeMsgSender',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'lpAdmin',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'srcEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'sender',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
],
internalType: 'struct Origin',
name: '_origin',
type: 'tuple',
},
{
internalType: 'bytes32',
name: '_guid',
type: 'bytes32',
},
{
internalType: 'bytes',
name: '_message',
type: 'bytes',
},
{
internalType: 'address',
name: '_executor',
type: 'address',
},
{
internalType: 'bytes',
name: '_extraData',
type: 'bytes',
},
],
name: 'lzReceive',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: '',
type: 'uint32',
},
{
internalType: 'bytes32',
name: '',
type: 'bytes32',
},
],
name: 'nextNonce',
outputs: [
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'oAppVersion',
outputs: [
{
internalType: 'uint64',
name: 'senderVersion',
type: 'uint64',
},
{
internalType: 'uint64',
name: 'receiverVersion',
type: 'uint64',
},
],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [],
name: 'oftVersion',
outputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
{
internalType: 'uint64',
name: 'version',
type: 'uint64',
},
],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [],
name: 'owner',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
],
name: 'peers',
outputs: [
{
internalType: 'bytes32',
name: 'peer',
type: 'bytes32',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'planner',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'to',
type: 'bytes32',
},
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'extraOptions',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'composeMsg',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'oftCmd',
type: 'bytes',
},
],
internalType: 'struct SendParam',
name: '_sendParam',
type: 'tuple',
},
],
name: 'quoteOFT',
outputs: [
{
components: [
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'maxAmountLD',
type: 'uint256',
},
],
internalType: 'struct OFTLimit',
name: 'oftLimit',
type: 'tuple',
},
{
components: [
{
internalType: 'int256',
name: 'feeAmountLD',
type: 'int256',
},
{
internalType: 'string',
name: 'description',
type: 'string',
},
],
internalType: 'struct OFTFeeDetail[]',
name: 'oftFeeDetails',
type: 'tuple[]',
},
{
components: [
{
internalType: 'uint256',
name: 'amountSentLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'amountReceivedLD',
type: 'uint256',
},
],
internalType: 'struct OFTReceipt',
name: 'oftReceipt',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'to',
type: 'bytes32',
},
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'extraOptions',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'composeMsg',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'oftCmd',
type: 'bytes',
},
],
internalType: 'struct SendParam',
name: '_sendParam',
type: 'tuple',
},
{
internalType: 'bool',
name: '_payInLzToken',
type: 'bool',
},
],
name: 'quoteSend',
outputs: [
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'msgFee',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: '_dstEid',
type: 'uint32',
},
{
internalType: 'uint64',
name: '_creditsArbitrum',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsCelo',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsEth',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsTon',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsTron',
type: 'uint64',
},
{
internalType: 'bytes',
name: '_extraOptions',
type: 'bytes',
},
{
internalType: 'bool',
name: '_payInLzToken',
type: 'bool',
},
],
name: 'quoteSendCredits',
outputs: [
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'msgFee',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'to',
type: 'bytes32',
},
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'extraOptions',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'composeMsg',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'oftCmd',
type: 'bytes',
},
],
internalType: 'struct SendParam',
name: '_sendParam',
type: 'tuple',
},
{
internalType: 'bool',
name: '_payInLzToken',
type: 'bool',
},
],
name: 'quoteWithdrawRemote',
outputs: [
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'msgFee',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'renounceOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'to',
type: 'bytes32',
},
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'extraOptions',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'composeMsg',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'oftCmd',
type: 'bytes',
},
],
internalType: 'struct SendParam',
name: '_sendParam',
type: 'tuple',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: '_fee',
type: 'tuple',
},
{
internalType: 'address',
name: '_refundAddress',
type: 'address',
},
],
name: 'send',
outputs: [
{
components: [
{
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'fee',
type: 'tuple',
},
],
internalType: 'struct MessagingReceipt',
name: 'msgReceipt',
type: 'tuple',
},
{
components: [
{
internalType: 'uint256',
name: 'amountSentLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'amountReceivedLD',
type: 'uint256',
},
],
internalType: 'struct OFTReceipt',
name: 'oftReceipt',
type: 'tuple',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: '_dstEid',
type: 'uint32',
},
{
internalType: 'uint64',
name: '_creditsArbitrum',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsCelo',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsEth',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsTon',
type: 'uint64',
},
{
internalType: 'uint64',
name: '_creditsTron',
type: 'uint64',
},
{
internalType: 'bytes',
name: '_extraOptions',
type: 'bytes',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: '_fee',
type: 'tuple',
},
],
name: 'sendCredits',
outputs: [
{
components: [
{
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'fee',
type: 'tuple',
},
],
internalType: 'struct MessagingReceipt',
name: 'msgReceipt',
type: 'tuple',
},
],
stateMutability: 'payable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_delegate',
type: 'address',
},
],
name: 'setDelegate',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'eid',
type: 'uint32',
},
{
internalType: 'uint16',
name: 'msgType',
type: 'uint16',
},
{
internalType: 'bytes',
name: 'options',
type: 'bytes',
},
],
internalType: 'struct EnforcedOptionParam[]',
name: '_enforcedOptions',
type: 'tuple[]',
},
],
name: 'setEnforcedOptions',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint16',
name: '_feeBps',
type: 'uint16',
},
],
name: 'setFeeBps',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_lpAdmin',
type: 'address',
},
],
name: 'setLpAdmin',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint32',
name: '_eid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: '_peer',
type: 'bytes32',
},
],
name: 'setPeer',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_planner',
type: 'address',
},
],
name: 'setPlanner',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'sharedDecimals',
outputs: [
{
internalType: 'uint8',
name: '',
type: 'uint8',
},
],
stateMutability: 'pure',
type: 'function',
},
{
inputs: [],
name: 'token',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'newOwner',
type: 'address',
},
],
name: 'transferOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'tvl',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_to',
type: 'address',
},
{
internalType: 'uint256',
name: '_amount',
type: 'uint256',
},
],
name: 'withdrawFees',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '_to',
type: 'address',
},
{
internalType: 'uint256',
name: '_amount',
type: 'uint256',
},
],
name: 'withdrawLocal',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{
internalType: 'uint32',
name: 'dstEid',
type: 'uint32',
},
{
internalType: 'bytes32',
name: 'to',
type: 'bytes32',
},
{
internalType: 'uint256',
name: 'amountLD',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'minAmountLD',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'extraOptions',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'composeMsg',
type: 'bytes',
},
{
internalType: 'bytes',
name: 'oftCmd',
type: 'bytes',
},
],
internalType: 'struct SendParam',
name: '_sendParam',
type: 'tuple',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: '_fee',
type: 'tuple',
},
{
internalType: 'address',
name: '_refundAddress',
type: 'address',
},
],
name: 'withdrawRemote',
outputs: [
{
components: [
{
internalType: 'bytes32',
name: 'guid',
type: 'bytes32',
},
{
internalType: 'uint64',
name: 'nonce',
type: 'uint64',
},
{
components: [
{
internalType: 'uint256',
name: 'nativeFee',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'lzTokenFee',
type: 'uint256',
},
],
internalType: 'struct MessagingFee',
name: 'fee',
type: 'tuple',
},
],
internalType: 'struct MessagingReceipt',
name: 'msgReceipt',
type: 'tuple',
},
],
stateMutability: 'payable',
type: 'function',
},
];
import { ethers } from 'ethers';
import { TronWeb } from 'tronweb';
import {
LEGACY_MESH_ABI,
MessagingFee,
OFTReceipt,
SendParam
} from './abis';
import {
LAYERZERO_EIDS,
RPC_URLS,
USDT0_CONTRACTS,
USDT_CONTRACTS,
USDT_DECIMALS
} from './constants';
// Types
export interface TronTransactionResult {
txid: string;
result: boolean;
energy_used: number;
energy_fee: number;
bandwidth_used: number;
bandwidth_fee: number;
net_fee: number;
net_usage: number;
energy_usage_total: number;
bandwidth_usage: number;
energy_usage: number;
blockNumber: number;
}
export interface ComposedTransferResult {
tronTx: TronTransactionResult;
totalFees: {
tron: bigint;
layerZero: bigint;
};
monitoring: {
tronExplorer: string;
layerZeroExplorer: string;
polygonExplorer: string;
};
guid: string;
}
export interface ComposedTransferQuote {
messagingFee: MessagingFee;
oftReceipt: OFTReceipt;
hopChainFee: MessagingFee;
totalFee: MessagingFee;
}
// Utility Functions
export function addressToBytes32(address: string): string {
return ethers.zeroPadValue(address, 32);
}
export function encodeComposeMessage(hopSendParams: SendParam): string {
return ethers.AbiCoder.defaultAbiCoder().encode(
['tuple(uint32,bytes32,uint256,uint256,bytes,bytes,bytes)'],
[[
hopSendParams.dstEid,
hopSendParams.to,
hopSendParams.amountLD,
hopSendParams.minAmountLD,
hopSendParams.extraOptions,
hopSendParams.composeMsg,
hopSendParams.oftCmd
]]
);
}
export function createLayerZeroOptions(
hopChainFee: MessagingFee,
dstChainKey: string
): string {
// For composed transfers, we can use a simpler approach
// LayerZero will handle the executor options automatically
// Option 1: Use empty options (simplest)
// return '0x';
// Option 2: Use basic executor options
const executorLzReceiveOption = [200_000n, 0n]; // [gas, value]
const executorComposeOption = [0n, 500_000n, (hopChainFee.nativeFee * 11n) / 10n]; // [index, gas, value]
// Encode options as tuples in the correct order
return ethers.AbiCoder.defaultAbiCoder().encode(
['tuple(tuple(uint256,uint256),tuple(uint256,uint256,uint256))'],
[[executorLzReceiveOption, executorComposeOption]]
);
}
// TronWeb Initialization
export function initializeTronWeb(privateKey: string, tronRpcUrl?: string): any {
const tronWeb = new TronWeb({
fullHost: tronRpcUrl,
privateKey: privateKey
});
return tronWeb;
}
// Core Transfer Functions
export async function getTronUSDTBalance(
tronWeb: any,
walletAddress: string
): Promise<bigint> {
try {
const contract = await tronWeb.contract().at(USDT_CONTRACTS.TRON);
const balance = await contract.balanceOf(walletAddress).call();
return BigInt(balance.toString());
} catch (error) {
throw new Error(`Failed to get USDT balance on Tron: ${error}`);
}
}
export async function getTronUSDTInfo(
tronWeb: any
): Promise<{
name: string;
symbol: string;
decimals: number;
totalSupply: bigint;
}> {
try {
const contract = await tronWeb.contract().at(USDT_CONTRACTS.TRON);
const [name, symbol, decimals, totalSupply] = await Promise.all([
contract.name().call(),
contract.symbol().call(),
contract.decimals().call(),
contract.totalSupply().call()
]);
return {
name: name.toString(),
symbol: symbol.toString(),
decimals: Number(decimals.toString()),
totalSupply: BigInt(totalSupply.toString())
};
} catch (error) {
throw new Error(`Failed to get USDT info on Tron: ${error}`);
}
}
export async function checkTronUSDTAllowance(
tronWeb: any,
walletAddress: string
): Promise<bigint> {
try {
const contract = await tronWeb.contract().at(USDT_CONTRACTS.TRON);
const allowance = await contract.allowance(
walletAddress,
USDT0_CONTRACTS.TRON.UsdtOFT
).call();
return BigInt(allowance.toString());
} catch (error) {
throw new Error(`Failed to check USDT allowance on Tron: ${error}`);
}
}
export async function approveTronUSDT(
tronWeb: any,
amount: bigint
): Promise<TronTransactionResult> {
try {
const contract = await tronWeb.contract().at(USDT_CONTRACTS.TRON);
console.log(` Approving ${ethers.formatUnits(amount, USDT_DECIMALS)} USDT for Legacy Mesh contract...`);
const tx = await contract.approve(
USDT0_CONTRACTS.TRON.UsdtOFT,
amount.toString()
).send({
feeLimit: 100_000_000, // 100 TRX fee limit
callValue: 0,
shouldPollResponse: true
});
console.log(` Approval transaction hash: ${tx.txid}`);
console.log(' USDT approval confirmed on Tron');
return tx;
} catch (error) {
throw new Error(`Failed to approve USDT on Tron: ${error}`);
}
}
export async function getHopChainQuote(
arbitrumProvider: ethers.JsonRpcProvider,
hopSendParams: SendParam
): Promise<MessagingFee> {
const arbitrumUsdt0Contract = new ethers.Contract(
USDT0_CONTRACTS.ARBITRUM.UsdtOFT,
LEGACY_MESH_ABI,
arbitrumProvider
);
try {
const quoteData = await arbitrumUsdt0Contract.quoteSend(hopSendParams, false);
return {
nativeFee: quoteData.nativeFee,
lzTokenFee: quoteData.lzTokenFee
};
} catch (error) {
// console.log('Hop chain quote error, using fallback fee:', error);
// Fallback fee: 0.0001 ETH
return {
nativeFee: BigInt(1e14), // 0.0001 ETH
lzTokenFee: 0n
};
}
}
export function buildHopChainSendParams(
recipientAddress: string,
amount: bigint,
dstChainKey: string = 'polygon'
): SendParam {
const dstEid = LAYERZERO_EIDS.POLYGON;
return {
dstEid,
to: addressToBytes32(recipientAddress),
amountLD: amount,
minAmountLD: 0n,
extraOptions: '0x',
composeMsg: '0x',
oftCmd: '0x'
};
}
export async function buildComposedSendParams(
tronWeb: TronWeb,
arbitrumProvider: ethers.JsonRpcProvider,
recipientAddress: string,
amount: bigint,
minAmount?: bigint
): Promise<{
sendParams: SendParam;
messagingFee: MessagingFee;
oftReceipt: OFTReceipt;
}> {
// Build hop chain parameters
const hopSendParams = buildHopChainSendParams(recipientAddress, amount);
// Get hop chain quote
const hopChainFee = await getHopChainQuote(arbitrumProvider, hopSendParams);
// Create LayerZero options with fee buffer
const extraOptions = createLayerZeroOptions(hopChainFee, 'polygon');
// Build main send parameters
const sendParams: SendParam = {
dstEid: LAYERZERO_EIDS.ARBITRUM,
to: addressToBytes32(USDT0_CONTRACTS.ARBITRUM.MultiHopComposer), // Send to MultiHopComposer
amountLD: amount,
minAmountLD: minAmount || amount / 2n, // Default to 50% slippage protection
extraOptions,
composeMsg: encodeComposeMessage(hopSendParams), // Encode hop chain params
oftCmd: '0x'
};
// Get quote for main transfer using TronWeb
const tronUsdt0Contract = await tronWeb.contract(TRON_OFT_ABI, USDT0_CONTRACTS.TRON.UsdtOFT);
// Convert to TronWeb format for contract calls - pass as tuple array
const sendParamsTron = [
sendParams.dstEid,
sendParams.to,
sendParams.amountLD.toString(),
sendParams.minAmountLD.toString(),
sendParams.extraOptions,
sendParams.composeMsg,
sendParams.oftCmd
];
// Use quoteOFT to get the quote (not send)
const [, feeDetails, oftReceipt] = await tronUsdt0Contract.quoteOFT(sendParamsTron).call();
sendParams.minAmountLD = BigInt(oftReceipt.amountReceivedLD?.toString() || '0');
sendParamsTron[3] = oftReceipt.amountReceivedLD?.toString();
const messagingFeeResult = await tronUsdt0Contract.quoteSend(sendParamsTron, false).call();
const messagingFee: MessagingFee = {
nativeFee: BigInt(messagingFeeResult.nativeFee?.toString() || '0'),
lzTokenFee: BigInt(messagingFeeResult.lzTokenFee?.toString() || '0')
};
const oftReceiptResult: OFTReceipt = {
amountReceivedLD: BigInt(oftReceipt.amountReceivedLD?.toString() || '0'),
amountSentLD: BigInt(oftReceipt.amountSentLD?.toString() || '0')
};
return {
sendParams,
messagingFee,
oftReceipt: oftReceiptResult
};
}
export async function executeComposedTransfer(
privateKey: string,
recipientAddress: string,
amount: string,
tronRpcUrl?: string,
arbitrumRpcUrl?: string
): Promise<ComposedTransferResult> {
// Initialize TronWeb and EVM providers
const tronWeb = initializeTronWeb(privateKey, tronRpcUrl);
const arbitrumProvider = new ethers.JsonRpcProvider(arbitrumRpcUrl || RPC_URLS.ARBITRUM);
const amountWei = ethers.parseUnits(amount, USDT_DECIMALS);
const walletAddress = tronWeb.address.fromPrivateKey(privateKey);
console.log(`πŸš€ Starting Composed MultiHop transfer: ${amount} USDT from Tron to Polygon`);
console.log(' Architecture: Tron (USDT) β†’ Arbitrum (MultiHopComposer) β†’ Polygon (USDT0)');
console.log(` Tron Address: ${walletAddress}\n`);
// Check USDT balance
const usdtBalance = await getTronUSDTBalance(tronWeb, walletAddress);
if (usdtBalance < amountWei) {
throw new Error(`Insufficient USDT balance. Required: ${amount} USDT, Available: ${ethers.formatUnits(usdtBalance, USDT_DECIMALS)} USDT`);
}
// Check and approve USDT if needed
const allowance = await checkTronUSDTAllowance(tronWeb, walletAddress);
if (allowance < amountWei) {
console.log(' Insufficient USDT allowance, requesting approval...');
await approveTronUSDT(tronWeb, amountWei);
} else {
console.log(` Sufficient USDT allowance: ${ethers.formatUnits(allowance, USDT_DECIMALS)} USDT`);
}
// Build composed send parameters
console.log('πŸ“Š Building composed transfer parameters...');
const { sendParams, messagingFee, oftReceipt } = await buildComposedSendParams(
tronWeb,
arbitrumProvider,
recipientAddress,
amountWei
);
console.log(` Expected USDT0 on Arbitrum: ${ethers.formatUnits(oftReceipt.amountReceivedLD, USDT_DECIMALS)} USDT0`);
console.log(` Native fee: ${ethers.formatEther(messagingFee.nativeFee)} TRX`);
// Execute composed transfer using TronWeb
console.log('πŸ”„ Executing composed transfer...');
const tronUsdt0Contract = await tronWeb.contract(TRON_OFT_ABI, USDT0_CONTRACTS.TRON.UsdtOFT);
const sendParamsTron = [
sendParams.dstEid,
sendParams.to,
sendParams.amountLD.toString(),
sendParams.minAmountLD.toString(),
sendParams.extraOptions,
sendParams.composeMsg,
sendParams.oftCmd
];
const messagingFeeTron = [
messagingFee.nativeFee.toString(),
messagingFee.lzTokenFee.toString()
];
const tronTx = await tronUsdt0Contract.send(
sendParamsTron,
messagingFeeTron,
walletAddress
).send({
feeLimit: 1_000_000_000, // 1000 TRX fee limit
callValue: messagingFee.nativeFee.toString(),
shouldPollResponse: true
});
console.log(` Tron transaction hash: ${tronTx.txid}`);
console.log(`βœ… Transfer completed in block ${tronTx.blockNumber}\n`);
// Calculate total fees
const totalFees = {
tron: BigInt(tronTx.energy_fee + tronTx.bandwidth_fee + tronTx.net_fee),
layerZero: messagingFee.nativeFee
};
// Generate monitoring links
const monitoring = {
tronExplorer: `https://tronscan.org/#/transaction/${tronTx.txid}`,
layerZeroExplorer: `https://layerzeroscan.com/tx/${tronTx.txid}`,
polygonExplorer: `https://polygonscan.com/address/${recipientAddress}`
};
console.log('πŸŽ‰ Composed MultiHop transfer completed successfully!');
console.log(` Total fees: ${ethers.formatEther(totalFees.tron)} TRX + ${ethers.formatEther(totalFees.layerZero)} TRX`);
console.log(' Note: Second hop (Arbitrum β†’ Polygon) will be executed automatically by MultiHopComposer');
return {
tronTx,
totalFees,
monitoring,
guid: tronTx.txid
};
}
export async function getComposedTransferQuote(
privateKey: string,
tronRpcUrl: string,
arbitrumRpcUrl: string,
recipientAddress: string,
amount: string
): Promise<ComposedTransferQuote> {
const tronWeb = initializeTronWeb(privateKey, tronRpcUrl);
const arbitrumProvider = new ethers.JsonRpcProvider(arbitrumRpcUrl);
const amountWei = ethers.parseUnits(amount, USDT_DECIMALS);
console.log('πŸ“Š Quoting composed multi-hop transfer...');
const { sendParams, messagingFee, oftReceipt } = await buildComposedSendParams(
tronWeb,
arbitrumProvider,
recipientAddress,
amountWei
);
// Get hop chain fee
const hopSendParams = buildHopChainSendParams(recipientAddress, amountWei);
const hopChainFee = await getHopChainQuote(arbitrumProvider, hopSendParams);
const totalFee: MessagingFee = {
nativeFee: messagingFee.nativeFee + hopChainFee.nativeFee,
lzTokenFee: messagingFee.lzTokenFee + hopChainFee.lzTokenFee
};
console.log(` Tron β†’ Arbitrum fee: ${ethers.formatEther(messagingFee.nativeFee)} TRX`);
console.log(` Arbitrum β†’ Polygon fee: ${ethers.formatEther(hopChainFee.nativeFee)} ETH`);
console.log(` Total fee: ${ethers.formatEther(totalFee.nativeFee)} (TRX + ETH)`);
return {
messagingFee,
oftReceipt,
hopChainFee,
totalFee
};
}
// Main execution function
export async function main() {
try {
const result = await executeComposedTransfer(
process.env.TRON_PRIVATE_KEY!,
process.env.RECIPIENT_ADDRESS!,
process.env.TRANSFER_AMOUNT!,
process.env.TRON_RPC_URL,
process.env.ARBITRUM_RPC_URL
);
console.log('\nπŸ“Š Transfer Summary:');
console.log(` Transaction: ${result.tronTx.txid}`);
console.log(` Tron Explorer: ${result.monitoring.tronExplorer}`);
console.log(` LayerZero Explorer: ${result.monitoring.layerZeroExplorer}`);
console.log(` Polygon Explorer: ${result.monitoring.polygonExplorer}`);
} catch (error) {
console.error('❌ Transfer failed:', error);
process.exit(1);
}
}
// Export for use in other files
export default {
main
};
// if the file is run directly, run the main function
if (require.main === module) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment