Skip to content

Swap

Same-chain token exchange. Every order is quoted against all live aggregators and DEXs in parallel, and settled through the BlazpayRelayer contract.

Route: /defi/swap · Source: defi-dex/src/pages/defi/swap.tsx · Fee: 0.1% of input

What it does

The user picks a source token, a destination token and an amount. Optionally a different recipient address. The app then:

  1. Opens an SSE stream and receives quotes as each provider answers — no waiting for the slowest one.
  2. Sorts by output amount descending and pre-selects the best.
  3. On confirm: checks and requests an ERC-20 approval if needed, simulates the transaction, then sends it.

One approval and one signature. The user pays gas.

How it behaves

Quote streaming

calculateQuote() opens EventSource(baseURL + '/defi/quotes?...'). Each message event carries one provider's meta object. The list re-sorts on every arrival (+b.amount - +a.amount), so the displayed best can change while quotes are still landing. An end event closes the stream.

Because providers answer at very different speeds, the first quote shown is rarely the winner. This is intentional — showing something in 400 ms beats showing nothing for 4 seconds.

Approval

The allowance target is the relayer address for that chain, not the aggregator's router. TradeManager.getAllowance reads inPercentFee and enableFees off the relayer first, because an ERC-20 swap pulls the fee as a second transferFrom — so the approval must cover amount + fee, not just amount.

Simulation

Before sending, simulateTransaction does a staticCall of executeMetaTransactionSwap. sortQuotes additionally runs estimateGas against each candidate and drops the ones that revert. A route that looks best on paper but reverts on-chain never reaches the user.

Dispatch

swap() branches by aggregator:

AggregatorPath
Everything relayer-backedsendTransactionRawtradeSdk.triggerTransaction
kimasendTransactionKima — Kima submits server-side, the frontend records the returned id and polls getTxStatus (provider currently disabled)
TRONsendTransacionRawTron — uses setAllowanceTron via TronWeb, not the relayer

Transaction record

sendTransactionRaw writes a transaction record using a UUID as the initial txHash, pushes ?hash=<uuid> into the URL so a page reload can recover the in-flight trade, then PATCHes /defi/transaction/hash/<uuid> with the real on-chain hash once it is known.

/defi/swap?chain=hemi preselects that chain and its native token as fromToken. Logic in src/utils/chainPreselect.ts (resolveAppChain maps a slug to the backend chain by chainId or name; buildNativeToken synthesises the native token record). defaultChainId is passed to TokenModal, which syncs it via effect. Solana and TRON resolve by name, because the app uses internal ids (102, 728126428) rather than real chain ids.

Default provider excludes

bz-backend/services/swap.service.js excludes some providers from the swap path by default. The frontend can override by passing its own excludes.

Excluded from swapReason
symbiosisBridge-oriented; poor same-chain pricing
one_inchExcluded from the default swap fan-out
open_oceanExcluded from the default swap fan-out
icecream_swap on PolygonChain-specific exclusion

Under the hood

ConcernWhere
Quote fan-outbz-backend/controllers/defi/swap.js → getQuotes, services/swap.service.js
Provider implementationsswap-sdk/src/aggregators/*.aggregator.ts
Transaction buildGET /defi/swap/:idQuote.getTransactionData()
EIP-712 signingbz-backend/controllers/defi/transaction.controller.js → signTx
Relayer interactionswap-sdk/src/relayer.ts
Frontend wrapperdefi-dex/src/utils/trade.ts

Deep dives: quote → settle pipeline, swap-sdk internals, BlazpayRelayer.

Known limits

  • Ethereum mainnet is not supported for swaps. No relayer is deployed there by design — gas cost makes small trades uneconomic. assertRelayerDeployed(1) throws.
  • Quote freshness is five minutes. The Redis cache under meta.id expires then. A user who leaves the confirm screen open longer gets a stale-quote error on build.
  • Solana and TRON do not go through the relayer, so they do not get the fee-refund and allowance-reset protections that EVM swaps have. See non-EVM paths.
  • A provider that returns an approve() step as its transaction data will look like a valid route. The relayer will execute it harmlessly (no output), but the user pays gas for nothing. The automation crons guard against this with a calldata check; the interactive swap path relies on sortQuotes gas simulation instead.
  • Token visibility is capped. The picker slices the top 200 tokens by the order field. A token that exists in the database but ranks below 200 appears absent. See token curation.

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.