Swap tokens
Let users swap tokens on SunSwap with zero TRX.
The sunswap_swap mode runs a gasless SunSwap swap. Tronergy finds the route, builds the transactions, pays the network cost, and broadcasts. The user gets the full route output. Tronergy adds no fee inside the swap.
This is the most involved mode. Get Send USDT working first. In sandbox, swaps run on the Nile testnet, not Shasta.
How it works
A swap has up to two stages:
- Setup. The first time a wallet swaps a token, it signs one or two approval transactions.
- Swap. The user signs the swap itself.
You do not track stages yourself. Every response has a nextAction. Follow it:
sign_setup: build, sign all returned transactions, execute.sign_swap: build, sign the swap transaction, execute.wait: something is still running. Wait, then ask again.done: the swap finished. Stop.
Full loop
import { TronWeb } from 'tronweb'
const tronWeb = new TronWeb({ fullHost: 'https://api.trongrid.io' }) // sandbox: https://nile.trongrid.io
const sign = (tx) => tronWeb.trx.sign(tx, process.env.USER_PRIVATE_KEY)
const base = { chain: 'tron', mode: 'sunswap_swap', environment: 'live' }
const intent = {
user_address: 'TXyz...',
token_in: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT
token_out: 'TToken...',
amount_in: '10000000', // base units of token_in
slippage_bps: 100, // 1%
recipient: 'TXyz...', // must equal user_address
}
let state = await call('/chain_relayers/portal/estimate', { ...base, payload: intent })
while (state.nextAction !== 'done') {
if (state.nextAction === 'wait') {
await new Promise((resolve) => setTimeout(resolve, 2000))
} else {
// 1. Build the current stage
const draft = await call('/chain_relayers/portal/construct-tx', {
...base,
payload: { session_id: state.sessionId },
})
// 2. Sign exactly what was returned, in order
const payload = draft.stage === 'setup'
? {
session_id: draft.sessionId,
draft_id: draft.draftId,
signed_txs: await Promise.all(
draft.unsignedTxs.map(async (entry) => ({ id: entry.id, tx: await sign(entry.tx) })),
),
}
: { session_id: draft.sessionId, draft_id: draft.draftId, signed_tx: await sign(draft.unsignedTxs[0].tx) }
// 3. Execute and wait for this stage to finish
const run = await call('/chain_relayers/portal/execute', {
...base,
idempotencyKey: draft.executeIdempotencyKey,
payload,
})
const operation = await waitForOperation(run.operationId) // see Track status
if (operation.status !== 'completed') throw new Error(`${draft.stage} ${operation.status}: ${operation.errorCode}`)
}
// 4. Ask what comes next
state = await call('/chain_relayers/portal/estimate', {
...base,
payload: { ...intent, session_id: state.sessionId },
})
}call() is a small fetch helper from the quickstart. waitForOperation() is in Track status.
Show the price
The first estimate returns a quote with amountOut. Show it to the user. During setup it is a preview. The final quote comes with the swap draft.
Need a price without starting a swap? Send quote_only: true with token_in, token_out and amount_in.
Good to know
- Set
slippage_bps. The default is0, which often fails on a moving price. Range is 0 to 5000. recipientmust equaluser_address. Tokens always go to the signing wallet.- Keep the intent the same when you pass
session_id. To swap something else, start a new session withoutsession_id. - Cost. Each stage is its own relay, charged to your balance. A first swap of a token is charged twice (setup + swap). Later swaps are charged once.
- Timing. A session lasts 30 minutes and is refreshed as you use it. A draft lasts 9 minutes.
- No
sync, no batch.sync: trueand batch do not work for swaps.
Recovering
DRAFT_EXPIREDorDRAFT_STALE: build the stage again withconstruct-txand re-sign.SUNSWAP_SESSION_REPRICE_REQUIRED: callestimateagain with the samesession_id, thenconstruct-tx.- A stage failed: call
estimateagain. It returns what is still missing. SUNSWAP_SESSION_COMPLETED: the swap is done. Start a new session to swap again.