> For the complete documentation index, see [llms.txt](https://docs-swap.crypto-chief.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs-swap.crypto-chief.com/getting-started/how-a-swap-works.md).

# How a Swap Works

## How a Swap Works

A swap is two calls: a **quote** that prices it, and a **build** that turns the price into unsigned transactions. Your wallet signs and sends those transactions; the API never signs, sends, or holds anything.

Two terms appear on every page:

* The **executor** is the on-chain contract or program the swap step calls: the 1inch or Sushi router or a DEX's own router on EVM, SunSwap on TRON, Jupiter on Solana, STON.fi or DeDust on TON, a `Payment` on XRP Ledger. `executor.id` names it.
* A **liquidity source** is a DEX whose pools the route goes through, for example `uniswap_v3`. [`/v1/liquidity-sources`](/api-reference/liquidity-sources.md) lists both.

### Quote vs build

[`POST /v1/quote`](/api-reference/quote.md) needs no wallet. Leave `from_address` out and you get a pure estimate: allowances are not checked and approvals are not included (the response says so in `warnings`). Pass `from_address` and the quote also reflects your wallet: its allowance, balance, trust lines and token accounts.

[`POST /v1/build`](/api-reference/build.md) requires `from_address`. It re-reads the pools on fresh network state, simulates the swap, and answers with a fresh quote plus `steps[]`, the unsigned transactions to execute.

A quote lives **30 seconds** (`expires_at` in the response). After that, build without it or quote again.

Passing the quote's `quote_id` to a build keeps that quote's route and executor, unless they no longer fit the build's own filters (`executors`, `sources`, `exclude_sources`, `max_hops`, `max_splits`, `allow_non_atomic`, `recipient`) or a clearly better route exists. When the build does not keep them, `warnings` says so.

An expired or unknown `quote_id` is not an error: the build searches afresh and adds a warning. A `quote_id` that is not a valid id is `400 INVALID_REQUEST`.

{% hint style="info" %}
On Solana, a build without `quote_id` searches with a larger account budget than the quote did and can find a better route. With `quote_id`, the build uses the quote's route.
{% endhint %}

### Slippage and the guaranteed minimum

`slippage_bps` is your tolerance, 1 bps = 0.01 %. Default `50`, at most `5000`. The guaranteed minimum is:

```
amount_out_min = floor(amount_out × (10000 − slippage_bps) / 10000)
```

Below `amount_out_min` the swap does not execute: the transaction reverts on EVM, TRON and Solana; on TON the DEX returns the input; on XRP Ledger the payment delivers at least `DeliverMin` or fails.

### What the route costs

`net_amount_out = amount_out − network cost of all steps in the output token − risk penalty`. Routes are ranked by it, not by the gross output: a route with a higher `amount_out` but expensive gas loses to a cheaper one. `fees.network` itemises the network cost; `fees.pools` the fee of each pool; `risk` explains the penalty.

The risk object carries a `level` (`low`, `medium`, `high`), a `score` from 0 (none) to 100 (do not execute), the `penalty` in output-token base units, and the `factors` behind it. The full factor table is on the [Quote](/api-reference/quote.md#risk-factors) page.

### Simulation

Every build simulates the swap step against fresh state before answering. The result is in `simulation`:

| `status`     | Meaning                                                                  |
| ------------ | ------------------------------------------------------------------------ |
| `passed`     | The swap executed in simulation; `amount_out` holds the simulated output |
| `incomplete` | The check started but did not finish; `reason` says why                  |
| `not_run`    | No simulation ran; `reason` says why                                     |

{% hint style="warning" %}
There is no `failed` status in a `200` response. A failed simulation is a `422 SIMULATION_FAILED` **without steps**, so when you hold steps at all, the simulation did not fail. One case to treat as normal: on **Solana**, insufficient funds produce `simulation.status: "not_run"` with the reason in a `200` response, not an error. Check your balance before signing.
{% endhint %}

The quote's own output is the **local quote**: computed from the API's pool data, before simulation. When the simulation passes but returns less than the local quote, within the slippage tolerance, `amount_out` and `amount_out_min` are recomputed from the simulation and the risk factor `simulation_below_quote` is added. A build's `amount_out_min` can therefore be below the quote's. If you showed the quote to someone, compare the two before signing.

### Setup steps

A build's `steps[]` can start with setup steps before the `swap` step:

| `kind`            | When                                                                                                                                                                                                                                                                                                                                   |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `approve_reset`   | The token requires zeroing the allowance first (flag `approve_reset_required`, e.g. USDT on Ethereum) and the current allowance is not zero                                                                                                                                                                                            |
| `approve`         | EVM / TRON: let the spender pull the input token. `approve_mode: "exact"` (default) approves the swap amount; `"unlimited"` removes the cap                                                                                                                                                                                            |
| `permit2_approve` | TRON's `sun_universal_router` pulls the input through **Permit2**, a shared allowance contract: you approve the token to Permit2 (an ordinary `approve` step), then this step grants the router a permission inside Permit2. An exact permission expires 24 hours after the build, by network time; an `unlimited` one does not expire |
| `trust_set`       | XRP Ledger: a **trust line** is an account's opt-in to hold an issued currency; without one the account cannot receive the output. This step creates it. It reserves 0.2 XRP while the line exists                                                                                                                                     |

Setup steps are skipped when the existing permission already covers the amount. A `permit2_approve` is skipped only when the Permit2 permission also expires more than 10 minutes later.

A native input (ETH, TRX, SOL, TON, XRP) needs no approval, and the swap step's `value` equals `amount_in`.

{% hint style="warning" %}
**Do not rely on the number of steps.** It depends on the executor and on your wallet's current permissions: the same swap can be one step today and three tomorrow. Execute whatever `steps[]` holds, in order, by `index`.
{% endhint %}

### Executing the steps

Execute steps **strictly in order**. When a step has `wait_for_confirmation: true`, wait until its transaction is confirmed on chain before signing the next one.

When a step has `rebuild_after_previous: true`, do not sign it. Call `/v1/build` again with the same parameters after the previous step is confirmed, and continue with the steps of the **new** response (the steps you already executed are not in it). The typical EVM shape: `approve`, then a rebuild, then a `swap` with a measured gas limit and `simulation.status: "passed"`. The swap step of the first build was made before the approval existed: it carries no measured `gas` and was not simulated.

If the rebuild still returns an `approve` step for the same token, your approval is not visible yet. Wait a few seconds and rebuild; do not sign a second approve.

{% hint style="info" %}
A long pause between steps means a new build: `amount_out_min` is computed from fresh state, so a stale build sells at a stale minimum.
{% endhint %}

### Non-atomic routes

By default every route settles in one swap transaction (`allow_non_atomic: false`). With `allow_non_atomic: true`, EVM and TRON routes may consist of several swap transactions executed one after another. The risk factor `non_atomic` marks them, and each next swap gets its own deadline, another `deadline_seconds` from its build.

A **deal** is one such swap: the same `network`, `from_address`, `token_in`, `token_out` and `amount_in`. The API remembers a deal for **one hour**. On every rebuild of the same deal, pass the hashes of the transactions you already sent in `executed_tx_hashes` (up to 16). Swaps proven by their receipts are not rebuilt: the response's `token_in` and `amount_in` become the intermediate token and the amount the executed swap guaranteed, and a warning names it.

{% hint style="danger" %}
**Once the last swap of the deal is confirmed, stop: do not rebuild.** A rebuild then is a **new deal** that sells your original input again. Its warning reads `all N swaps of the previous build of this trade were executed; this build is a new trade`, and its `token_in` is the token you started with. If you see that after executing swaps, discard the response.
{% endhint %}

| Answer                | Meaning                                                          | What to do                                                                  |
| --------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `503 STATE_STALE`     | Your previous swap is not visible on chain yet (no receipt)      | Retry later, or pass its hash in `executed_tx_hashes`                       |
| `503 RPC_UNAVAILABLE` | The chain could not be read                                      | Retry later                                                                 |
| `400 INVALID_REQUEST` | The one-hour record expired (or was lost) and hashes were passed | Start the remainder as a new deal from the token the executed swap paid out |

Without hashes, a rebuild after the record expired builds a new deal from the original `token_in`.

{% hint style="warning" %}
On TRON there is no nonce: a swap the chain does not show yet is recognised **only** by its hash in `executed_tx_hashes`. Keep every transaction hash of a non-atomic deal until it is complete. Solana, TON and XRP Ledger ignore the field.
{% endhint %}

### Deadlines per network

`deadline_seconds` (default `120`) bounds how long the swap transaction stays valid. It counts from network time: `max(time of the last block, the API's clock) + deadline_seconds`. The response carries `deadline` (RFC 3339) only when every executor of the route checks it on chain; `executor.onchain_deadline` says which.

| Family     | Validity                                                                                                                                             |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| EVM, TRON  | `deadline_seconds` (1–86400), when the executor checks it on chain                                                                                   |
| Solana     | The transaction is valid until `last_valid_block_height`, about 60–90 seconds. `deadline_seconds` does not apply                                     |
| TON        | `validUntil` of the TON Connect request is the swap deadline. `deadline_seconds` takes 10–86400; keep it at most `300`, or the TON Connect SDK warns |
| XRP Ledger | `LastLedgerSequence` = validated ledger + 35, about 120 seconds. Past it, rebuild                                                                    |

### What can still fail on chain

A passed simulation is a statement about fresh state, not a promise about the future block:

* **The price moved beyond your slippage.** The swap reverts (the fee is still consumed); on XRP Ledger a partial payment delivers at least `DeliverMin` or fails.
* **The transaction expired.** It waited past its deadline, `last_valid_block_height` or `LastLedgerSequence`. Rebuild.
* **TRON energy exceeded the cap.** `fee_limit` is capped at 100 TRX; a swap that needs more energy than the cap buys fails with `OUT_OF_ENERGY`. The risk factor `fee_limit_capped` warns about it in the quote.
* **TON settles asynchronously.** The output arrives in later messages. A rejected swap returns your input; a rejected second hop leaves you holding the intermediate token (risk factor `async_settlement`).
* **XRP partial payments.** The real result is the `delivered_amount` of the validated transaction, which can be anywhere between `DeliverMin` and `Amount`. A `Batch` (a swap with a partner fee) applies both inner payments or none, and its result does not show them: check both inner transactions in the validated ledger.

Always read the settled result on chain: the transaction receipt on EVM and TRON, the balances or `delivered_amount` on Solana and XRP Ledger, the incoming messages on TON. The [Production Checklist](/guides/production-checklist.md) collects the full list.
