Transfer Jetton Tokens
Transfer Jetton tokens gaslessly with fees paid in paymaster tokens.
This guide explains how to transfer Jetton tokens gaslessly, override paymaster configuration, estimate transfer fees, and run preflight checks.
Transfer Tokens (Gasless)
You can send Jetton tokens gaslessly using account.transfer(). Fees are deducted from the configured paymaster token:
const result = await account.transfer({
token: 'EQ...', // Jetton master contract address
recipient: 'EQ...', // Recipient's TON address
amount: 1000000000 // Amount in Jetton's base units
})
console.log('Signed transfer body hash:', result.hash)
console.log('Transfer fee:', result.fee, 'paymaster token units')Override Paymaster Configuration
You can override the default paymaster token and maximum fee on a per-transfer basis by passing a second configuration argument to account.transfer():
const result = await account.transfer({
token: 'EQ...',
recipient: 'EQ...',
amount: 1000000000
}, {
paymasterToken: {
address: 'EQ...' // Override default paymaster token
},
transferMaxFee: 2000000000 // Override maximum allowed fee
})
console.log('Signed transfer body hash:', result.hash)
console.log('Transfer fee:', result.fee, 'paymaster token units')transferMaxFee rejects estimates greater than the configured cap. A fee estimate equal to the cap is allowed.
Before signing or relaying, beta.10 and later verify that the relay estimate preserves the requested Jetton transfer and contains only the expected paymaster commission message. The validation also resolves the paymaster token's Jetton wallet through the configured TON client. Treat validation or provider errors as a failed preflight; do not reconstruct and submit the relay messages yourself.
Estimate Transfer Fees
You can get a fee estimate before executing the transfer using account.quoteTransfer():
const quote = await account.quoteTransfer({
token: 'EQ...',
recipient: 'EQ...',
amount: 1000000
})
console.log('Transfer fee estimate:', quote.fee, 'paymaster token units')quoteTransfer() performs the same estimate validation and paymaster-wallet lookup as transfer() before returning the commission.
Preflight Transfer Checks
Inspect balances and fees before transferring:
- Use
account.getTokenBalance()to check Jetton balance. - Use
account.quoteTransfer()with the intended paymaster to estimate the fee. - Query that paymaster Jetton explicitly with
getTokenBalance(paymasterJettonAddress).getPaymasterTokenBalance()always reads the wallet-level paymaster, so do not use it to preflight a per-call override. - If the transferred Jetton is also the paymaster Jetton, require one balance to cover the transfer amount plus the fee. Compare parsed TON addresses because different string encodings can identify the same Jetton master.
- Reject a quote above your application's fee policy, then execute
account.transfer()withtransferMaxFeeset to that quote. The method obtains a fresh estimate and aborts before relay if it has risen. This explicit policy check matters because a per-call configuration replaces, rather than merges with, the wallet-level transfer configuration:
This example imports Address from @ton/ton for canonical address comparison. Add @ton/ton as a direct dependency in your application before using it.
import { Address } from '@ton/ton'
async function transferWithChecks(account, jettonAddress, paymasterJettonAddress, recipient, amount, maxFee) {
if (typeof jettonAddress !== 'string' || jettonAddress.length === 0) {
throw new Error('Invalid Jetton address format')
}
if (typeof paymasterJettonAddress !== 'string' || paymasterJettonAddress.length === 0) {
throw new Error('Invalid paymaster Jetton address format')
}
if (typeof recipient !== 'string' || recipient.length === 0) {
throw new Error('Invalid recipient address format')
}
const amountBaseUnits = BigInt(amount)
const maxFeeBaseUnits = BigInt(maxFee)
const transferOptions = {
token: jettonAddress,
recipient,
amount: amountBaseUnits
}
const paymasterToken = { address: paymasterJettonAddress }
const transferBalance = await account.getTokenBalance(jettonAddress)
if (transferBalance < amountBaseUnits) {
throw new Error('Insufficient Jetton balance')
}
const quote = await account.quoteTransfer(transferOptions, { paymasterToken })
console.log('Estimated fee (paymaster token):', quote.fee)
if (quote.fee > maxFeeBaseUnits) {
throw new Error('Quoted fee exceeds application policy')
}
const paymasterBalance = await account.getTokenBalance(paymasterJettonAddress)
const sameJetton = Address.parse(jettonAddress).equals(Address.parse(paymasterJettonAddress))
const requiredPaymasterBalance = sameJetton
? amountBaseUnits + quote.fee
: quote.fee
if (paymasterBalance < requiredPaymasterBalance) {
throw new Error('Insufficient paymaster Jetton balance')
}
const result = await account.transfer(transferOptions, {
paymasterToken,
transferMaxFee: quote.fee
})
console.log('Signed transfer body hash:', result.hash)
console.log('Actual fee (paymaster token):', result.fee)
return result
}Next Steps
Learn how to sign and verify messages with your gasless TON account.