Tronergydocs

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:

  1. Setup. The first time a wallet swaps a token, it signs one or two approval transactions.
  2. 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

swap.ts
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 is 0, which often fails on a moving price. Range is 0 to 5000.
  • recipient must equal user_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 without session_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: true and batch do not work for swaps.

Recovering

  • DRAFT_EXPIRED or DRAFT_STALE: build the stage again with construct-tx and re-sign.
  • SUNSWAP_SESSION_REPRICE_REQUIRED: call estimate again with the same session_id, then construct-tx.
  • A stage failed: call estimate again. It returns what is still missing.
  • SUNSWAP_SESSION_COMPLETED: the swap is done. Start a new session to swap again.

See construct and errors for all swap fields and codes.