# Getting started

Easily integrate cross-chain swaps into your own application with SwapKit.

SwapKit offers a composable, user-friendly Partner API and Widget to integrate cross-chain swaps. SwapKit gives developers API access to a powerful suite of non-custodial, permissionless DeFi tools to interact with 6,000+ crypto assets across 30+ blockchains including Bitcoin, Ethereum, BNB Chain and Solana via many different providers, including Near Intents, Chainflip, Garden, Flashnet, Mayan or THORChain.

All transactions through SwapKit require a single signature by the user and integrators can earn trading fees from every trade.

SwapKit allows an easy access to:

* Cross-chain trading API with DEX Aggregation and transaction creation support
* Wallet interaction for multiple chains
* Cross-chain transactions tracking
* The possibility to check anti-money laundering (AML) compliance of the addresses involved in a trade before the transaction is signed.

SwapKit can be implemented through our [REST API](/swapkit-api/introduction), integrating cross-chain swaps into your application.

You can also implement SwapKit through [our Widget](/swapkit-widget/introduction), a drop-in, embeddable swap interface for your website or app.

### What is SwapKit?

SwapKit provides seamless access to cross-chain trading through multiple decentralized liquidity sources. It allows users to trade native assets in a single transaction without dealing with the complexity typically involved in cross-chain interactions.

SwapKit sources liquidity from NEAR Intents, THORChain, Chainflip, Flashnet, Garden, Harbor, Mayan, 1inch and Maya Protocol, which combined create a list of 30+ [supported chains](/swapkits-trade-offerings#cross-chain-trading-matrix), with more coming.

To initiate a cross-chain transaction through SwapKit, users only need gas on the originating chain. From there, they can access assets on any connected chain, including tokens aggregated from DEXs, reaching the full range of tokens on the destination blockchain.

For developers, SwapKit simplifies cross-chain swaps integration and ensures your application stays up to date.

* **Open-source SDK:** View the GitHub repository [here](https://github.com/thorswap/SwapKit).
* **Smart contracts:** Fully open source.
* **API:** Closed source proprietary code base.
* **Widget:** a drop-in integration of SwapKit, ready to implement into your site.


# SwapKit's trade offerings

Our API aggregates several key providers and protocols, offering access to an extensive range of chains and tokens without the complexity of managing multiple integrations. It is designed to integrate cross-chain swaps into your application with ease in addition to in-chain dex aggregation for single-chain swaps.

Combining our providers, we can offer a single signature trade experience from any asset in the Ethereum chain into native Bitcoin, or viceversa. You can swap into or from any asset in the EVM chains we have integrated, and soon we will have the same offering for Solana.

### Cross-chain trading matrix

With SwapKit, you can offer trades through 31 different chains but not all of them are interconnected. In the following table you can see which chains users can trade into with a single signature.

Bitcoin and Ethereum are integrated with all other chains we offer.

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="178">Source Chain</th><th>Output Chains</th></tr></thead><tbody><tr><td>Bitcoin</td><td>SOL, ETH, ARB, Base, BSC, Ripple, AVAX, Tron, ZCash, Sui, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Dash, Near, THORChain, MayaChain, Kujira, HOOD</td></tr><tr><td>Solana</td><td>BTC, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Near, HOOD</td></tr><tr><td>Ethereum</td><td>BTC, SOL, ARB, Base, BSC, Ripple, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Dash, Near, THORChain, MayaChain, Kujira, HOOD</td></tr><tr><td>Ripple</td><td>BTC, SOL, ETH, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Near, THORChain, HOOD</td></tr><tr><td>Arbitrum</td><td>BTC, SOL, ETH, Ripple, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dash, Near, THORChain, MayaChain, Kujira, HOOD</td></tr><tr><td>Base</td><td>BTC, SOL, ETH, Ripple, ARB, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Near, THORChain, HOOD</td></tr><tr><td>BSC</td><td>BTC, SOL, ETH, Ripple, ARB, Base, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Near, THORChain, HOOD</td></tr><tr><td>AVAX</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Near, THORChain</td></tr><tr><td>Tron (TRX)</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, ZCash, Sui, ADA,  TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Dogecoin, Cosmos, Near, THORChain, HOOD</td></tr><tr><td>ZCash</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira, HOOD</td></tr><tr><td>Sui</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira</td></tr><tr><td>Cardano (ADA)</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira</td></tr><tr><td>TON</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira, HOOD</td></tr><tr><td>Starknet</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira</td></tr><tr><td>Monad</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Near</td></tr><tr><td>Polygon</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash,Sui, ADA,  TON, Starknet, Monad, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Near, HOOD</td></tr><tr><td>Optimism</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Near, HOOD</td></tr><tr><td>Berachain</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin, Near</td></tr><tr><td>Gnosis</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, XLayer, ADI, LTC,  BCH, Dogecoin, Near</td></tr><tr><td>X Layer</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, ADI, LTC, BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira</td></tr><tr><td>ADI Chain</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, LTC, BCH, Dogecoin, Dash, Near, THORChain, MayaChain, Kujira</td></tr><tr><td>Litecoin</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, BCH, Dogecoin, Cosmos, Near, THORChain, HOOD</td></tr><tr><td>Bitcoin Cash</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, Dogecoin, Cosmos, Near, THORChain</td></tr><tr><td>Dogecoin</td><td>BTC, ETH, Base, BSC, Ripple, AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC, BCH, Cosmos, Near, THORChain</td></tr><tr><td>Cosmos</td><td>BTC, ETH, Base, BSC, Ripple, AVAX, Base, LTC, BCH, Dogecoin, THORChain</td></tr><tr><td>Dash</td><td>BTC, ETH, ARB, THORChain, MayaChain, Kujira</td></tr><tr><td>Near Chain</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC,  AVAX, Tron, ZCash, Sui, ADA, TON, Starknet, Monad, Polygon, OP, Bera, Gnosis, XLayer, ADI, LTC,  BCH, Dogecoin</td></tr><tr><td>THORChain</td><td>BTC, ETH, Ripple, ARB, Base, BSC, AVAX, LTC, BCH, Dogecoin, Cosmos, Dash, MayaChain, Kujira</td></tr><tr><td>MayaChain</td><td>BTC, ETH, ARB, Dash, THORChain, Kujira</td></tr><tr><td>Kujira</td><td>BTC, ETH, ARB, Dash, THORChain, MayaChain</td></tr><tr><td>Robinhood (HOOD)</td><td>BTC, SOL, ETH, Ripple, ARB, Base, BSC, Tron, ZCash, TON, Polygon, OP, LTC</td></tr></tbody></table>

### Supported chains and providers

Listed by provider, here’s a breakdown of our currently supported blockchains:

* **THORChain:** BTC, ETH, Ripple, BSC, AVAX, Tron, Base, Cosmos (ATOM), Dogecoin, Litecoin, Bitcoin Cash, THORChain.
* **NEAR:** BTC, Solana, ETH, Ripple, Arbitrum, Base, BSC, Avalanche, Tron, ZCash, Sui, Cardano, TON, Starknet, Monad, Polygon, Optimism, Berachain, Gnosis, X Layer, ADI, Dogecoin, Litecoin, Bitcoin Cash, Near Chain.
* **MAYAChain:** BTC, ETH, Arbitrum, Dash, ZCash, Kujira, THORChain, MAYAChain.
* **Chainflip:** BTC, Solana, ETH, Arbitrum.
* **Garden:** BTC, Solana, ETH, Arbitrum, BSC, Base, Robinhood.
* **Flashnet:** BTC, Solana, ETH, Ripple, Arbitrum, Base, BSC, Tron, ZCash, Polygon, Optimism, Litecoin, TON, Robinhood.
* **Mayan**: Solana, ETH, Arbitrum, Base, BSC, Avalanche, Sui, Monad, Polygon, Optimism.
* **Harbor:** BTC and ETH.
* **Uniswap:** ETH and Arbitrum assets.
* **1inch:** ETH, BSC, BASE, AVAX, Arbitrum, Gnosis, Optimism, Polygon and Robinhood assets.
* **Jupiter:** Solana assets

Additionally, THORChain, MAYAChain and Chainflip have a corresponding Streaming provider. Streaming a swap performs it over time, improving price execution in exchange for a longer execution time. It is especially useful for larger swaps.


# Monetization

You can monetize your SwapKit integration and collect fees.

To monetize your integration, follow the steps below to register and set up your affiliate configuration.

### Step 1: Register in the SwapKit Partner's Dashboard

You can register yourself at [SwapKit's dashboard](https://dashboard.swapkit.dev/) and have your API key ready to use.\
If you need help through the process you can [contact us here](https://partnerships.swapkit.dev/) and schedule a short call with us.

### Step 2: Create an app and configure 2FA

Create an app and enter the `Affiliate Config` . You will be prompted to activate two-factor authentication, which is needed before you can set up affiliate configuration.

<figure><img src="/files/Cf229w6nMivEkezLY0XF" alt="" width="563"><figcaption></figcaption></figure>

To use 2FA you will need an authenticator application. You should also store the recovery codes shown in case you lose access to your authenticator application.

### Step 3: Configure the app's general fee tiers

General configuration allows you to set default fee tiers for all the different swap providers. It has settings for value ranges and the option to set up a specific fee tier for stablecoin swaps.

<figure><img src="/files/6ESiSITg9KbnozPQX5t1" alt="" width="563"><figcaption></figcaption></figure>

### Step 4: Configure provider specific settings

For each swap provider, you need to setup an address to collect the fees. You can use the same address for all of them but you need to register accordingly for each provider.

You can also define specific **affiliate fee tiers** in each provider configuration. These will override the general fee tiers.

### Step 5: Monetize API requests

When making requests to the `/v3/quote`  endpoint, use the **`x-api-key`** header with the generated API key. The affiliate fee tier will be applied automatically, ensuring you earn the configured fees for each transaction.&#x20;

There is no need to pass the specific affiliate settings when requesting a quote if it is already set up using our dashboard, but passing them will override any settings you have set up on the dashboard.

***

### Fee collection by provider

The `affiliateFee` you set on `/v3/quote` (or via the dashboard fee tiers) controls how much is charged per swap. This section covers what happens to those fees *after* they are charged.

SwapKit collects your fees and sends them to you automatically across all providers — you don't need to interact with any provider directly. What varies by provider is **the asset your fees are paid in**, and **whether they're automatically converted to a preferred asset** (only THORChain and Maya do this). The sections below cover each one.

| **THORChain**    | RUNE                                                                                               | Yes — auto-converts to your preferred asset (THORName); else RUNE  |
| ---------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **Maya**         | CACAO                                                                                              | Yes — auto-converts to your preferred asset (MAYAName); else CACAO |
| **NEAR Intents** | The asset being swapped (varies per swap)                                                          | No — paid in the asset earned                                      |
| **Jupiter**      | The swap's token (varies)                                                                          | No — paid in the asset earned                                      |
| **1inch**        | The swap's token (varies)                                                                          | No — paid in the asset earned                                      |
| **Garden**       | USDC (on Ethereum)                                                                                 | No — paid in USDC                                                  |
| **Chainflip**    | USDC                                                                                               | No — paid in USDC                                                  |
| **Flashnet**     | USDC (on Solana)                                                                                   | No — paid in USDC                                                  |
| **Mayan**        | Varies by route — the token you receive or the token you sold, and it can land on different chains | No — you receive/withdraw whatever token accrued                   |

***


# Partnership

Reach out to our team.

You can register yourself at [SwapKit's dashboard](https://dashboard.swapkit.dev/) and have your API key ready to use.

If you would like to speak with the team, you can reach out via the [Swapkit's Discord](https://discord.com/invite/swapkit) channel, contact us directly through [the form in our website](https://swapkit.dev/contact/#), or book a meeting with us at [partnerships.swapkit.dev](https://partnerships.swapkit.dev/).


# Smart contract limitations & edge cases

Key corner cases to take into account when integrating with our cross-chain providers

When integrating with THORChain or Mayachain, certain limitations at the smart contract and transaction level must be taken into account. Failing to do so may result in failed swaps, rejected transactions, or permanent loss of funds.&#x20;

### EVM Transaction Types

THORChain and Mayachain do not support EVM Type 4 transactions introduced in [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), which involve account abstraction. This includes:

* All `type: 4` transactions
* Transactions from wallets based on abstracted accounts

Any transaction using this format will be rejected and **may lead to loss of funds** if funds are transferred under an unsupported transaction structure.

The following EVM transaction types are supported:

* `type: 0` (Legacy)
* `type: 2` (EIP-1559)

The `includeTx` optional argument when requesting a quote returns a legacy transaction object.

Chainflip, by contrast, does not impose restrictions on transaction types. All EVM transaction types, including Type 4, are supported when depositing on a Chainflip channel.

### Transaction Log Limit

For inbound transactions into THORChain and Mayachain, the maximum number of logs that a transaction can include is **50 logs**.

This limitation is particularly important for integrators who use smart contract wrappers to:

* Perform token swaps
* Route through aggregators
* Execute any custom logic **before** sending funds to THORChain or Mayachain

If the resulting transaction generates more than 50 logs (e.g., due to complex DeFi interactions), **THORChain will not process it**.

### THORChain, Mayachain & Smart Contract Limitations <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Thorchain has strict gas limits when sending assets to smart contracts. Wallet-emitting events require gas and might break compatibility with Thorchain normal swap flow.\
By default, SwapKit API checks if the sender and recipient addresses are smart contract and will filter out `THORCHAIN`, `THORCHAIN_STREAMING`, `MAYACHAIN` and `MAYACHAIN_STREAMING` providers.\
You may use the `allowSmartContractReceiver`  after confirming with SwapKit developers that your wallet respects Thorchain gas requirements.

### Solana multisig vaults & PDAs (off-curve addresses)

Ordinary Solana wallets are on-curve ed25519 public keys. Program Derived Addresses (PDAs) are off-curve by construction and have no private key, so they cannot sign transactions. This includes Squads multisig vaults, Associated token accounts (ATAs) and Program-owned state accounts.

These are valid, well-formed Solana addresses, so SwapKit treats them the same way it treats EVM smart-contract counterparties: valid, but only accepted when you explicitly opt in. SwapKit rejects them by default (`invalidDestinationAddress` / `invalidSourceAddress`), funds sent to one are unrecoverable. Never send to a token account (ATA); use the owner address.

**To swap into a vault you control:**

1. `/v3/quote` — `destinationAddress` is optional and *never validated* (used only for screening).&#x20;
2. `/v3/swap` — pass `destinationAddress` + `allowSmartContractReceiver: true`.

`allowSmartContractSender` works the same way for off-curve *source* addresses, on `/v3/swap` only. (A PDA can't sign a plain transfer, so a multisig source needs your own signing flow regardless.)

**Current limitations:**

Chainflip → SOL token (via Jupiter) — requires on-curve owner

### Exchange deposit addresses as source or refund address <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Refunds return to `sourceAddress` — there is no separate `refundAddress` on `/v3/swap`. SwapKit derives it from `sourceAddress`, including Chainflip's `refundParameters.refundAddress` and the refund address in the Maya memo for Zcash.

Do not use a centralised exchange deposit address. It passes validation and screening, so nothing in the API rejects it, but the exchange may not credit the refund — some only credit while actively expecting a deposit, and some flag a provider's outbound address as risky and stop crediting anything from it. The refund succeeds on-chain while the user still cannot reach the funds, and recovery is up to the exchange's support process. The same applies to `destinationAddress`.

Use a self-custody address the user holds keys for, and surface this if your interface lets them paste an arbitrary address.

### XRP through THORChain limitations <a href="#transaction-log-limit" id="transaction-log-limit"></a>

Integrators should be aware of if they are not using our `includeTx` optional argument to build transactions for XRP swaps through THORChain:

* **Transaction Type**: Only `Payment` transactions are supported for both inbound and outbound transfers.
* **Address Format**: Only **classic XRP addresses** (starting with `r...`) are supported. X-addresses (which encode both address and destination tag) are **not supported** and must be decoded before use.
* **Memo Field**:
  * THORChain supports only the first entry in the `Memos[]` array.
  * Additional memos (if present) will be ignored.
  * Maximum memo length: 250 characters, any excess will be ignored.

For reliable behavior, always use classic addresses and ensure your memo data fits within the 250-character limit.


# Introduction

Our API aggregates several key providers and protocols, offering access to an extensive range of chains and tokens without the complexity of managing multiple integrations. It is designed to integrate cross-chain swaps into your application with ease in addition to in-chain dex aggregation for single-chain swaps.

The latest version of our API is version 3, with endpoints identified by `v3` in the call. It represents a new flow that clearly separates the responsibilities of a swap into two distinct calls: fetching a price and building the swap transaction.

On top of that, SwapKit's fees are reduced from a fixed 0.15% on all swaps to:

* 0.15% on swaps between $0 and $500k.
* 0.12% on swaps between $500k and $1M.
* 0.10% on swaps above $1M.
* 0.01% on stablecoin to stablecoin swaps.

### How to use SwapKit's API

Register for an API key [in our dashboard](https://dashboard.swapkit.dev/) and be ready to get started.\
To get the most out of SwapKit's API, we recommend going through the endpoints in the following order:

1. **`/providers`** - Retrieve a list of available providers and their supported chains.
2. **`/tokens`** - Fetch a list of supported tokens across different chains and providers.
3. **`/v3/quote`** - Price discovery only. Returns available routes with expected amounts, fees and execution times. Quotes are cached for five minutes.
4. **`/v3/swap`** - Transaction execution. Takes a `routeId` from a quote and builds a ready-to-broadcast transaction.
5. **`/track`** - Track the status of a swap to monitor its progress and completion.

However, `/providers` and `/tokens` don't change often, and you can directly request a quote once you have everything set up. As an alternative, you can use the `/swapTo` endpoint to find the swap connections between tokens, similar to the [cross-chain trading matrix](/swapkits-trade-offerings#cross-chain-trading-matrix).\
Every address involved in a **`/v3/swap`** call is validated for AML compliance. An address could receive a valid quote but be refused on this step. We also offer [SLIP-0024 verification](/spotlights/slip-0024-transaction-payload-signing) for our returned transaction data.<br>

Read our recommended implementation flow [here in our documentation](/swapkit-api/quote-and-swap-implementation-flow).\
Register for an API key [in our dashboard](https://dashboard.swapkit.dev/) and try it yourself at <https://api.swapkit.dev/docs/>. You can also find the OpenAPI definitions [here](https://api.swapkit.dev/docs/json).\
Check out our transaction tracking interface at <https://track.swapkit.dev/>.<br>


# Swap Types

Understand the different swap types offered by SwapKit

SwapKit supports three distinct swap mechanisms, each serving different use cases and offering unique advantages.

### Cross-Chain Swaps

Cross-chain swaps enable direct asset exchanges between different blockchain networks in a single transaction. They are powered by specialized cross-chain protocols that act as bridges between otherwise isolated blockchains and support the major tokens on each chain.

#### Available Providers

SwapKit integrates with eight major cross-chain swap providers:

* NEAR Intents
* THORChain
* Chainflip
* Flashnet
* Mayan
* Garden
* Harbor
* Maya Chain

Each provider offers a different chain coverage. More information is available [here](/swapkits-trade-offerings).

### Single-Chain Swaps

Single-chain swaps occur within a single blockchain ecosystem, leveraging decentralized exchanges (DEXs) and aggregators to find the best rates for token swaps on that specific chain.

#### Current Provider

* 1inch, available on:
  * Ethereum (ETH)
  * Avalanche (AVAX)
  * Arbitrum (ARB)
  * BNB Smart Chain (BSC)
  * BASE
  * Polygon
* Jupiter, available on Solana (SOL)

For a complete list of supported tokens on each chain, query the `/tokens` endpoint.

### Cross-Chain DEX Aggregation

Some tokens other than the gas assets like ETH, BNB or AVAX are available through our cross-chain providers, but others like most alt-coins are not. Cross-chain DEX aggregation combines single-chain DEX aggregators with cross-chain protocols to access any token on supported chains. This is achieved through two mechanisms:

#### Swap Ins

Swap ins perform a single-chain swap followed by a cross-chain transfer. This allows users to swap from any token in the source chain into the major tokens in other supported chains.

Swap Ins are currently supported with the combination of 1inch + THORChain (with support for other providers coming soon).

**Supported Networks:** available for swaps initiated on networks where THORChain and 1inch overlap:

* Ethereum (ETH)
* Avalanche (AVAX)
* BNB Smart Chain (BSC)
* BASE

Into any chain supported by THORChain.

#### Swap Outs

Swap outs perform the reverse operation, a cross-chain swap first followed by a single-chain swap on the destination chain.

**Current Status:** **Temporarily Disabled**

### Using the /quote Endpoint

The `/v3/quote` endpoint intelligently routes your swaps through the appropriate providers based on the assets and chains involved.\
As you delve into our documentation, come back here to understand more about the logic behind integrating Cross-Chain DEX Aggregation.

#### Provider Selection

The `providers` parameter is **optional**. When not specified, the API will:

* Search all available routing paths
* Return the routes found
* Include the providers used in the response

If you specify the `providers` parameter but it's more restrictive than the available options, you'll receive an error:

```json
{
  "error": "noRoutesFound",
  "message": "No routes found for ETH.CRV-0xD533a949740bb3306d119CC777fa900bA034cd52 -> BTC.BTC",
  "data": {
    "sellAsset": "ETH.CRV-0xD533a949740bb3306d119CC777fa900bA034cd52",
    "buyAsset": "BTC.BTC"
  }
}
```


# Quote and Swap Implementation flow

Implement a correct quoting flow

A key part of our service is the ability to include transaction data in a `/swap` response.\
After already implementing our [/providers](/swapkit-api/providers-providers-status-and-identifiers-mapping) and their [/tokens](/swapkit-api/tokens-list-and-search-supported-tokens), you will use the [/quote](/swapkit-api/v3-quote-request-a-swap-quote) and [/swap](/swapkit-api/v3-swap-obtain-swap-transaction-details) endpoints to process transactions for your users.

### 1. Initial Quote Request

First, fetch quotes with the [/v3/quote](/swapkit-api/v3-quote-request-a-swap-quote) endpoint. For this step,  `"sourceAddress"` and `"destinationAddress"` are not needed and instead you are just quoting the price of a swap:

```bash
curl -X POST "https://api.swapkit.dev/v3/quote" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "slippage": 3
  }'
```

You can filter for the providers you have integrated using the `"providers"` argument if you want to limit the options shown.

This returns a regular quote response including pricing information, estimated timing and fees, and route details with the available providers, but without transaction data. The `"nextActions"` object will inform you of the following steps to take. Generally, you will provide the `routeId` into the `/swap` endpoint.

### 2. Identify the Provider route you want to use

Once you have presented a price to the user, they would then accept it. This determines which provider offers the best match for the route you quoted. The available providers are labeled with the `"RECOMMENDED"`, `"FASTEST"` and `"CHEAPEST"` tags.

Then, before presenting the user with a transaction to sign, you will call the `/swap` endpoint, referencing the chosen quote via its `routeId`.

### 3. Request a /swap - Transaction Building

When the user is ready to execute the swap, request the [/v3/swap](/swapkit-api/v3-swap-obtain-swap-transaction-details) endpoint with the selected `routeId` along with the user's  `sourceAddress`  and `destinationAddress`

```bash
curl -X POST "https://api.swapkit.dev/v3/swap" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "routeId": "eed91159-86bd-4674-9558-48f7e4f8bac0",
    "sourceAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P"
}'
```

This endpoint can take a bit longer to reply. SwapKit builds the transaction payload, including fetching UTXOs and building PSBT when required, does a balance check and also performs **address screening on every `/swap` call automatically**.

The `routeId` is valid for 5 minutes. After it expires, a new quote is fetched using the original parameters before the swap request is processed.

The swap will include transaction-related fields under `tx`, which may be either an object or a string. It also includes the price, so you can display it to the user again before signing or compare it internally against the previous value before presenting it:

```json
{
  // ... all fields from above, plus:
  "targetAddress": "1FSHYruFaYjm5FQrzB3LmF15FMC3QwUFmy",
  "inboundAddress": "1FSHYruFaYjm5FQrzB3LmF15FMC3QwUFmy",
  "tx": "cHNidP8BAHUCAAAAAc86GvpcKcJoCV1YpX9orAcE/ZckHbLzuTXOPl0McccZAAAAAAD/////AoCWmAAAAAAAGXapFJ5Z+YVfu2g/WM/XGmu9FBGSeS5wiKx/LhEm4wAAABepFFKL8plEPOVGF9/52YVT+SKl3opehwAAAAAAAQEg/8SpJuMAAAAXqRRSi/KZRDzlRhff+dmFU/kipd6KXocAAAA=",
  "meta": {
    // ... existing meta fields, plus:
    "txType": "PSBT"
  }
}
```

If [SLIP-0024 signing](/spotlights/slip-0024-transaction-payload-signing) is enabled for your API key, the response also includes a signed payload ready for verification.

***

### Transaction Building Process

When calling the `/swap` endpoint, the system performs the following validations:

#### 1. Balance Verification

SwapKit first checks if the source address has sufficient balance to cover the `sellAmount`. If insufficient funds are detected, an `insufficientBalance` error is thrown for the entire request.

#### 2. Transaction Building

If the balance check passes, the system attempts to build the transaction. In certain edge cases, the wallet may have enough balance to match the `sellAmount`, but insufficient funds to cover network fees. Since different providers have varying transaction sizes, some may return valid transaction data while others might fail with an `unableToBuildTransaction` error.

#### 3. Successful Response

If all validations pass, you will receive a complete route with transaction data that can be directly signed and broadcasted.

### Key Fields

* `targetAddress` The deposit address where funds should be sent, or the address of the contract that should be called.
* `inboundAddress` The address monitoring for incoming transactions.
* `tx` The transaction data ready for signing.
* `txType` The format of the transaction data (e.g., "PSBT" for Bitcoin or "EVM" for EVM chains).

### Error Handling

Be prepared to handle the different errors that can be returned. You can find more details on the [swap endpoint page](https://app.gitbook.com/o/amdCeBqeAzowzd0wBh9E/s/l88kbsjWGhgdARxzgqCE/~/edit/~/changes/60/swapkit-api/v3-swap-obtain-swap-transaction-details#swap-errors).

* `insufficientBalance` The source address doesn't have enough tokens for the swap.
* `insufficientAllowance`: The source address doesn't have enough tokens approved for the contract interaction. Relevant for tokens in EVM networks.
* `unableToBuildTransaction` The wallet has sufficient tokens but insufficient funds for network fees.

This is an example error for a wallet without enough balance:

```json
{
  "message": "Cannot build transaction. Insufficient balance for asset ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 amount 700 address 0x...",
  "error": "insufficientBalance",
  "data": {
    "chain": "ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "amount": "700",
    "address": "0x..."
  }
}
```


# /providers - Providers, status, and identifiers mapping

The `/providers` endpoints describe which swap providers SwapKit has integrated, the chains they support, and their current operational status. All endpoints are `GET` and require your API key in the `x-api-key` header. Chains are reported as chain IDs throughout. See the table at the bottom for reference.

| Endpoint                         | Purpose                                                                                                               |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `/providers`                     | Full list of providers with token-list metadata, supported chains, and the chains currently enabled for you           |
| `/providers/status`              | Per-provider swap status: supported chains, the SwapKit-level enabled/disabled split, and your API key's own settings |
| `/providers/identifiers-mapping` | Mapping between SwapKit asset identifiers and a provider's native identifiers                                         |

{% hint style="info" %}
The set of providers is dynamic — don't hard-code it. Streaming providers with the `_STREAMING` suffix split a swap into sub-swaps over time. They are slower to execute, but often price better on larger trades where slippage matters. Non-streaming `THORCHAIN` and `MAYACHAIN` are not listed. They are superseded by `THORCHAIN_STREAMING` and `MAYACHAIN_STREAMING`, which are what SwapKit routes through.
{% endhint %}

***

### GET `/providers`

Returns every provider integrated by SwapKit, with token-list metadata and chain support. Takes no query parameters, but note that `enabledChainIds` is evaluated against your API key — your key's own settings are applied.

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/providers' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'
```

### Response fields

Each object in the returned array contains:

| Field                             | Description                                                                                                                                                                             |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` / `provider`               | Provider identifier used to reference it in SwapKit                                                                                                                                     |
| `displayName` / `displayNameLong` | Human-readable names such as `"Chainflip"` or `"Chainflip Streaming"`                                                                                                                   |
| `keywords`                        | Search keywords for the provider                                                                                                                                                        |
| `count`                           | Number of supported tokens                                                                                                                                                              |
| `logoURI`                         | URL to the provider's logo                                                                                                                                                              |
| `url`                             | URL to the provider's token list                                                                                                                                                        |
| `supportedActions`                | Actions the provider supports. Currently `["swap"]`                                                                                                                                     |
| `supportedChainIds`               | Every chain the provider can support — its full capability, regardless of current status                                                                                                |
| `enabledChainIds`                 | Subset of `supportedChainIds` currently usable for your API key. Empty if the provider is paused. Otherwise excludes chains disabled at the SwapKit level or by your key's own settings |

{% hint style="info" %}
`supportedChainIds` is what a provider can do. `enabledChainIds` is what it can do right now, for you. When a provider is paused, it still appears with its full `supportedChainIds`, but with an empty `enabledChainIds`.
{% endhint %}

#### Example — a fully enabled provider

```json
{
  "name": "ONEINCH",
  "provider": "ONEINCH",
  "displayName": "1inch",
  "displayNameLong": "1inch V6",
  "keywords": ["oneinch", "1inch", "1inch.exchange"],
  "count": 1351,
  "logoURI": "https://storage.googleapis.com/token-list-swapkit/images/eth.1inch-0x111111111117dc0aa78b770fa6a738034120c302.png",
  "url": "https://storage.googleapis.com/token-list-swapkit/oneinch.json",
  "supportedActions": ["swap"],
  "supportedChainIds": ["42161", "43114", "8453", "56", "1"],
  "enabledChainIds": ["42161", "43114", "8453", "56", "1"]
}
```

#### Example — a paused provider

```json
{
  "name": "THORCHAIN_STREAMING",
  "provider": "THORCHAIN_STREAMING",
  "displayName": "THORChain",
  "displayNameLong": "THORChain Streaming",
  "count": 40,
  "url": "https://storage.googleapis.com/token-list-swapkit/thorchain_streaming.json",
  "supportedActions": ["swap"],
  "supportedChainIds": ["litecoin", "thorchain-1", "1", "728126428", "43114", "8453", "bitcoincash", "56", "bitcoin", "dogecoin", "cosmoshub-4", "ripple", "solana"],
  "enabledChainIds": []
}
```

***

### GET `/providers/status`

Returns an API-key-aware swap-status overview. Use this to understand why a provider or chain is or isn't available to you. Only the `swap` action is reflected.

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/providers/status' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'
```

The response is a single `providersChains` array, one entry per provider:

| Field                   | Description                                                                                                                                                             |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                  | Provider                                                                                                                                                                |
| `supportedChainsGlobal` | Every chain the provider has a swap entry for — its full capability                                                                                                     |
| `enabledChainsGlobal`   | Chains currently usable at the SwapKit level                                                                                                                            |
| `disabledChainsGlobal`  | Chains not usable at the SwapKit level — either the chain's swap status is disabled, or the provider is globally paused, in which case all supported chains appear here |
| `enabledChainsApiKey`   | Chains your API key has explicitly enabled                                                                                                                              |
| `disabledChainsApiKey`  | Chains your API key has explicitly disabled                                                                                                                             |

For every provider, `enabledChainsGlobal` and `disabledChainsGlobal` together partition `supportedChainsGlobal`. The `...ApiKey` lists contain only chains your key has an explicit setting for. Each is a subset of `supportedChainsGlobal`.

{% hint style="info" %}
The `...ApiKey` lists were historically called "overrides." They no longer override the SwapKit-level status. They are simply your key's own setting for a given provider-chain, reported alongside the global status so you can see both.
{% endhint %}

```json
{
  "providersChains": [
    {
      "name": "MAYAN",
      "supportedChainsGlobal": ["1", "10", "137", "143", "42161", "43114", "56", "8453", "solana", "sui"],
      "enabledChainsGlobal": ["1", "10", "137", "143", "42161", "43114", "56", "8453", "solana"],
      "disabledChainsGlobal": ["sui"],
      "enabledChainsApiKey": ["1", "10", "42161", "43114", "56", "8453", "solana", "sui"],
      "disabledChainsApiKey": ["137", "143"]
    },
    {
      "name": "THORCHAIN_STREAMING",
      "supportedChainsGlobal": ["1", "43114", "56", "728126428", "8453", "bitcoin", "bitcoincash", "cosmoshub-4", "dogecoin", "litecoin", "ripple", "solana", "thorchain-1"],
      "enabledChainsGlobal": [],
      "disabledChainsGlobal": ["1", "43114", "56", "728126428", "8453", "bitcoin", "bitcoincash", "cosmoshub-4", "dogecoin", "litecoin", "ripple", "solana", "thorchain-1"],
      "enabledChainsApiKey": ["1", "43114", "56", "728126428", "8453", "bitcoin", "bitcoincash", "cosmoshub-4", "dogecoin", "litecoin", "ripple", "thorchain-1"],
      "disabledChainsApiKey": ["solana"]
    }
  ]
}
```

`MAYAN` shows all the moving parts at once: a chain disabled at the SwapKit level (`sui`), and an API key that independently re-enables `sui` while disabling `137` and `143`.

`THORCHAIN_STREAMING` shows the global pause forcing every chain into `disabledChainsGlobal`.

***

### GET `/providers/identifiers-mapping`

Returns the mapping between SwapKit asset identifiers and a provider's native asset identifiers. The optional `provider` query parameter narrows the result to one provider.

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/providers/identifiers-mapping?provider=CHAINFLIP' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'
```

| Parameter  | In      | Required | Description                               |
| ---------- | ------- | -------- | ----------------------------------------- |
| `provider` | `query` | No       | Restrict the mapping to a single provider |

***

### Chain IDs and corresponding names

All Providers endpoints report chains using these IDs. They are standard across the crypto ecosystem:

| Chain ID               | Chain name                |
| ---------------------- | ------------------------- |
| `1`                    | Ethereum Mainnet          |
| `42161`                | Arbitrum One              |
| `43114`                | Avalanche C-Chain         |
| `8453`                 | Base                      |
| `56`                   | Binance Smart Chain (BSC) |
| `137`                  | Polygon                   |
| `10`                   | Optimism                  |
| `100`                  | Gnosis                    |
| `80094`                | BeraChain                 |
| `143`                  | Monad                     |
| `196`                  | X-Layer - OKB             |
| `728126428`            | Tron - TRX                |
| `36900`                | Adi                       |
| `4663`                 | Robinhood Chain           |
| `0x534e5f4d41494e`     | Starknet                  |
| `cardano`              | Cardano                   |
| `ton`                  | TON                       |
| `solana`               | Solana                    |
| `bitcoin`              | Bitcoin                   |
| `bitcoincash`          | Bitcoin Cash              |
| `litecoin`             | Litecoin                  |
| `ripple`               | XRP - Ripple              |
| `cosmoshub-4`          | Cosmos Hub                |
| `near`                 | Near Chain                |
| `dash`                 | Dash                      |
| `zcash`                | ZCash                     |
| `sui`                  | Sui                       |
| `dogecoin`             | Dogecoin                  |
| `kaiyo-1`              | Kujira                    |
| `mayachain-mainnet-v1` | MayaChain                 |
| `thorchain-1`          | THORChain                 |
| `hype`                 | Hype                      |


# /tokens - List and search supported tokens

## `GET /tokens`

The `/tokens` endpoint provides a list of tokens for a specified provider. This endpoint requires a query parameter to determine which provider's tokens you want to retrieve.

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/tokens`

#### Example Request

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/tokens?provider=CHAINFLIP' \
  -H 'accept: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE"
```

***

### Example shortened response

```json
{
  "provider": "CHAINFLIP",
  "name": "CHAINFLIP",
  "timestamp": "2025-01-11T16:31:04.355Z",
  "version": {
    "major": 1,
    "minor": 0,
    "patch": 0
  },
  "keywords": [],
  "count": 10,
  "tokens": [
    {
      "chain": "BTC",
      "chainId": "bitcoin",
      "ticker": "BTC",
      "identifier": "BTC.BTC",
      "symbol": "BTC",
      "name": "Bitcoin",
      "decimals": 8,
      "logoURI": "https://storage.googleapis.com/token-list-swapkit/images/btc.btc.png",
      "coingeckoId": "bitcoin"
    },
    {
      "chain": "ARB",
      "chainId": "42161",
      "ticker": "ETH",
      "identifier": "ARB.ETH",
      "symbol": "ETH",
      "name": "Arbitrum Ether",
      "decimals": 18,
      "logoURI": "https://storage.googleapis.com/token-list-swapkit/images/arb.eth.png",
      "coingeckoId": "ethereum"
    },
    {
      "chain": "SOL",
      "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "chainId": "solana",
      "ticker": "USDC",
      "identifier": "SOL.USDC-EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "symbol": "USDC-EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "name": "Solana USDC",
      "decimals": 6,
      "logoURI": "https://storage.googleapis.com/token-list-swapkit/images/sol.usdc-epjfwdd5aufqssqem2qn1xzybapc8g4weggkzwytdt1v.png"
    },
    ...
  ]
}
```

***

### Response fields

The relevant response fields are:

<table><thead><tr><th width="257">Field</th><th width="102">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>provider</code> and <code>name</code></td><td><code>string</code></td><td>The name of the provider specified in the query.</td></tr><tr><td><code>timestamp</code></td><td><code>string</code></td><td>The timestamp of when the response was generated.</td></tr><tr><td><code>count</code></td><td><code>number</code></td><td>The number of tokens included in the response.</td></tr><tr><td><code>tokens</code></td><td><code>array</code></td><td>An array of token objects, each representing a token available for the specified provider.</td></tr></tbody></table>

#### Each token includes information to properly distinguish it from others. Note the `identifier` in particular, as it's what identifies a token within the SwapKit API:

<table><thead><tr><th width="257">Field</th><th width="102">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>chain</code></td><td><code>string</code></td><td>The blockchain the token is associated with (e.g., <code>ETH</code>, <code>BTC</code>, <code>SOL</code>).</td></tr><tr><td><code>address</code></td><td><code>string</code></td><td>The contract address of the token (only provided if it is not the gas token of the network).</td></tr><tr><td><code>chainId</code></td><td><code>string</code></td><td>The ID of the chain. It could be a numeric ID (e.g., <code>42161</code> for Arbitrum) or a name like <code>solana</code>.</td></tr><tr><td><code>ticker</code></td><td><code>string</code></td><td>The ticker symbol of the token (e.g., <code>ETH</code>, <code>BTC</code>).</td></tr><tr><td><code>identifier</code></td><td><code>string</code></td><td>An identifier for the token that combines chain and token address, used to identify the token in SwapKit API.</td></tr><tr><td><code>symbol</code></td><td><code>string</code></td><td>The token symbol, which includes address information.</td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>The full name of the token.</td></tr><tr><td><code>decimals</code></td><td><code>number</code></td><td>The number of decimal places the token supports.</td></tr><tr><td><code>logoURI</code></td><td><code>string</code></td><td>A URL pointing to the token's logo image.</td></tr><tr><td><code>coingeckoId</code></td><td><code>string</code></td><td>The identifier of the token on CoinGecko (if available).</td></tr></tbody></table>

***

### Notes

* The `identifier` is what SwapKit uses to uniquely identify a token in all its requests and responses. It includes the chain, the name and the token address. It is how the token should be named when using the `/quote` endpoint.
  * The identifier is built by putting together Chain.Ticker-ContractAddress (e.g., `"ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"`).
  * Gas assets like BTC, ETH or SOL don't require a contract address, so their identifier is just `BTC.BTC` for example.
* Tokens with an `address` field are specific instances of a token on a given chain (e.g., `USDC` on Ethereum or Solana). While the `coingeckoId` could be shared, we provide the relevant contract address for the token in the specific chain.

***

## `GET /tokens/search` - Search tokens

The `/tokens/search` endpoint runs a free-text search across SwapKit's combined provider token lists. Built for search-box UX, it accepts a ticker (`usdc`), name (`usd coin`), contract address, or full SwapKit identifier (`ETH.USDC-0x…`), and returns deduplicated results ranked by relevance.

Like all `/tokens/*` endpoints, it is protected by the `x-api-key` header and rate-limited.

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/tokens/search`

#### Example Request

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/tokens/search?query=usdc&chain=ETH&limit=20' \
  -H 'accept: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE"
```

Searching by contract address works too:

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/tokens/search?query=0xdAC17F958D2ee523a2206206994597C13D831ec7' \
  -H 'accept: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE"
```

***

### Query parameters

<table><thead><tr><th width="140">Parameter</th><th width="110">Type</th><th width="109.9296875">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>query</code></td><td><code>string</code></td><td>Yes</td><td>Free text, 3–256 characters. Matched case-insensitively against <code>identifier</code>, <code>ticker</code>, <code>symbol</code>, <code>name</code>, and contract <code>address</code>.</td></tr><tr><td><code>chain</code></td><td><code>string</code></td><td>No</td><td>Only return assets on this chain (e.g. <code>ETH</code>, <code>BTC</code>, <code>SOL</code>).</td></tr><tr><td><code>provider</code></td><td><code>string</code></td><td>No</td><td>Only match assets present in this provider's token list (e.g. <code>ONEINCH</code>). Restricts what can match, not what is reported in <code>providers</code>.</td></tr><tr><td><code>limit</code></td><td><code>number</code></td><td>No</td><td>Page size, 1–100. Defaults to <code>100</code>.</td></tr><tr><td><code>page</code></td><td><code>number</code></td><td>No</td><td>1-based page number (≥ 1). Defaults to <code>1</code>.</td></tr></tbody></table>

***

### Example response

```json
{
  "tokens": [
    {
      "identifier": "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
      "chain": "ETH",
      "chainId": "1",
      "ticker": "USDC",
      "symbol": "USDC-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
      "name": "USD Coin",
      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "decimals": 6,
      "logoURI": "https://…",
      "coingeckoId": "usd-coin",
      "providers": ["ONEINCH", "UNISWAP_V3"],
      "marketCapUsd": 30000000000
    },
    ...
  ],
  "total": 42,
  "page": 1,
  "limit": 100,
  "hasMore": false
}
```

***

### Response fields

<table><thead><tr><th width="140">Field</th><th width="110">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>tokens</code></td><td><code>array</code></td><td>Array of matching token objects, ranked most relevant first (see <a href="#ranking">Ranking</a>).</td></tr><tr><td><code>total</code></td><td><code>number</code></td><td>Total number of matches before pagination.</td></tr><tr><td><code>page</code></td><td><code>number</code></td><td>The 1-based page number returned.</td></tr><tr><td><code>limit</code></td><td><code>number</code></td><td>The page size used.</td></tr><tr><td><code>hasMore</code></td><td><code>boolean</code></td><td>Whether another page of results exists.</td></tr></tbody></table>

Each object in `tokens` uses the same shape as a token from the `/tokens` endpoint, plus `providers` and `marketCapUsd` :

<table><thead><tr><th width="170">Field</th><th width="110">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>identifier</code></td><td><code>string</code></td><td>The SwapKit identifier (<code>Chain.Ticker-ContractAddress</code>).</td></tr><tr><td><code>chain</code></td><td><code>string</code></td><td>The blockchain the token is on (e.g., <code>ETH</code>, <code>BTC</code>, <code>SOL</code>).</td></tr><tr><td><code>chainId</code></td><td><code>string</code></td><td>The ID of the chain. Either a numeric ID (e.g., <code>1</code> for Ethereum, <code>42161</code> for Arbitrum) or a name like <code>solana</code>.</td></tr><tr><td><code>ticker</code></td><td><code>string</code></td><td>The ticker symbol of the token (e.g., <code>USDC</code>).</td></tr><tr><td><code>symbol</code></td><td><code>string</code></td><td>The token symbol, which includes address information. <em>Optional — omitted when unknown.</em></td></tr><tr><td><code>name</code></td><td><code>string</code></td><td>The full name of the token. <em>Optional — omitted when unknown.</em></td></tr><tr><td><code>address</code></td><td><code>string</code></td><td>The contract address of the token. <em>Only provided if it is not the gas token of the network.</em></td></tr><tr><td><code>decimals</code></td><td><code>number</code></td><td>The number of decimal places the token supports.</td></tr><tr><td><code>logoURI</code></td><td><code>string</code></td><td>URL pointing to the token's logo image. <em>Optional — omitted when unknown.</em></td></tr><tr><td><code>coingeckoId</code></td><td><code>string</code></td><td>The token's CoinGecko ID. <em>Optional — omitted when unknown.</em></td></tr><tr><td><code>providers</code></td><td><code>array</code></td><td>Every provider listing this asset. Reported in full even when the <code>provider</code> filter is used — the filter restricts what can match, not what is reported.</td></tr><tr><td><code>marketCapUsd</code></td><td><code>number</code></td><td>Market cap in USD, from SwapKit's CoinGecko-backed price feed. <em>Optional</em> — only tokens with market-cap data in the feed include it; long-tail tokens won't have it, which is expected.</td></tr></tbody></table>

***

### Ranking

Results are sorted primarily by **match quality**, in tiers:

1. Exact identifier match
2. Exact contract-address match
3. Exact ticker / symbol match
4. Exact name match
5. Ticker / symbol / identifier prefix
6. Name prefix
7. Substring anywhere in the above fields

Within a tier, ties break by **market cap** (descending; assets without market-cap data sort after those with it), then by the **number of providers** listing the asset, then by **identifier** alphabetically, so ordering is fully deterministic.

***

### Errors

<table><thead><tr><th width="110">Status</th><th>When</th></tr></thead><tbody><tr><td><code>400</code></td><td>Missing/empty <code>query</code>, <code>limit</code> outside 1–100, <code>page</code> &#x3C; 1, or an invalid <code>chain</code>/<code>provider</code> value.</td></tr><tr><td><code>401</code></td><td>Missing or invalid API key.</td></tr><tr><td><code>429</code></td><td>Rate limit exceeded. The default limit is 60 requests per minute per API key (configurable per key); the response includes a <code>retryAfter</code> value in seconds.</td></tr><tr><td><code>500</code></td><td>Token lists temporarily unavailable (transient — safe to retry).</td></tr></tbody></table>

***

### Notes

* **Case-insensitive everywhere**, including contract addresses. Identifiers are returned normalized (uppercased) except on case-sensitive chains (Solana, NEAR, Sui, Tron, TON, Ripple), where original casing is preserved. Note that the address inside an EVM `identifier` is uppercased, while the standalone `address` field keeps its checksummed casing — use `address` when you need the checksummed form.
* **Multi-word queries** match tokens that contain every term — e.g. `usd coin` matches "USD Coin". When matching names, prefixes, or substrings, spaces and the separators `. _ : / -` all count as the same thing. Exact matches on an identifier, address, or ticker use the value exactly as written.
* **Deduplicated by normalized identifier** — a token listed by 5 providers appears once, with all 5 in `providers`.
* **Same data source as `/tokens`.** Results come from the same cached token lists behind `GET /tokens` — no live provider calls. The lists refresh roughly every 30 minutes, and search responses are additionally cached per query for about 5 minutes (region-local).
* **Pagination is deterministic and stable within a cache window.** Deterministic ordering means paging is stable across calls within the same \~5-minute response cache window. After the token lists refresh (\~30 min), ranking can shift, so a client paging slowly across a refresh boundary may see a token skipped or repeated.
* **Empty results.** A query with no matches (or a `page` beyond the last page) returns `"tokens": []`, `"total": 0`, `"hasMore": false`.
* **SDK.** Will be available shortly in the TypeScript SDK as `SwapKitService.searchTokens()`.


# /swapFrom - Request sell swap options

Check the swap options to sell a given token

The `/swapFrom` endpoint is called with a specified token and returns a list of tokens it can be sold for using SwapKit's API. The response includes all possible providers.\
It can be used as an alternative filter to the `/tokens`, or as a counterpart to the `/swapTo` endpoints.

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/swapFrom`

### Example Request

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/swapFrom?buyAsset=BTC.BTC' \
  -H 'accept: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE"
```

***

### Example shortened response

```json
[
  "AVAX.AVAX",
  "BASE.ETH",
  "BCH.BCH",
  "BSC.BNB",
  "DOGE.DOGE",
  "ETH.ETH",
  "GAIA.ATOM",
  "LTC.LTC",
  "THOR.RUJI",
  "THOR.TCY",
  "TRON.TRX",
  "XRP.XRP",
  "THOR.RUNE",
  "AVAX.SOL-0XFE6B19286885A4F7F55ADAD09C3CD1F906D2478F",
  "AVAX.USDC-0XB97EF9EF8734C71904D8002F8B6BC66DD9C48A6E",
  "AVAX.USDT-0X9702230A8EA53601F5CD2DC00FDBC13D4DF4A8C7",
  "BASE.USDC-0X833589FCD6EDB6E08F4C7C32D4F71B54BDA02913",
  "BSC.BTCB-0X7130D2A12B9BCBFAE4F2634D864A1EE1CE3EAD9C",
  "BSC.BUSD-0XE9E7CEA3DEDCA5984780BAFC599BD69ADD087D56",
  "BSC.ETH-0X2170ED0880AC9A755FD29B2688956BD959F933F8",
...
]

```

***

### Notes

* Include the token's identifier in the request as a parameter, as in the example request.
  * For USDC on Ethereum, it would be `https://api.swapkit.dev/swapTo?buyAsset=ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48` for example
* As mentioned before, the response includes all available tokens to swap from using any of the providers SwapKit has available. Because of this, the list for an ERC-20 token will be quite long.


# /swapTo - Request buy swap options

Check the swap options to buy a given token

The `/swapTo` endpoint is called with a specified token, and returns a list of tokens that can be bought with using SwapKit's API. The response includes all possible providers.\
It can be used as an alternative filter to the `/tokens`, or as a counterpart to the `/swapFrom` endpoints.

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/swapTo`

### Example Request

```bash
curl -X 'GET' \
  'https://api.swapkit.dev/swapTo?sellAsset=BTC.BTC' \
  -H 'accept: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE"
```

***

### Example shortened response

```json
[
  "AVAX.AVAX",
  "BASE.ETH",
  "BCH.BCH",
  "BSC.BNB",
  "DOGE.DOGE",
  "ETH.ETH",
  "GAIA.ATOM",
  "LTC.LTC",
  "THOR.RUJI",
  "THOR.TCY",
  "TRON.TRX",
  "XRP.XRP",
  "THOR.RUNE",
  "AVAX.SOL-0XFE6B19286885A4F7F55ADAD09C3CD1F906D2478F",
  "AVAX.USDC-0XB97EF9EF8734C71904D8002F8B6BC66DD9C48A6E",
  "AVAX.USDT-0X9702230A8EA53601F5CD2DC00FDBC13D4DF4A8C7",
  "BASE.USDC-0X833589FCD6EDB6E08F4C7C32D4F71B54BDA02913",
  "BSC.BTCB-0X7130D2A12B9BCBFAE4F2634D864A1EE1CE3EAD9C",
  "BSC.BUSD-0XE9E7CEA3DEDCA5984780BAFC599BD69ADD087D56",
  "BSC.ETH-0X2170ED0880AC9A755FD29B2688956BD959F933F8",
  ...
]

```

***

### Notes

* Include the token's identifier in the request as a parameter, as in the example request.
  * For USDC on Ethereum, it would be `https://api.swapkit.dev/swapTo?sellAsset=ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48` for example
* As mentioned before, the response includes all available tokens to swap to using any of the providers SwapKit has available. Because of this, the list for an ERC-20 token will be quite long.


# /v3/quote  - Request a swap quote

Obtain a quote before performing a swap.

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/quote`

The first step towards performing a swap is requesting a quote, which will compare the price offered by the different swap providers.

Quotes are cached for 5 minutes, and can be used to obtain swap transaction details.

***

### Request Schema

Here's a detailed description of the different parameters:

<table data-full-width="false"><thead><tr><th width="189.8828125">Parameter</th><th width="92">Type</th><th width="89">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>Yes</td><td>The asset being sold (e.g. <code>"ETH.ETH"</code>).</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>Yes</td><td>The asset being bought (e.g. <code>"BTC.BTC"</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>string</code></td><td>Yes</td><td>Amount in basic units (decimals separated with a dot).</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>No</td><td>Blockchain address to send the asset from. Must be a valid address for the sell asset's chain<br><br>Note - This is optional. By providing here we can screen the address against our index of bad addresses. Full screen is done in the <code>/v3/swap</code> endpoint<br><br>Refunds go here — there's no separate refundAddress. Must be an address the user controls and can receive at, <a href="/pages/XPS6PexP0R4eFuXzuIXH#transaction-log-limit-2">not an exchange deposit address</a>.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>No</td><td>Recipient blockchain address to send the asset to. Must be a valid address for the buy asset's chain<br><br>Note - This is optional. By providing here we can screen the address against our index of bad addresses. Full screen is done in the <code>/v3/swap</code> endpoint</td></tr><tr><td><code>providers</code></td><td><code>array</code></td><td>No</td><td>Limits the possible liquidity providers. If omitted, all available providers are used.</td></tr><tr><td><code>slippage</code></td><td><code>number</code></td><td>No</td><td>Max slippage in percentage (5 = 5%).</td></tr><tr><td><code>affiliateFee</code></td><td>number</td><td>No</td><td>Affiliate fee override in basis points (0-1000, max 10%). Must be a positive integer. If it is not provided, the API key configured fee tiers are applied.</td></tr><tr><td><code>cfBoost</code></td><td><code>boolean</code></td><td>No</td><td>Enables Chainflip boost for better rates.</td></tr><tr><td><code>maxExecutionTime</code></td><td><code>number</code></td><td>No</td><td>Maximum execution time in seconds. Routes exceeding this time are filtered out.</td></tr><tr><td><code>quoteType</code></td><td><code>string</code></td><td>No</td><td><code>"EXACT_INPUT"</code> (default) or <code>"FLEX_INPUT"</code>. Controls how the provider treats the input amount. See <a href="#flex-input-quotes">Flex input quotes</a> below.</td></tr><tr><td><code>usePrivacyMode</code></td><td><code>boolean</code></td><td>No</td><td>Set to <code>true</code> to request a privacy-preserving swap, where the trade details aren't exposed publicly while the swap executes. Acts as a filter. <a href="#privacy-mode">See Privacy mode</a>. Default: <code>false</code>.</td></tr><tr><td><code>enableSweep</code></td><td><code>boolean</code></td><td>No</td><td>Set to true to enable sweeping wallet funds when the transaction would otherwise leave unspendable dust in the address. Default: <code>false</code></td></tr><tr><td><code>gasCheck</code></td><td><code>boolean</code></td><td>No</td><td>Set to true to check the wallet's native gas balance and warn when it can't cover the network fee. Opt-in, and applies to <strong>token sells</strong> only. The check runs at <code>/v3/swap</code>, which inherits this value unless it sets its own — no <code>insufficientGas</code> warning appears in the <code>/v3/quote</code> response. Default: <code>false</code>.</td></tr></tbody></table>

**Note:** Asset names for `sellAsset` and `buyAsset` should follow the following nomenclature:

* Chain.Asset (e.g., `"BTC.BTC"` or `"ARB.ETH"`)
* Chain.Asset-ContractAddress (e.g., `"ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"`)

This is the `identifier` as provided in the [`/tokens`](/swapkit-api/tokens-list-and-search-supported-tokens) or [`/swapTo`](/swapkit-api/swapto-request-buy-swap-options) endpoints.

***

#### Flex input quotes

The optional `quoteType` parameter controls how the amount you send is treated by the swap provider.

<table><thead><tr><th width="238.34765625">Value</th><th>Description</th></tr></thead><tbody><tr><td><code>EXACT_INPUT</code> (default)</td><td>The exact <code>sellAmount</code> is swapped. This is the standard behaviour and applies when <code>quoteType</code> is omitted.</td></tr><tr><td><code>FLEX_INPUT</code></td><td>Swaps whatever is actually deposited instead of requiring the exact <code>sellAmount</code>. Set it when your deposit amount isn't fixed at quote time.</td></tr></tbody></table>

By default a quote assumes you'll deposit the exact input amount you asked for. Flexible input relaxes that: the amount that actually arrives may differ from the quote, and the route settles on whatever it receives.

It applies in three cases:

* **Sending a full balance.** When the wallet sweeps its whole balance (when `enableSweep` is set), the exact amount isn't known until send time, so flexible input is applied automatically.
* **Manual sends.** If the user signs and sends the transaction themselves and might deposit a slightly different amount than quoted, pass `quoteType: FLEX_INPUT` so the first leg tolerates the difference.
* **Multi-provider routes.** On any leg after the first, the input is the previous leg's output, which isn't known exactly ahead of time — so flexible input is turned on automatically for those legs.

When you pass `FLEX_INPUT`, only routes where every provider supports flexible input are returned — any route containing a provider that doesn't is dropped.

Flexible input is decided entirely at quote time. Both the behaviour and the route filtering happen at `/v3/quote`; it can't be set at `/swap`, which only executes the route you've already picked.

The providers that support FLEX\_INPUT are:&#x20;

* `THORCHAIN`
* `THORCHAIN_STREAMING`
* `MAYACHAIN`
* `MAYACHAIN_STREAMING`
* `CHAINFLIP`
* `CHAINFLIP_STREAMING`
* `NEAR`
* `FLASHNET`

#### Privacy mode

The optional `usePrivacyMode` parameter requests a privacy-preserving swap, where the trade details aren't exposed publicly while it executes. Off unless you ask for it. Today it's fulfilled through NEAR Intents' Confidential Intents, though the parameter describes the capability rather than any one provider's implementation.

It's a **filter, not a preference**: only routes where *every* provider supports a privacy mode are returned. A route whose second hop is public exposes the trade just as thoroughly as a fully public one, so partial privacy is treated as no privacy. `NEAR` is the only privacy-capable provider today, so requesting privacy narrows you to the pairs NEAR Intents covers.

{% hint style="warning" %}
Because it filters rather than falls back, `usePrivacyMode: true` can leave you with nothing on a pair that quotes fine without it — either `404 noRoutesFound`, or `200` with an empty `routes` array and a populated `providerErrors`. Decide up front whether you retry as a public quote or tell the user the pair isn't available privately; silently returning a public route is the wrong answer, since the user wouldn't know they hadn't got privacy.
{% endhint %}

Privacy mode is decided entirely at quote time. You can't set it at `/v3/swap`, which inherits the mode from the quote it executes. The flag isn't echoed in the response, so track it against `routeId` on your side.

Tracking is unaffected: `/track` resolves these swaps normally, including by `depositAddress`, because it reads the provider's status API rather than the public explorer.

#### Example request

A simple request to trade `ETH.ETH` to `BTC.BTC` may omit the `providers` array if you can manage them in the response, but should include the amount and slippage settings.

A quote expires after 5 minutes. Once it does, you can request a new one, or one will be requested automatically if a swap is initiated using this `routeId` .

{% hint style="info" %}
Using the header `x-api-key` in your `/quote` request automatically applies your affiliate addresses and fee values set up through our [partners dashboard](/monetization#step-3-set-affiliate-fee-tiers) in the response parameters.
{% endhint %}

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST "https://api.swapkit.dev/v3/quote" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "sellAsset": "ETH.USDC-0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    "buyAsset": "BTC.BTC",
    "sellAmount": "300",
    "slippage": 2
  }'
```

{% endtab %}
{% endtabs %}

***

### Quote Response Schema

| Field            | Type                          | Description                                                                                                          |
| ---------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `quoteId`        | `string`                      | UUID for this quote response                                                                                         |
| `createdAt`      | `string`                      | ISO 8601 timestamp of when this quote response was created. Top level — a sibling of quoteId, not a per-route field. |
| `routes`         | `QuoteRoute[]`                | An array of routes with an individual `routeId` to request a swap with. `QuoteRoute` schema is explained below       |
| `providerErrors` | `QuoteError[]` or `undefined` | Optional. If errors encountered from providers, details are returned here. `QuoteError` schema is explained below    |
| `error`          | `string` or `undefined`       | In case of a bad request, root level `error` is provided.                                                            |

#### Quote Route Schema

Each route identifies a provider or a group of providers for the swap. Each item in the routes array contains the information needed to compare between them:

<table><thead><tr><th width="265">Field</th><th width="84">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>routeId</code></td><td><code>string</code></td><td>UUID of this specific swap response.</td></tr><tr><td><code>providers</code></td><td><code>array</code></td><td>List of providers available for this route (<code>CHAINFLIP</code>, <code>THORHCAIN</code> etc.).</td></tr><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>The asset being sold (e.g., <code>“ETH.ETH”</code>).</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>The asset being bought (e.g., <code>“BTC.BTC”</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>string</code></td><td>Amount of the sell asset in smallest units.</td></tr><tr><td><code>expectedBuyAmount</code></td><td><code>string</code></td><td>Estimated amount of the buy asset to be received.</td></tr><tr><td><code>expectedBuyAmountMaxSlippage</code></td><td><code>string</code></td><td>Worst-case buy amount considering max slippage.</td></tr><tr><td><code>fees</code></td><td><code>array</code></td><td>List of fees applied to the swap (inbound, network, affiliate, service, outbound, liquidity).</td></tr><tr><td><code>estimatedTime</code></td><td><code>object</code></td><td>Estimated time for different phases of the swap.</td></tr><tr><td><code>totalSlippageBps</code></td><td><code>number</code></td><td>Expected total slippage.</td></tr><tr><td><code>legs</code></td><td><code>array</code></td><td>The different steps invovled in the swap.</td></tr><tr><td><code>warnings</code></td><td><code>array</code></td><td>Potential warnings about this swap provider.</td></tr><tr><td><code>meta</code></td><td><code>object</code></td><td>Other information about the transaction and the assets involved.</td></tr><tr><td><code>meta.tags</code></td><td><code>array</code></td><td><code>"FASTEST"</code>, <code>"RECOMMENDED"</code> or <code>"CHEAPEST"</code> tag help sort the available routes.</td></tr><tr><td><code>nextActions</code></td><td><code>object</code></td><td>Data needed for the next request in the flow. <code>{ method: string; url: string; payload: object }</code></td></tr></tbody></table>

***

### Quote Error Schema

| Field       | Type     | Description                                   |
| ----------- | -------- | --------------------------------------------- |
| `provider`  | `string` | Specific ProviderName value                   |
| `errorCode` | `string` | One of the possible error codes listed below. |
| `message`   | `string` | Message relating to thrown error.             |

#### Quote Error Codes and messages

| Error                             | Message                                                     | Status Code | Scenario                                                              |
| --------------------------------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------- |
| `noRoutesFound`                   | No routes found for swap from {sellAsset} to {buyAsset}     | 404         | No valid swap path exists between the requested token pair.           |
| `blackListAsset`                  | Asset {asset} is blacklisted                                | 400         | The sell or buy asset is identified as a scam token in our blacklist. |
| `apiKeyInvalid` or `unauthorized` | "Invalid API key" / "Unauthorized”                          | 401         | Missing, expired, or invalid API key in x-api-key header.             |
| `invalidRequest`                  | "Request body is required and must be a valid JSON object”. | 400         | Request body is missing, null, or malformed JSON.                     |

The `invalidRequest` error may include additional details depending on the missing parameters. Make sure to check the JSON formatting in the request.

***

### Fees breakdown

Fees are categorized into different types based on their role in the swap process.

<table><thead><tr><th width="297">Fee Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>Inbound</strong></td><td>Cost of getting the sell asset to the provider: estimated gas for the deposit, plus any provider ingress fee. Two entries when they're in different assets.</td></tr><tr><td><strong>Network</strong></td><td>Blockchain transaction fee for processing the swap.</td></tr><tr><td><strong>Affiliate</strong></td><td>Fee paid to the specified affiliate.</td></tr><tr><td><strong>Service</strong></td><td>SwapKit's service fee.</td></tr><tr><td><strong>Outbound</strong></td><td>Fee for transferring the buy asset to the destination address.</td></tr><tr><td><strong>Liquidity</strong></td><td>Fee applied by the liquidity provider to facilitate the swap.</td></tr></tbody></table>

Fees other than `inbound` are already reflected in `expectedBuyAmount`, whether taken from the input or the output — don't subtract them again. `inbound` is the only fee your wallet funds on top of `sellAmount`, and it isn't reflected in `expectedBuyAmount`.

**Chainflip is the exception.** Its `inbound` combines your gas with Chainflip's ingress fee, and only the gas is paid on top of `sellAmount` — the ingress is already reflected in `expectedBuyAmount`. Token deposits return the two separately (ingress in the deposited asset, gas in the chain's gas asset); native deposits sum them into one entry, so budget the full amount as an upper bound.

***

### Estimated time

The estimated time in seconds for the swap is divided into the following phases:

* **Inbound:** Time taken to receive the sell asset.
* **Swap:** Time taken for the swap process.
* **Outbound:** Time taken to transfer the bought asset to the destination. This includes the provider outbound time, not only the transaction time.
* **Total:** The sum of all time estimates.

#### **Example estimated time:**

```json
"estimatedTime": {
    "inbound": 30,
    "swap": 6,
    "outbound": 624,
    "total": 660
}
```

***

### Route metadata

The `meta` section provides additional information about the swap.

| **assets**          | Details of the involved assets, including price and image links.                            |
| ------------------- | ------------------------------------------------------------------------------------------- |
| **tags**            | \["FASTEST", "RECOMMENDED", "CHEAPEST"]                                                     |
| **approvalAddress** | Token-approval spender address. Present only for EVM ERC-20 sells that require an approval. |

`/v3/swap` returns a larger `meta` than this. Fields that only exist once a transaction is built — `allowance`, and the [deposit-channel fields](/swapkit-api/v3-swap-obtain-swap-transaction-details#deposit-channel-fields-in-meta) `providerDepositChannelId` / `depositChannelExpiration` — are documented on that page. `/v3/quote` never opens a deposit channel, so they never appear here.

#### **Example `meta` Object:**

```json
"meta": {
    "assets": [{
        "asset": "ETH.ETH",
        "price": 2752.14,
        "image": "https://storage.googleapis.com/token-list-swapkit/images/eth.eth.png"
    }, {
        "asset": "BTC.BTC",
        "price": 97648,
        "image": "https://storage.googleapis.com/token-list-swapkit/images/btc.btc.png"
    }],
    "tags": ["FASTEST"],
    "approvalAddress": "0x1111111254eeb25477b68fb85ed929f73a960582"
}
```

The `"tags"` help identify what SwapKit considers the best routes by filtering through the `["FASTEST", "RECOMMENDED", "CHEAPEST"]` labels.

<table><thead><tr><th width="283">Tag</th><th>Description</th></tr></thead><tbody><tr><td><strong>RECOMMENDED</strong></td><td>Best overall route based on output and speed.</td></tr><tr><td><strong>CHEAPEST</strong></td><td>The route with the maximum output.</td></tr><tr><td><strong>FASTEST</strong></td><td>The route with the shortest total estimated time.</td></tr></tbody></table>

To determine the `RECOMMENDED` route when multiple options are available, each route gets two normalized scores:

* **Output score (0-100)**: Compare the route's output to the maximum output of the available routes, multiplied by 100 for weight.\
  `(routeOutput / maxOutput) * 100`&#x20;
* **Time score (0-50)**: Compare the route's expected confirmation time to the minimum confirmation time of the available routes, subtracting from 50 to give it a dynamic weight. The fastest route will obtain 50 points here.\
  `50 - abs(routeTime - minTime) / (maxTime - minTime)`

Output and time are weighted dynamically, depending on the difference between the fastest route and the one being analyzed.

<table><thead><tr><th width="283">Time spread (Current vs Fastest)</th><th>Output weight</th><th>Time weight</th></tr></thead><tbody><tr><td>≤ 60 seconds</td><td>100 %</td><td>0 %</td></tr><tr><td><strong>60s - 5 min</strong></td><td>95 %</td><td> 5 %</td></tr><tr><td><strong>5 - 15 min</strong></td><td>90 %</td><td>10 %</td></tr><tr><td><strong>> 15 min</strong></td><td>80 %</td><td>20 %</td></tr></tbody></table>

The weights are then multiplied to the score, and the route with the highest total is tagged as `RECOMMENDED` . It is simply `outputScore * outputWeight + timeScore * timeWeight` .

***

### Next Actions

The `nextActions` object includes information about the request to make next. Generally it will point to the `/swap` endpoint which will return a transaction object, but for ERC-20 tokens the `/swap` endpoint may first return an `approvalTx` for spending approval, requiring a second `/swap` call to get the transaction.

For example, it could look like the following, which includes the `method`, `url` and `payload` to use. The `"soureAddress"` and `"destinationAddress"` need to be filled in:

```json
"nextActions": [
    {
        "method": "POST",
        "url": "/swap",
        "payload": {
            "routeId": "14ddcf49-4adb-4817-a55e-1bfb1296283d"
        }
    }
]
```


# /v3/swap - Obtain swap transaction details

Use an existing routeId to obtain a swap's transaction details.

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/swap`

After the user has accepted a quote, perform a request to the `/swap` endpoint to obtain transaction details. Use a quote's `routeId` to identify it (it is cached for 5 minutes), providing additional details about the involved addresses.

### Request Schema

<table><thead><tr><th width="244">Parameter</th><th width="92">Type</th><th width="96">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>routeId</code></td><td><code>string</code></td><td>Yes</td><td>The ID of the route to swap. Obtained from a previous <code>/v3/quote</code> response</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>Yes</td><td>Blockchain address to send the asset from. Must be a valid address for the sell asset's chain<br><br>Refunds go here — there's no separate refundAddress. Must be an address the user controls and can receive at, <a href="/pages/XPS6PexP0R4eFuXzuIXH#transaction-log-limit-2">not an exchange deposit address</a>.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>Yes</td><td>Recipient blockchain address to send the asset to. Must be a valid address for the buy asset's chain</td></tr><tr><td><code>disableBuildTx</code></td><td><code>boolean</code></td><td>No</td><td>Whether to skip providing a built transaction, ready to sign by the wallet.</td></tr><tr><td><code>disableBalanceCheck</code></td><td><code>boolean</code></td><td>No</td><td>Whether to skip balance validation. If <code>false</code> or omitted, checks that source address has sufficient balance. Do not use without consulting SwapKit devs first. Default: <code>false</code></td></tr><tr><td><code>disableEstimate</code></td><td><code>boolean</code></td><td>No</td><td>Whether to skip on-chain gas estimation when building the transaction. Default: <code>false</code></td></tr><tr><td><code>allowSmartContractSender</code></td><td><code>boolean</code></td><td>No</td><td>Whether to allow the source address to be a smart contract, <a href="/pages/XPS6PexP0R4eFuXzuIXH#solana-multisig-vaults-and-pdas-off-curve-addresses">or an off-curve Solana address</a>. Do not use without consulting SwapKit devs first. Default: <code>false</code></td></tr><tr><td><code>allowSmartContractReceiver</code></td><td><code>boolean</code></td><td>No</td><td>Whether to allow the destination address to be a smart contract, <a href="/pages/XPS6PexP0R4eFuXzuIXH#solana-multisig-vaults-and-pdas-off-curve-addresses">or an off-curve Solana address</a>. Do not use without consulting SwapKit devs first. Default: <code>false</code></td></tr><tr><td><code>disableSecurityChecks</code></td><td><code>boolean</code></td><td>No</td><td>Whether to bypass address format validation and security checks. Default: <code>false</code></td></tr><tr><td><code>overrideSlippage</code></td><td><code>boolean</code></td><td>No</td><td>Whether to bypass the 5% output amount deviation check when the quote is refreshed. Default: <code>false</code></td></tr><tr><td><code>enableSweep</code></td><td><code>boolean</code></td><td>No</td><td>Whether to enable max-spend sweep behaviour for UTXO assets (BTC, LTC, DOGE, BCH). Set to <code>true</code> to send the full spendable balance instead of exactly <code>sellAmount</code> when the swap would otherwise leave unspendable dust. Opt-in; if omitted, inherits <code>enableSweep</code> from the <code>/v3/quote</code> call that created the <code>routeId</code>. Default: <code>false</code>.</td></tr><tr><td><code>gasCheck</code></td><td><code>boolean</code></td><td>No</td><td>Opt-in native-gas balance check for <strong>token sells</strong> (gas-asset sells are covered by <code>insufficientBalance</code> instead). When the wallet can't cover the network fee, the route carries an <code>insufficientGas</code> warning and the transaction is still returned. Inherits from <code>/v3/quote</code> when omitted. Default: <code>false</code>.</td></tr></tbody></table>

{% hint style="warning" %}
When you test your integration, use an address with enough `sellAsset` balance to cover the swap. This will ensure you receive complete responses with a valid transaction object.
{% endhint %}

For example, the request could be as simple as this (this `routeId` will not work for you, as it has now expired):

{% tabs %}
{% tab title="cURL" %}

```sh
curl -X 'POST' \
  'https://api.swapkit.dev/v3/swap' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "routeId": "85bb4fa2-3a65-4d22-b20b-e43d028d83e6",
    "sourceAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P"
}'
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
A fresh quote is fetched on every `/swap` call to refresh price and slippage. Route IDs are cached for 5 minutes\
If the `routeId` is older than 5 minutes, it will not be cached anymore and won't be recognized by the swap endpoint.
{% endhint %}

#### Disabling transaction building

* `disableBuildTx` is the most effective filter. Data on where to send the tokens to swap will be provided, but the transaction building won't be performed. This avoids all balance checks and errors derived from it.
* `disableBalanceChecks` still tries to build a transaction without checking the balance of the address. This works on some chains, like EVM, but will fail on UTXO chains because building a transaction requires checking the balance (the flag is not applied).

Using them together ensures no errors from wallet balance or other transaction building errors are returned. This is only recommended for advanced integrations, where you are sure you can build the transaction yourselves.

### Swap Response Schema

<table><thead><tr><th width="271">Field</th><th width="85">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>swapId</code></td><td><code>string</code></td><td>UUID of this specific swap response.</td></tr><tr><td><code>createdAt</code></td><td><code>string</code></td><td>ISO 8601 timestamp of when this swap response was built.</td></tr><tr><td><code>quoteCreatedAt</code></td><td><code>string</code></td><td>ISO 8601 timestamp of the underlying quote. Note the unit change from <code>expiration</code>, which is unix seconds as a string.</td></tr><tr><td><code>providers</code></td><td><code>array</code></td><td>List of providers available for this route (<code>CHAINFLIP</code>, <code>THORHCAIN</code>etc.).</td></tr><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>The asset being sold (e.g., <code>“ETH.ETH”</code>).</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>The asset being bought (e.g., <code>“BTC.BTC”</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>string</code></td><td>Amount of the sell asset in smallest units.</td></tr><tr><td><code>expectedBuyAmount</code></td><td><code>string</code></td><td>Estimated amount of the buy asset to be received.</td></tr><tr><td><code>expectedBuyAmountMaxSlippage</code></td><td><code>string</code></td><td>Worst-case buy amount considering max slippage.</td></tr><tr><td><code>tx</code></td><td>varies</td><td>Explained below</td></tr><tr><td><code>approvalTx</code></td><td><code>object</code><br>(optional)</td><td>For EVM sell chain. When a token approval transaction is required to be submitted before the <code>tx</code> can be broadcasted. See <a href="#the-approvaltx-object">below</a> for more details.</td></tr><tr><td><code>targetAddress</code></td><td><code>string</code><br>(optional)</td><td>The address the swap transaction targets — a DEX aggregator/router contract for EVM swaps, or the inbound vault address for non-EVM cross-chain swaps. Optional: it is omitted for deposit-channel routes (e.g. Chainflip), where the send destination comes from the deposit address in <code>meta</code> or is resolved during <code>tx</code> building instead.</td></tr><tr><td><code>memo</code></td><td><code>string</code></td><td>Optional. Memo with instructions for swap.</td></tr><tr><td><code>fees</code></td><td><code>array</code></td><td>List of fees applied to the swap (inbound, network, affiliate, service).</td></tr><tr><td><code>estimatedTime</code></td><td><code>object</code></td><td>Estimated time for different phases of the swap.</td></tr><tr><td><code>totalSlippageBps</code></td><td><code>number</code></td><td>Expected total slippage.</td></tr><tr><td><code>legs</code></td><td><code>array</code></td><td>The different involved steps in the swap.</td></tr><tr><td><code>warnings</code></td><td><code>array</code></td><td>Potential warnings about this swap provider. Warnings never block the response — <code>tx</code> is still returned.</td></tr><tr><td><code>meta</code></td><td><code>object</code></td><td>Other information about the transaction. A superset of the <code>/v3/quote</code> route metadata — it adds the swap-only <a href="#the-meta.allowance-field"><code>allowance</code></a> and, on routes that open a deposit channel, <a href="#deposit-channel-fields-in-meta">the deposit-channel fields</a>.</td></tr><tr><td><code>routeId</code></td><td><code>string</code></td><td>UUID of the specific route this swap was built from — the same <code>routeId</code> returned by <code>/v3/quote</code> and passed back in the <code>/v3/swap</code> request.</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>Blockchain address to send the asset from.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>Recipient blockchain address to send the asset to.</td></tr><tr><td><code>txHint</code></td><td><code>string</code><br>(enum)</td><td>Hint for how to broadcast the returned <code>tx</code>. One of <code>simpleTransfer</code> (plain value transfer), <code>transferWithMemo</code> (transfer that must include the <code>memo</code>), or <code>contractCall</code> (submit as a smart-contract / EVM transaction).</td></tr><tr><td><code>txType</code></td><td><code>string</code><br>(enum, optional)</td><td>The format of the returned <code>tx</code>. <a href="/pages/M3AWLoRoaDgNepIh7aXK">See Transaction Formats by txType</a>. Also mirrored at <code>meta.txType</code>.</td></tr><tr><td><code>inboundAddress</code></td><td><code>string</code><br>(optional)</td><td>For deposit/memo-based providers, the protocol's inbound vault address that funds are sent to. Present only when the route settles via an inbound deposit rather than a direct contract call.</td></tr><tr><td><code>shieldedMemo</code></td><td><code>object</code><br>(optional)</td><td>Shielded-Zcash source only. <code>{ unifiedAddress: string, uivk?: string }</code>. Send a 0-value shielded note carrying the <code>memo</code> to <code>unifiedAddress</code> in the <strong>same transaction</strong> as the value output to <code>inboundAddress</code>. <code>uivk</code> is MAYAChain's unified incoming viewing key for decrypting the note. See <a href="/pages/NcESduEO43IIMl89J7UI">Zcash shielded &#x26; unified addresses</a> for more details.</td></tr><tr><td><code>expiration</code></td><td><code>string</code><br>(optional)</td><td><strong>Quote deadline only — not the deposit window.</strong> Unix <strong>seconds</strong>, as a string (not ISO 8601). For most providers this is a flat <code>quoteCreatedAt + 3600s</code> rather than a provider-supplied value; only FLASHNET and MAYAN return their own deadline, and CHAINFLIP returns none at all. If you need to know how long a deposit channel accepts funds, use <code>meta.depositChannelExpiration</code> — <a href="#deposit-channel-fields-in-meta">see below</a>.</td></tr></tbody></table>

#### The `approvalTx` object

<table><thead><tr><th width="227.8984375">Field</th><th width="128.6484375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>approvalTx.to</code></td><td><code>string</code></td><td>Token contract address to send the approval</td></tr><tr><td><code>approvalTx.from</code></td><td><code>string</code></td><td>User wallet address.</td></tr><tr><td><code>approvalTx.value</code></td><td><code>string</code></td><td>ETH value — always <code>"0"</code> for approvals.</td></tr><tr><td><code>approvalTx.data</code></td><td><code>string</code></td><td>Encoded <code>approve()</code> call data.</td></tr><tr><td><code>approvalTx.gasLimit</code></td><td><code>string</code></td><td>Estimated max gas units for the approval transaction, as a hex quantity string</td></tr><tr><td><code>approvalTx.gasPrice</code></td><td><code>string</code></td><td>Gas price in wei per gas unit, as a hex quantity string. Legacy (type-0) gas pricing.</td></tr></tbody></table>

#### The `meta.allowance` field

| Field            | Type                | Description                                                                                    |
| ---------------- | ------------------- | ---------------------------------------------------------------------------------------------- |
| `meta.allowance` | `string` (optional) | The sell token's current on-chain allowance held by `meta.approvalAddress`, in **base units**. |

* **`"0"` is a normal value** — it means no allowance has been granted yet, not that the field is missing.
* **It is not the approval signal.** It's returned whether or not an approval is needed; the presence of `approvalTx` remains the only "approval required" indicator. Don't derive one from the other — when the allowance check fails, `allowance` is simply absent while `approvalTx` may still be present.
* **Omitted** when an ERC-20 allowance isn't a meaningful concept for the route: native gas-asset sells, non-EVM sell chains, and deposit-channel routes (e.g. Chainflip) that move funds with a plain `transfer` rather than a `transferFrom` pull.

#### Deposit-channel fields in `meta`

When a route settles by opening a deposit channel, `/v3/swap` returns the channel's id and its deadline. These are **`/v3/swap` only** — `/v3/quote` never opens a channel, so they never appear in a quote response.

<table><thead><tr><th width="290">Field</th><th width="120">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>meta.providerDepositChannelId</code></td><td><code>string</code><br>(optional)</td><td>The provider's own id for the deposit channel, returned as soon as the channel is opened. Pass it to <a href="/pages/MPFP4ml2c7aOy7SPTKbh"><code>/track</code></a> as <code>depositChannelId</code> to follow the swap without a transaction hash.</td></tr><tr><td><code>meta.depositChannelExpiration</code></td><td><code>number</code><br>(optional)</td><td>Unix <strong>seconds</strong> after which the channel stops accepting a deposit. <strong>This is the deadline where user funds are at stake</strong> — drive your deposit countdown from this, never from <code>expiration</code>.</td></tr><tr><td><code>meta.depositChannelExpirationBlock</code></td><td><code>number</code><br>(optional)</td><td>Chainflip only. The raw source-chain block the channel expires at (<code>sourceChainExpiryBlock</code>). Informational — <code>depositChannelExpiration</code> is not derived from it.</td></tr></tbody></table>

{% hint style="warning" %}
`expiration` and `meta.depositChannelExpiration` are different deadlines with different units. `expiration` is the quote deadline — a string of unix seconds, usually just one hour out. `meta.depositChannelExpiration` is the channel deadline — a number, and on Chainflip roughly 24 hours out. Using `expiration` for a deposit countdown will show a window \~23h shorter than the real one.
{% endhint %}

**Which providers return them**

<table><thead><tr><th width="270">Route</th><th width="150">Channel id</th><th width="200">Expiration</th><th>Expiration block</th></tr></thead><tbody><tr><td><code>CHAINFLIP</code> / <code>CHAINFLIP_STREAMING</code>, deposit-channel</td><td>yes</td><td>channel open + a flat 24h</td><td>yes</td></tr><tr><td><code>CHAINFLIP</code> vault swaps</td><td>—</td><td>—</td><td>—</td></tr><tr><td><code>NEAR</code></td><td>yes</td><td>provider's quote deadline</td><td>—</td></tr><tr><td><code>FLASHNET</code></td><td>yes</td><td>provider's <code>expiresAt</code> — the same value as top-level <code>expiration</code></td><td>—</td></tr><tr><td><code>GARDEN</code></td><td>yes</td><td>may be omitted</td><td>—</td></tr><tr><td>all other providers</td><td>—</td><td>—</td><td>—</td></tr></tbody></table>

Chainflip channels close after a flat 24 hours regardless of source chain, so that the network can rotate its vaults and authority sets. The deadline is computed from that fixed lifetime, not from source-chain block times.

On a multi-provider route these fields describe the channel you actually deposit into — the first leg's. When Chainflip is a later leg there is no channel for the user to deposit to, and the fields are absent.

#### The `tx` field

We provide the ready to be signed transaction in the `tx` field. It can be an object or a string, depending on the source chain of the swap.&#x20;

* VM sell chain - Ethers V6 Transaction <https://docs.ethers.org/v6/api/transaction/#Transaction>
* UTXO sell chain (BTC, BCH, LTC, DOGE) - base64 encoded PSBT ZCash sell chain - By default a base64 encoded PSBT using <https://github.com/BitGo/BitGoJS/blob/master/modules/utxo-lib/src/bitgo/UtxoPsbt.ts#L161> library is returned.We also support returning of an unsigned PCZT using <https://github.com/zcash/librustzcash> - Please reach out for more information.
* TRON sell chain - A TransactionBuilder object from the TronWeb lib <https://tronweb.network/docu/docs/API%20List/transactionBuilder/sendAsset#example>
* Cosmos sell chains (including THOR & MAYA) - A native Cosmos transaction object <https://tutorials.cosmos.network/academy/2-cosmos-concepts/3-transactions.html#generating-transactions>

As an example, for an EVM sell chain it might look like this. In this case, it's a simple transfer without any contract involved:

```json
"tx": {
        "to": "0x4e6960159d85254cb8e88483793f6fa8e9a49a4c",
        "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "gas": "0x5208",
        "gasPrice": "0x9129260",
        "value": "3000000000000000000",
        "data": "0x"
    },
```

The transaction can also use [SLIP-0024 payload for verification](/spotlights/slip-0024-transaction-payload-signing). Reach out to use if you are interested.

***

### **Swap Errors**

<table><thead><tr><th width="179">Field</th><th width="154">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>provider</code></td><td><code>string</code></td><td>Specific provider name</td></tr><tr><td><code>errorCode</code></td><td><code>string</code></td><td>One of the possible error codes listed below.</td></tr><tr><td><code>message</code></td><td><code>string</code></td><td>Message related to the error.</td></tr></tbody></table>

#### **Error codes and messages**

<table><thead><tr><th>Error</th><th>Message</th><th width="111">Status Code</th><th>Scenario</th></tr></thead><tbody><tr><td><code>swapRouteNotFound</code></td><td>Route not found for routeId: {routeId}</td><td>404</td><td>The routeId doesn't exist in cache (expired after 5 minutes or never existed). Client must request a new quote first.</td></tr><tr><td><code>isSanctionedAddress</code></td><td>Address {address} is sanctioned or risky</td><td>400</td><td>Source or destination address flagged by screening service (Chainalysis/Elliptic). Addresses are screened against sanctions lists and risk databases</td></tr><tr><td><code>apiKeyInvalid</code> / <code>unauthorized</code></td><td>"Invalid API key" / "Unauthorized”</td><td>401</td><td>Missing, expired, or invalid API key in x-api-key header.</td></tr><tr><td><code>insufficientBalance</code></td><td>Insufficient balance for {chain} amount {amount} at address {address}</td><td>400</td><td>When <code>disableBalanceCheck</code> is <code>false</code>, source address doesn't have enough tokens to complete the swap. Balance fetched from blockchain and compared to sellAmount.</td></tr><tr><td><code>insufficientGas</code></td><td>Cannot build transaction. Insufficient gas balance on chain {chain}</td><td>400</td><td>EVM token sell where the node reports insufficient funds while estimating gas. Distinct from the non-blocking <code>insufficientGas</code> warning in <code>warnings[]</code>, which does not fail the request.</td></tr><tr><td><code>invalidSourceAddress</code></td><td>Invalid source address: {address}</td><td>400</td><td>Source address validation fails (invalid format, smart contract when not allowed, or fails security checks). Also returned for an off-curve Solana source unless <code>allowSmartContractSender: true</code>.</td></tr><tr><td><code>invalidDestinationAddress</code></td><td>Invalid destination address: {address}</td><td>400</td><td>Destination address validation fails (invalid format, smart contract when not allowed, or fails security checks). Also returned for off-curve Solana destinations unless <code>allowSmartContractReceiver: true</code></td></tr><tr><td><code>outputAmountDeviationTooHigh</code></td><td>Output amount deviation too high. Original: {originalAmount}, Refreshed: {refreshedAmount}, Deviation: {percentageChange}%</td><td>400</td><td>Quote was refreshed and the new output amount deviates more than 5% from the cached quote. Protects users from price slippage. Can be bypassed with <code>overrideSlippage: true</code></td></tr><tr><td><code>noRoutesFound</code></td><td>No routes found for swap from {sellAsset} to {buyAsset}</td><td>404</td><td>When quote is refreshed, no valid routes are returned from providers. Rare - indicates liquidity dried up or all providers failed.</td></tr><tr><td><code>memoTooLongForSourceChain</code></td><td>Destination address is too long for the source chain's memo limit</td><td>400</td><td>Generated memo exceeds the source chain's memo capacity (e.g. a long unified refund address on a transparent source).</td></tr><tr><td><code>zcashMemoTooLong</code></td><td>Zcash memo exceeds the byte budget</td><td>400</td><td>Final Zcash memo exceeds its byte budget (80B transparent source / 512B shielded source).</td></tr><tr><td><code>zcashShieldedMemoUnavailable</code></td><td>Maya shielded memo support is unavailable</td><td>503</td><td>MAYAChain's shielded-memo config is disabled or absent when a shielded Zcash deposit is requested.</td></tr><tr><td><code>zcashShieldedRefundMissing</code></td><td>Refund address missing from memo for shielded Zcash deposit</td><td>500</td><td>Safety guard: shielded-source memo produced without the refund address embedded; swap aborted to prevent unrecoverable funds.</td></tr><tr><td><code>invalidAddressForChain</code></td><td>Invalid address for chain</td><td>400</td><td>The derived refund address fails Zcash validation.</td></tr></tbody></table>

***

### Additional Notes

* **Quote price refresh:** The quote response is cached for 5 minutes. Each `/swap` call fetches a fresh quote from the blockchain to refresh price and slippage; after 5 minutes the `routeId` expires and `/swap` returns a `swapRouteNotFound` error. To get the swap payload, always call `/swap` with the `nextActions` from the quote response.
* **Balance check:** By default we check if the `sourceAddress` has a sufficient balance. Since OKW is already the wallet, you can use `disableBalanceCheck: true` in the body of the `/swap` request.
* **Latency:** The `/swap` endpoint is more costly and slower. It builds the transaction payload, including fetching UTXOs and building PSBT when required, performs a balance check, and automatically runs **address screening on every `/swap` call**. It also opens a deposit channel when NEAR or Chainflip providers are used, which can take up to 2.5s. Setting `gasCheck: true` adds a native-balance lookup, and on TRON an extra fee estimate for low-balance wallets.

  Calling this endpoint should only be done after the user has selected a quote as their swap route.
* The `/swap` response also carries over some of the same fields returned by `/quote`, such as `estimatedTime`, `totalSlippageBps`, and the `fees` breakdown.

Some of these can be skipped by using the optional parameters listed in the [request schema](#request-schema).

**Optional `/swap` params:**

1. `disableBalanceCheck` : skip checking the `sourceAddress` balance of the `fromAsset` . Recommended for wallets that do this check internally.
2. `disableEstimate` : skip gas estimation for EVM chains.
3. `overrideSlippage` : receive a `/swap` transaction payload even if the output amount doesn’t fit within the slippage parameter provided in the referenced `/quote` through the `routeId` (relevant if a new quote has to be fetched).
4. `gasCheck` : opt in to the native-gas balance check on token sells, adding an `insufficientGas` warning when the wallet can't cover the network fee. Off by default; costs an extra balance lookup.


# /track - Request the status of a swap

The `/track` endpoint provides real-time status information for a specific transaction. It is particularly useful for tracking the progress and details of swaps, transfers, and other operations. To use this endpoint, you normally provide the **chain ID** and **transaction hash**.

For NEAR Intents swaps, you can also call the endpoint with **depositAddress**, which is the address the deposit transaction was sent to.

For deposit-channel routes you can also call it with **depositChannelId** — the `meta.providerDepositChannelId` returned by [`/v3/swap`](/swapkit-api/v3-swap-obtain-swap-transaction-details). This is the way to track a Chainflip swap whose deposit was broadcast outside your app, where you never see a transaction hash.\
\
For a complete list of chain IDs used by SwapKit you can [check the table here](/swapkit-api/providers-providers-status-and-identifiers-mapping#chain-ids-and-corresponding-names).

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/track`&#x20;

SwapKit has it's own transaction tracking interface here: <https://track.swapkit.dev/>. You can also directly fill the fields by directing users to `https://track.swapkit.dev/?hash={{hash}}` by populating it with the respective transaction hash.

#### Request Body:

<table><thead><tr><th>Field</th><th width="172">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>hash</code></td><td><code>string</code></td><td>Transaction hash (required if using <code>chainId</code>)</td></tr><tr><td><code>chainId</code></td><td><code>string</code></td><td>Chain ID of the transaction (required if using <code>hash</code>)</td></tr><tr><td><code>depositChannelId</code></td><td><code>string</code></td><td>Deposit channel ID, can be used instead of <code>hash</code> and <code>chainId</code>. Use the <code>meta.providerDepositChannelId</code> returned by <code>/v3/swap</code>. Needed for Chainflip when the deposit was broadcast without a wallet connection, so no hash is available.</td></tr><tr><td><code>depositAddress</code></td><td><code>string</code></td><td>Deposit address used to swap with NEAR Intents, can be used instead of <code>hash</code> and <code>chainId</code></td></tr><tr><td><code>block</code></td><td><code>number</code></td><td>Block number. Required for Polkadot.</td></tr><tr><td><code>routeId</code></td><td><code>string</code></td><td>The <code>routeId</code> of the <code>/v3/quote</code> route this transaction executed. Pass it on <code>/track</code> calls until the response contains a parsed transaction, to enable <a href="#realized-slippage">realized-slippage</a> reporting on completion. Best-effort: quote data expires. Send it on the first call, right after broadcast.</td></tr></tbody></table>

Provide exactly one of: hash + chainId, depositChannelId, or depositAddress. `routeId` is not one of those identifiers — it is additive, and is passed alongside whichever one you use.

#### Example Requests:

<details>

<summary><strong><code>hash</code> and <code>chainId</code></strong></summary>

```bash
curl -X 'POST' \
  'https://api.swapkit.dev/track' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_API_KEY_HERE" \
  -d '{
  "hash": "0x1890aba1c0b25126892af2ab09f5c1bba75adefc47918a96ea498764ab643ce9",
  "chainId": "1",
  "routeId": "eed91159-86bd-4674-9558-48f7e4f8bac0"
}'
```

</details>

<details>

<summary><strong><code>depositAddress</code></strong></summary>

```bash
curl -X 'POST' \
  'https://api.swapkit.dev/track' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
  "depositAddress": "0x6f2B69c522031A7640b432172Ac84e57Dc0a3A63"
}'
```

</details>

<details>

<summary><strong><code>depositChannelId</code></strong></summary>

```bash
curl -X 'POST' \
  'https://api.swapkit.dev/track' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_API_KEY_HERE" \
  -d '{
  "depositChannelId": "1234567-Bitcoin-89"
}'
```

</details>

***

### Response

The response contains detailed information about the transaction status, type, and associated metadata. It also includes the array `"legs"` which represent the different stages or components of the transaction.

#### Response Fields:

<table><thead><tr><th width="219">Field</th><th width="119">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>chainId</code></td><td><code>string</code></td><td>The chain ID where the transaction occurred.</td></tr><tr><td><code>hash</code></td><td><code>string</code></td><td>The transaction hash.</td></tr><tr><td><code>block</code></td><td><code>number</code></td><td>The block number where the transaction was included.</td></tr><tr><td><code>type</code></td><td><code>string</code></td><td>The type of the transaction (e.g., <code>swap</code>, <code>token_transfer</code>).</td></tr><tr><td><code>status</code></td><td><code>string</code></td><td>The transaction status (e.g., <code>completed</code>).<br>See <a href="#transaction-status">below</a> for more details.</td></tr><tr><td><code>trackingStatus</code></td><td><code>string</code></td><td>The current tracking status (using <code>status</code> is enough and this field should not be necessary).</td></tr><tr><td><code>fromAsset</code></td><td><code>string</code></td><td>The asset being sent.</td></tr><tr><td><code>fromAmount</code></td><td><code>string</code></td><td>The amount of <code>fromAsset</code> that was actually deposited.</td></tr><tr><td><code>fromAddress</code></td><td><code>string</code></td><td>The address sending the asset.</td></tr><tr><td><code>toAsset</code></td><td><code>string</code></td><td>The asset being received.</td></tr><tr><td><code>toAmount</code></td><td><code>string</code></td><td>The amount of <code>toAsset</code>.</td></tr><tr><td><code>toAddress</code></td><td><code>string</code></td><td>The recipient address.</td></tr><tr><td><code>finalisedAt</code></td><td><code>number</code></td><td>UNIX timestamp indicating when the transaction finalized.</td></tr><tr><td><code>slippageTolerance</code></td><td><code>number</code> (Optional)</td><td>Tolerance of the quote the swap committed to, in basis points. See <a href="#realized-slippage">Realized slippage</a>.</td></tr><tr><td><code>realizedSlippageBps</code></td><td><code>number</code> (Optional)</td><td>Realized slippage of the settled output vs the quoted expected output, in basis points. Positive means the swap settled for <em>less</em> than quoted. See <a href="#realized-slippage">Realized slippage</a>.</td></tr><tr><td><code>meta</code></td><td><code>object</code></td><td>Metadata including images, provider info, and <code>fees</code> — the fees actually charged. See <a href="/pages/MPFP4ml2c7aOy7SPTKbh#realized-fees">Realized fees</a>. And the USD valuations <code>amountInUsd</code> / <code>amountOutUsd</code> (see <a href="#usd-amounts">USD amounts</a>).</td></tr><tr><td><code>payload</code></td><td><code>object</code></td><td>Additional transaction specific data.</td></tr><tr><td><code>legs</code></td><td><code>array</code></td><td>Detailed breakdown of each transaction leg.</td></tr></tbody></table>

***

#### Example response:

```json
{
  "chainId": "43114",
  "hash": "0x18f6d7b91ceffcc6d70b5d73f324198d9847531b7fe53d9446b7e60a64fa44b9",
  "block": 57181100,
  "type": "swap",
  "status": "completed",
  "trackingStatus": "completed",
  "fromAsset": "AVAX.AVAX",
  "fromAmount": "9.58",
  "fromAddress": "0xC935B2397f0c6f85235ceFba2Eb714fb5F919Ca0",
  "toAsset": "THOR.RUNE",
  "toAmount": "0",
  "toAddress": "thor1mse4ysqpru6s7f6twlskt2yz963xz630mwtxqn",
  "finalisedAt": 1739313043,
  "slippageTolerance": 300,
  "realizedSlippageBps": 42,
  "meta": {
    "provider": "THORCHAIN",
    "providerAction": "swap",
    "amountInUsd": "312.44",
    "amountOutUsd": "311.13",
    "fees": [
      {
        "type": "inbound",
        "amount": "0.000041953000041953",
        "asset": "AVAX.AVAX",
        "chain": "AVAX",
        "protocol": "THORCHAIN"
      }
    ],
    "images": {
      "from": "https://storage.googleapis.com/token-list-swapkit/images/avax.avax.png",
      "to": "https://storage.googleapis.com/token-list-swapkit/images/thor.rune.png",
      "provider": "https://storage.googleapis.com/token-list-swapkit/images/thor.rune.png",
      "chain": "https://storage.googleapis.com/token-list-swapkit/avax.avax.png"
    }
  },
  "payload": {
    "memo": "=:r:thor1mse4ysqpru6s7f6twlskt2yz963xz630mwtxqn:0:-_/t:5/50"
  },
  "legs": [
    {
      "chainId": "43114",
      "hash": "0x18f6d7b91ceffcc6d70b5d73f324198d9847531b7fe53d9446b7e60a64fa44b9",
      "block": 57181100,
      "type": "swap",
      "status": "completed",
      "trackingStatus": "completed",
      "fromAsset": "AVAX.AVAX",
      "fromAmount": "9.58",
      "fromAddress": "0xC935B2397f0c6f85235ceFba2Eb714fb5F919Ca0",
      "toAsset": "AVAX.AVAX",
      "toAmount": "9.58",
      "toAddress": "0x8F66c4AE756BEbC49Ec8B81966DD8bba9f127549",
      "finalisedAt": 1739313038,
      "meta": {
        "fees": [
          {
            "type": "inbound",
            "amount": "0.000041953000041953",
            "asset": "AVAX.AVAX",
            "chain": "AVAX",
            "protocol": "THORCHAIN"
          }
        ],
        "images": {
          "from": "https://storage.googleapis.com/token-list-swapkit/images/avax.avax.png",
          "to": "https://storage.googleapis.com/token-list-swapkit/images/avax.avax.png",
          "chain": "https://storage.googleapis.com/token-list-swapkit/avax.avax.png"
        }
      },
      "payload": {
        "memo": "=:r:thor1mse4ysqpru6s7f6twlskt2yz963xz630mwtxqn:0:-_/t:5/50"
      }
    },
    {
      "chainId": "thorchain-1",
      "hash": "18f6d7b91ceffcc6d70b5d73f324198d9847531b7fe53d9446b7e60a64fa44b9",
      "block": 19828318,
      "type": "swap",
      "status": "completed",
      "trackingStatus": "completed",
      "fromAsset": "AVAX.AVAX",
      "fromAmount": "9.58",
      "fromAddress": "0xc935b2397f0c6f85235cefba2eb714fb5f919ca0",
      "toAsset": "THOR.RUNE",
      "toAmount": "0",
      "toAddress": "thor1mse4ysqpru6s7f6twlskt2yz963xz630mwtxqn",
      "finalisedAt": 1739313043,
      "meta": {
        "provider": "THORCHAIN",
        "providerAction": "swap",
        "images": {
          "from": "https://storage.googleapis.com/token-list-swapkit/images/avax.avax.png",
          "to": "https://storage.googleapis.com/token-list-swapkit/images/thor.rune.png",
          "provider": "https://storage.googleapis.com/token-list-swapkit/images/thor.rune.png",
          "chain": "https://storage.googleapis.com/token-list-swapkit/thor.rune.png"
        }
      },
      "payload": {
        "memo": "=:r:thor1mse4ysqpru6s7f6twlskt2yz963xz630mwtxqn:0:-_/t:5/50",
        "thorname": ""
      }
    }
  ]
}
```

***

#### Notes:

* The `legs` array provides a detailed view of each step in the transaction process.
* `meta` contains additional information, including images, the swap provider details, the fees actually charged (`meta.fees`), and the USD valuations (`meta.amountInUsd` / `meta.amountOutUsd`).
* The `payload` may include data like `evmCalldata` or `memo` for more complex transactions.

#### Realized fees

`meta.fees` reports what the swap actually settled, as opposed to the estimates returned by `/v3/quote`. It uses the same schema as the quote response's `fees`, so a quote and its settled result can be compared entry for entry.

It appears at transaction level and on each entry of `legs`, and is omitted entirely — never an empty array — whenever no fee has settled yet or the provider exposes none.

One exception: **NEAR**'s `outbound` echoes the quote-time withdrawal fee, since 1Click exposes no settled value. It appears only on a successful swap and is already reflected in the amount received.

**Fee entry fields**

<table><thead><tr><th width="140">Field</th><th width="110">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td><code>string</code></td><td>One of <code>inbound</code>, <code>outbound</code>, <code>liquidity</code>, <code>network</code>, <code>affiliate</code>, <code>service</code>.</td></tr><tr><td><code>amount</code></td><td><code>string</code></td><td>Fee amount, in decimal units of <code>asset</code>.</td></tr><tr><td><code>asset</code></td><td><code>string</code></td><td>Asset the fee was charged in (e.g. <code>"BTC.BTC"</code>).</td></tr><tr><td><code>chain</code></td><td><code>string</code></td><td>Chain of the fee asset.</td></tr><tr><td><code>protocol</code></td><td><code>string</code></td><td>Provider that charged it (e.g. <code>"CHAINFLIP"</code>).</td></tr><tr><td><code>amountBps</code></td><td><code>number</code></td><td>Optional. Fee in basis points — present on <code>affiliate</code> and <code>service</code> entries.</td></tr></tbody></table>

It is an array, not one bucket per type, because a single swap can pay the same type more than once. Within one provider, entries sharing a type and asset are summed. Across legs they're concatenated, not summed: an exact repeat of type + asset + protocol is dropped, while the same type and asset from different protocols both appear. `inbound` is the exception — see below.

**Leg-level vs transaction-level**

Each leg reports what that leg paid. For `inbound`, the transaction level reports the sum of the wallet gas and any matching ingress. On a Chainflip BTC deposit, the miner fee sits on the deposit leg, Chainflip's ingress fee sits on the swap leg, and the transaction reports the total:

```json
{
  "meta": {
    "fees": [
      { 
        "type": "inbound", 
        "amount": "0.00001219", 
        "asset": "BTC.BTC", 
        "chain": "BTC", 
        "protocol": "CHAINFLIP" 
      }
    ]
  },
  "legs": [
    {
      "meta": {
        "fees": [
          { 
            "type": "inbound", 
            "amount": "0.00000861", 
            "asset": "BTC.BTC", 
            "chain": "BTC", 
            "protocol": "CHAINFLIP" 
          }
        ]
      }
    },
    {
      "meta": {
        "fees": [
          { 
            "type": "inbound", 
            "amount": "0.00000358", 
            "asset": "BTC.BTC", 
            "chain": "BTC", 
            "protocol": "CHAINFLIP" 
          }
        ]
      }
    }
  ]
}
```

Those two components merged because both were in BTC. When they're in different assets they stay separate — a Chainflip USDC deposit on Ethereum reports two inbound entries: the ingress fee in USDC and the wallet gas in ETH.

Plain transfers with no swap provider report no inbound fee — there is nothing to attribute it to. It is also omitted when the fee is zero, when the chain data doesn't expose the sender's fee (Zcash shielded sends, UTXO transactions with an unresolvable input, Tron when free bandwidth covered the cost).

#### USD amounts

`meta.amountInUsd` / `meta.amountOutUsd` value the amount deposited and received. Transaction-level only.

* Positive 2-decimal string or absent — never `"0"`. A sub-cent value rounds to zero and is therefore reported as absent, not as a misleading `"0.00"`. Absent means "not priced", not "worth nothing"; each field resolves independently.
* Priced at current price at track time; only live or recently-settled swaps (\~1h of `finalisedAt`), never backfilled.
* The first valid value stored is kept forever, even under `forceUpdate`. There is no settlement guard, so if you track while the swap is still pending, whatever `toAmount` the parser had at that moment is the value that gets frozen.

#### Realized slippage

On a completed swap, `/track` compares the settled output with the quote it was committed against. Both fields are in bps, transaction-level only (not per leg), and omitted when uncomputable.

| Field                 | Type                | Description                                                                           |
| --------------------- | ------------------- | ------------------------------------------------------------------------------------- |
| `slippageTolerance`   | `number` (optional) | Tolerance of the quote the swap committed to, in bps.                                 |
| `realizedSlippageBps` | `number` (optional) | (expected − actual) / expected in bps, rounded. Positive = received less than quoted. |

* Only on completed swaps. Either field may be absent; `slippageTolerance` in particular isn't available from every quote source.
* On a partially refunded swap the value measures against the full quoted amount, so it reads as a large positive number — check `trackingStatus` for `partially_refunded` before reading it as slippage.
* Automatic on Chainflip, NEAR, Garden, Flashnet, Jupiter and Harbor routes. On any other route, pass `routeId` on your `/track` calls until the response contains a parsed transaction — quotes expire \~5 minutes after quoting, so call right after broadcast.

### Transaction status

An important part of the response is the transaction status from the `status` field, which can have multiple values:

<table><thead><tr><th width="154">Status value</th><th>Explanation</th></tr></thead><tbody><tr><td><code>not_started</code></td><td>The swap has not happened yet.</td></tr><tr><td><code>pending</code></td><td>Intermediate state. The transaction has been detected by the mempool but is pending block confirmation.</td></tr><tr><td><code>swapping</code></td><td>The swap is happening.</td></tr><tr><td><code>completed</code></td><td>The swap is finished.</td></tr><tr><td><code>refunded</code></td><td>The swap was refunded because of the slippage settings.</td></tr><tr><td><code>unknown</code></td><td>Catch all for other situations.</td></tr><tr><td><code>failed</code></td><td>The transaction failed in an inbound EVM contract.</td></tr></tbody></table>


# /price - Lookup token prices

### Endpoint

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/price`&#x20;

***

### &#x20;Request parameters

<table><thead><tr><th width="103">Parameter</th><th width="95">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>tokens</code></td><td><code>array</code></td><td>List of token identifiers to fetch price for. Each item is an object with an <code>identifier</code> field (e.g., <code>{ "identifier": "ETH.ETH" }</code>).</td></tr><tr><td><code>metadata</code></td><td><code>boolean</code></td><td>If <code>true</code>, includes extended CoinGecko metadata (currently always included, even if set to <code>false</code>).</td></tr></tbody></table>

{% hint style="info" %}
Identifiers follow the format `Chain.Asset`, such as `ETH.ETH`, `BTC.BTC` or `SOL.SOL`, with the contract address added at the end when necessary, such as `ARB.PENDLE-0x0c880f6761F1af8d9Aa9C466984b80DAb9a8c9e8`.\
This is the same formatting used in our `/quote` endpoint.
{% endhint %}

***

### Example requests

{% tabs %}
{% tab title="Multiple Tokens (cURL)" %}

```sh
curl -X POST \
  'https://api.swapkit.dev/price' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "tokens": [
      { "identifier": "ETH.ETH" },
      { "identifier": "BTC.BTC" },
      { "identifier": "SOL.SOL" },
      {"identifier": "BSC.CAKE-0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82"}
    ],
    "metadata": true
  }'
```

{% endtab %}

{% tab title="Single Token (cURL)" %}

```sh
curl -X POST \
  'https://api.swapkit.dev/price' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "tokens": [
      { "identifier": "ETH.ETH" }
    ],
    "metadata": true
  }'
```

{% endtab %}
{% endtabs %}

***

### Response format

The endpoint returns an array of token price objects.

```json
[
  {
    "identifier": "ETH.ETH",
    "provider": "",
    "cg": {
      "id": "ethereum",
      "name": "Ethereum",
      "market_cap": 197138665861,
      "total_volume": 12560864823,
      "price_change_24h_usd": -39.89,
      "price_change_percentage_24h_usd": -2.38,
      "sparkline_in_7d": [...],
      "timestamp": "2025-04-15T12:30:44.643Z"
    },
    "price_usd": 1653.61,
    "timestamp": 1744720254562
  }
]
```

For each token, it includes the following fields:

<table><thead><tr><th width="309">Field</th><th width="94">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>identifier</code></td><td><code>string</code></td><td>The token identifier you queried (e.g., <code>ETH.ETH</code>).</td></tr><tr><td><code>price_usd</code></td><td><code>number</code></td><td>The current price of the token in USD.</td></tr><tr><td><code>timestamp</code></td><td><code>number</code></td><td>Millisecond timestamp of the price fetch.</td></tr><tr><td><code>cg</code></td><td><code>object</code></td><td><em>(If <code>metadata: true</code> — currently always present)</em> CoinGecko metadata.</td></tr><tr><td>└─ <code>id</code></td><td><code>string</code></td><td>CoinGecko’s internal ID.</td></tr><tr><td>└─ <code>name</code></td><td><code>string</code></td><td>Full name of the token.</td></tr><tr><td>└─ <code>market_cap</code></td><td><code>number</code></td><td>Total market capitalization in USD.</td></tr><tr><td>└─ <code>total_volume</code></td><td><code>number</code></td><td>24-hour trading volume in USD.</td></tr><tr><td>└─ <code>price_change_24h_usd</code></td><td><code>number</code></td><td>Price change in absolute USD over the last 24 hours.</td></tr><tr><td>└─ <code>price_change_percentage_24h_usd</code></td><td><code>number</code></td><td>Percentage price change over the last 24 hours.</td></tr><tr><td>└─ <code>sparkline_in_7d</code></td><td><code>array</code></td><td>7-day price history (useful for drawing sparkline charts).</td></tr><tr><td>└─ <code>timestamp</code></td><td><code>string</code></td><td>Timestamp of the CoinGecko data.</td></tr></tbody></table>

When a token's price is not found, or the token name is not correctly specified, the endpoint will return a price of 0 USD for that token. In the example below, `ETH.HTE` is not a valid identifier so the example response below fails to return a correct price:

```json
[
  {
    "identifier": "ETH.HTE",
    "provider": "",
    "price_usd": 0,
    "timestamp": 0
  }
]
```


# /v3/limit - Place and manage limit orders

Offer your users price-contingent swaps that execute asynchronously when the market reaches their target.

{% hint style="warning" %}
**Pre-release.** This API is in active development and request/response schemas may still change before GA. Only test with small amounts — orders placed here route real liquidity through our providers, so any funds committed are at real risk while the service stabilises.
{% endhint %}

The **SwapKit Limit Order API** lets integrators offer their users price-contingent swaps: the order rests until the market reaches the target price, then settles asynchronously. All endpoints sit under `/v3/limit/*`, every request must carry an `x-api-key` header, and all payloads are JSON.

Integrators **do not choose the provider**. SwapKit routes the pair server-side and returns the chosen provider on the quote. The wallet-signing shape differs per provider, and the service surfaces that difference on the `/v3/limit/build` response.

***

### Signing models

Every provider fits one of two signing models. `/v3/limit/build` tells you which one applies to a given order by populating exactly one set of fields:

| Model             | Build response                                  | What the wallet does                                                                                                                                         |
| ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Signed intent** | `typedData` populated, `tx` and `txMeta` `null` | Signs an off-chain payload. Nothing goes on-chain at build time — the provider holds the signed order until a filler matches it.                             |
| **Deposit**       | `tx` and `txMeta` populated, `typedData` `null` | Signs and broadcasts a transaction. The deposit **is** the order: its parameters are encoded into the transaction, and the resulting tx hash is the receipt. |

{% hint style="info" %}
**Branch on the artifact, not the provider.** Check `typedData !== null` instead of switching on `provider`. Every provider maps onto one of these two models — including ones not yet live — so your integration keeps working as new providers ship.
{% endhint %}

### Providers

| Provider      | Signing model       | Coverage                              | Chains                                                    | Status                                                  |
| ------------- | ------------------- | ------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------- |
| **1inch**     | Signed intent       | Same-chain EVM swaps                  | ETH, ARB, AVAX, BASE, BSC                                 | Available — more EVM chains rolling out                 |
| **Harbor**    | Deposit             | Cross-chain between BTC, ETH and USDT | BTC, ETH (+ USDT)                                         | Available — BTC ↔ ETH.ETH and BTC ↔ ETH.USDT pairs      |
| **THORChain** | Confirmed at launch | Native cross-chain across 11 chains   | BTC, ETH, LTC, ATOM, DOGE, AVAX, BSC, SOL, BASE, TRX, XRP | Coming soon — broadens BTC, LTC, Cosmos, Doge, TRX, XRP |
| **Jupiter**   | Confirmed at launch | Solana native assets                  | SOL                                                       | Coming soon — Solana spot liquidity                     |

New providers are added without a breaking change: the endpoints, the order object, and the status lifecycle stay identical, and the only per-provider variation is which signing model the `/build` response uses.

***

### Endpoint index

| Method | Endpoint                    | Description                                                                                         |
| ------ | --------------------------- | --------------------------------------------------------------------------------------------------- |
| `GET`  | `/v3/limit/tokens`          | Supported assets and pairs per provider.                                                            |
| `POST` | `/v3/limit/quote`           | Price the pair, reserve a `routeId`. [See below](#id-1.-price-the-pair).                            |
| `POST` | `/v3/limit/build`           | Persist the order and produce the signing artifact. [See below](#id-2.-build-the-signing-artifact). |
| `POST` | `/v3/limit/submit`          | Relay the wallet signature or deposit tx hash. [See below](#id-3.-submit-the-signed-artifact).      |
| `GET`  | `/v3/limit/orders/:orderId` | Single order detail, refreshed from the provider. [See below](#id-4.-single-order-detail).          |
| `GET`  | `/v3/limit/orders`          | Paginated list scoped to the API key. [See below](#id-5.-paginated-order-list).                     |
| `POST` | `/v3/limit/cancel/build`    | Pre-build a cancel transaction. [See below](#id-6.-cancel-an-order).                                |
| `POST` | `/v3/limit/cancel/submit`   | Record the broadcast cancel tx or signature. [See below](#id-7.-submit-the-cancellation).           |

***

## 1. Price the pair

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/limit/quote`

Returns a market price for the pair, computes the user's `limitPrice` deviation from spot, and caches a `routeId` for the subsequent `/v3/limit/build` call.

### Request schema

<table><thead><tr><th width="193.56640625">Parameter</th><th width="208.23828125">Type</th><th width="99.421875">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>Yes</td><td>The asset being sold (e.g. <code>"ETH.ETH"</code>)</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>Yes</td><td>The asset being bought (e.g. <code>"BTC.BTC"</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>decimal string</code></td><td>No</td><td>Provide <strong>any two</strong> of <code>sellAmount</code> / <code>buyAmount</code> / <code>limitPrice</code>; the third is derived. <strong>Human-readable decimal, not base units</strong> — selling 10 USDT is <code>"10"</code>, not <code>"10000000"</code>.</td></tr><tr><td><code>buyAmount</code></td><td><code>decimal string</code></td><td>No</td><td>Derived from the other two if omitted. Same human-readable decimal convention as <code>sellAmount</code>.</td></tr><tr><td><code>limitPrice</code></td><td><code>decimal string</code></td><td>No</td><td><code>buyAsset</code> per 1 <code>sellAsset</code>, in human-readable decimal.</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>No</td><td>Screened for AML at quote time and pre-filled into the <code>/v3/limit/build</code> hint.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>No</td><td>Same treatment as <code>sourceAddress</code>.</td></tr><tr><td><code>affiliateFee</code></td><td><code>int</code> (0–1000 bps)</td><td>No</td><td>Overrides the API key default.</td></tr><tr><td><code>expiresAt</code></td><td><code>int</code> (unix seconds)</td><td>No</td><td>Defaults to now + 3 days.</td></tr></tbody></table>

{% hint style="info" %}
Asset identifiers follow the same nomenclature as the rest of the API — `Chain.Asset` (`"BTC.BTC"`) or `Chain.Asset-ContractAddress` (`"ETH.USDC-0xA0b8…"`). See [`/tokens`](/swapkit-api/tokens-list-and-search-supported-tokens).
{% endhint %}

### Response schema

**Top level**

| Field     | Type           | Description                  |
| --------- | -------------- | ---------------------------- |
| `quoteId` | `string`       | UUID for this quote response |
| `routes`  | `LimitRoute[]` | A single-entry array.        |

**Per route**

<table><thead><tr><th width="221.390625">Field</th><th width="201.84375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>routeId</code></td><td><code>string</code></td><td>UUID of this specific swap response. Pass this to <code>/v3/limit/build</code>.</td></tr><tr><td><code>provider</code></td><td><code>enum</code></td><td>Provider used, chosen server-side.</td></tr><tr><td><code>sellAmount</code>, <code>buyAmount</code>, <code>limitPrice</code></td><td><code>decimal string</code></td><td>Resolved values, including the leg you didn't provide.</td></tr><tr><td><code>spotPrice</code></td><td><code>decimal string</code></td><td>Current market reference price.</td></tr><tr><td><code>effectiveFillPrice</code></td><td><code>decimal string</code></td><td>The price the market must reach for the order to fill: <code>spotPrice × (1 + feeGapBps / 10_000)</code>. The gap is <code>takerFeeBps + integratorFeeBps</code> on 1inch, where the integrator fee is charged on the taker side; on Harbor it's <code>takerFeeBps</code> alone, since the integrator fee is settled out of proceeds and doesn't affect the fill threshold. Equals <code>spotPrice</code> when the gap is zero. </td></tr><tr><td><code>takerFeeBps</code></td><td><code>number</code></td><td>Protocol-side taker fee in bps a whitelisted filler pays on top of the taking amount. Excludes the integrator fee, which is reported separately as <code>integratorFeeBps</code>.</td></tr><tr><td><code>spotPriceDeviationBps</code></td><td><code>number</code></td><td>Signed bps delta of <code>limitPrice</code> vs <strong><code>effectiveFillPrice</code></strong> — not vs raw <code>spotPrice</code>. <br><strong>Positive</strong>: the limit sits above the fill threshold, so the order waits for the market to move. <br><strong>Negative</strong>: it's at or below the threshold and would fill immediately at a worse rate than a market swap.</td></tr><tr><td><code>minExpirationSeconds</code>, <code>maxExpirationSeconds</code></td><td><code>number</code></td><td>Bounds for the <code>expiresAt</code> you may pass to <code>/v3/limit/build</code>.</td></tr><tr><td><code>integratorFeeBps</code></td><td><code>number</code></td><td>Affiliate fee applied to this route.</td></tr><tr><td><code>warnings</code></td><td><code>Warning[]</code></td><td>Structured warning objects — <a href="#warnings">see below</a>.</td></tr><tr><td><code>nextActions</code></td><td><code>object[]</code></td><td>Data needed for the next request in the flow (<code>/v3/limit/build</code> call).</td></tr></tbody></table>

Compare against `effectiveFillPrice`, not `spotPrice` — it's the threshold that triggers a fill, and `spotPriceDeviationBps` is measured from it.&#x20;

`expiresAt` is an absolute unix timestamp, but the bounds are durations: `expiresAt − now` must fall within `[minExpirationSeconds, maxExpirationSeconds]`. Outside that, `/build` returns `limitOrderExpirationOutOfBounds` (400).

### Warnings

Each entry in `warnings[]` is a structured object — the same shape swap quotes already use — so you can render a short `display` label with a longer `tooltip` behind it. `tooltip` is optional; null-check it. The array is always present and may be empty, and the cached route carries its warnings through to the `/v3/limit/build` response as well.

At most one price warning fires per quote. Both are measured against `effectiveFillPrice`, not `spotPrice`:

* `limitPriceBelowSpot` — `spotPriceDeviationBps ≤ -100`. The limit sits 1% or more below the effective fill price, so the order fills immediately at a clearly worse rate than a market swap.
* `limitPriceWithinFeeGap` — `-100 < spotPriceDeviationBps ≤ 0`, and the fee gap is non-zero. The limit sits inside the fee gap: it still fills immediately, but only modestly off-market. Raising the limit by `|spotPriceDeviationBps|` bps or more puts it above the threshold, where it rests as a genuine limit order.

Both mean the order fills on submission rather than resting — surface either to the user before they sign. Render the `display` string with `tooltip` behind it rather than switching on `code`, and treat `code` as an open enum: new values ship with new providers.

### Errors

| Status | Error code                  | Scenario                                                                                                                                                             |
| ------ | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `insufficientLiquidity`     | The provider could not return a market rate for the pair, or the market rate / requested amount evaluated to zero. Surfaced as *"Insufficient liquidity for trade"*. |
| 400    | `invalidSourceAddress`      | `sourceAddress` was provided but doesn't match the sell-chain address format. Per-chain validation fires at `/quote` rather than waiting for `/build`.               |
| 400    | `invalidDestinationAddress` | `destinationAddress` was provided but doesn't match the buy-chain address format. Same early-validation behaviour.                                                   |
| 401    | `apiKeyInvalid`             | Missing or unrecognized `x-api-key` header.                                                                                                                          |

***

## 2. Build the signing artifact

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/limit/build`

Validates and screens both addresses per-chain, resolves the token spender where the chain needs one, and runs the provider build, deep address screens, and approval check in parallel. Persists the order with `status = PENDING`.

### Request schema

<table><thead><tr><th width="211.0390625">Parameter</th><th width="146.75390625">Type</th><th width="113.75">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>routeId</code></td><td><code>string</code></td><td>Yes</td><td>The ID of the route to build the order from. Obtained from a previous <code>/v3/limit/quote</code> response</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>Yes</td><td>Blockchain address to send the asset from. Must be a valid address for the sell asset's chain. Becomes the order's <code>maker</code>.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>Yes</td><td>Recipient blockchain address to send the asset to. Must be a valid address for the buy asset's chain. Becomes the order's <code>receiver</code>.</td></tr><tr><td><code>expiresAt</code></td><td><code>int</code> (unix seconds)</td><td>Yes</td><td>Must fall inside <code>[minExpirationSeconds, maxExpirationSeconds]</code> from <code>/v3/limit/quote</code>.</td></tr><tr><td><code>allowPartialFill</code></td><td><code>bool</code></td><td>No</td><td>1inch only, and must be <code>true</code> (the default). Disabling either flag forces LOP v6 bit-invalidator mode, which the orderbook accepts only for RFQ orders, so <code>/build</code> returns <code>limitOrderUnsupportedFillFlags</code> (400). Harbor ignores both.</td></tr><tr><td><code>allowMultipleFills</code></td><td><code>bool</code></td><td>No</td><td>Same constraint as <code>allowPartialFill</code></td></tr><tr><td><code>usePermit2</code></td><td><code>bool</code></td><td>No</td><td><strong>Currently ignored server-side</strong> — a placeholder for an upcoming Permit2 two-phase build flow on EVM. Until it ships, EVM token orders use the <a href="#the-approvaltx-object"><code>approvalTx</code> path</a>. Default <code>false</code>.</td></tr></tbody></table>

### Build response schema

<table><thead><tr><th width="187.5">Field</th><th width="190.875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>orderId</code></td><td><code>string</code></td><td>UUID of this specific order. Pass it to <code>/v3/limit/submit</code> and every later call.</td></tr><tr><td><code>orderHash</code></td><td><code>string</code></td><td>Canonical on-chain identifier of the order, <code>0x</code>-prefixed.</td></tr><tr><td><code>typedData</code></td><td><code>object</code> (optional)</td><td>Off-chain payload for the wallet to sign. <strong>Signed-intent orders only</strong> — <code>null</code> on deposit orders. <a href="#the-typeddata-object">See below</a>.</td></tr><tr><td><code>tx</code></td><td>varies (optional)</td><td>Ready-to-sign deposit transaction. <strong>Deposit orders only</strong> — <code>null</code> on signed-intent orders. <a href="#the-tx-field">See below</a>.</td></tr><tr><td><code>txMeta</code></td><td><code>object</code> (optional)</td><td>Broadcast hints for <code>tx</code>. <strong>Deposit orders only</strong> — <code>null</code> on signed-intent orders. <a href="#the-tx-field">See below</a>.</td></tr><tr><td><code>isApproved</code></td><td><code>boolean</code> (optional)</td><td>Whether the sell-side token allowance is already in place. Omitted when no approval is part of the flow — <a href="#the-isapproved-field">See below</a>.</td></tr><tr><td><code>approvalTx</code></td><td><code>object</code> (optional)</td><td>Present when a token approval transaction must be submitted before the order can be signed or broadcast. <a href="#the-approvaltx-object">See below</a>.</td></tr><tr><td><code>warnings</code></td><td><code>Warning[]</code></td><td>Potential warnings about this order, carried over from <code>/v3/limit/quote</code>. Warnings never block the response.</td></tr><tr><td><code>nextActions</code></td><td><code>object[]</code></td><td>Data needed for the next request in the flow (<code>/v3/limit/submit</code> call).</td></tr></tbody></table>

### Response — the two signing models

You always get exactly one of the two shapes below, never both and never a mix — see [signing models](#signing-models).

{% tabs %}
{% tab title="Signed intent — typedData to sign" %}
**1inch today.**

```json
{
  "orderId": "ord_…",
  "orderHash": "0x…",
  "typedData": {
    "domain":      { },
    "types":       { "Order": [] },
    "primaryType": "Order",
    "message":     { }
  },
  "tx":     null,
  "txMeta": null,
  "isApproved": true,
  "warnings":   [],
  "nextActions": []
}
```

The wallet signs `typedData`, then you post the resulting signature to `/v3/limit/submit` as `{ orderId, signature }`.
{% endtab %}

{% tab title="Deposit — tx to broadcast" %}
**Harbor today.**

```json
{
  "orderId": "ord_…",
  "orderHash": "0x…",
  "typedData": null,
  "tx": {
    "from":  "0x…",
    "to":    "0x…",
    "value": "…",
    "data":  "0x…"
  },
  "txMeta": {
    "txType":  "evm",
    "chainId": "1",
    "memo":    "o:…"
  },
  "isApproved": true,
  "warnings":   [],
  "nextActions": []
}
```

The wallet signs and broadcasts `tx`, then you post the on-chain hash to `/v3/limit/submit` as `{ orderId, depositTxHash }`.
{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Why the two models exist.** They reflect two kinds of venue. An off-chain orderbook can hold a signed order until a filler matches it, so a signature is all it needs from the user. A deposit venue has no off-chain book to hold anything — the order doesn't exist until it's on-chain — so the deposit transaction has to carry the order parameters itself. 1inch is the first venue of the former kind, Harbor of the latter.
{% endhint %}

#### The `typedData` object

Returned for signed-intent orders only. A structured payload the wallet signs without broadcasting — today an EIP-712 document with `domain`, `types`, `primaryType`, and `message`. Pass it to the wallet unmodified and post the resulting signature to `/v3/limit/submit`; don't reconstruct or reorder it, since the signature is taken over the exact payload.

#### The `tx` field

Returned for deposit orders only. Its shape depends on the sell chain, and `txMeta` tells you how to handle it — the same pattern `/v3/swap` uses:

<table><thead><tr><th width="186.359375">Field</th><th width="159.66796875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>txMeta.txType</code></td><td><code>string</code></td><td>How to sign and broadcast <code>tx</code>. <code>"evm"</code> — <code>tx</code> is an object with <code>{ from, to, value, data }</code>. <code>"psbt"</code> — <code>tx</code> is a base64 PSBT string. More values ship with new chains.</td></tr><tr><td><code>txMeta.chainId</code></td><td><code>string</code></td><td>Chain the deposit is broadcast on. Numeric-string for EVM (<code>"1"</code>), slug for non-EVM (<code>"bitcoin"</code>).</td></tr><tr><td><code>txMeta.memo</code></td><td><code>string</code></td><td>The order memo encoded into the deposit, exposed so you can verify it before signing. Present for providers that encode order parameters in a memo.</td></tr></tbody></table>

#### The `approvalTx` object

Present when the sell asset is a token on a chain that requires an allowance to a spender contract, and the maker's current allowance is below the order amount. Same shape as the `approvalTx` returned by [`/v3/swap`](/swapkit-api/v3-swap-obtain-swap-transaction-details#the-approvaltx-object) — broadcast it and wait for confirmation **before** signing or broadcasting the order.

<table><thead><tr><th width="216.56640625">Field</th><th width="129.50390625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>approvalTx.to</code></td><td><code>string</code></td><td>Token contract address to send the approval</td></tr><tr><td><code>approvalTx.from</code></td><td><code>string</code></td><td>User wallet address.</td></tr><tr><td><code>approvalTx.value</code></td><td><code>string</code></td><td>ETH value — always <code>"0"</code> for approvals.</td></tr><tr><td><code>approvalTx.data</code></td><td><code>string</code></td><td>Encoded <code>approve()</code> call data.</td></tr><tr><td><code>approvalTx.gasLimit</code></td><td><code>string</code></td><td>Optional. Estimated max gas units for the approval transaction, as a hex quantity string</td></tr><tr><td><code>approvalTx.gasPrice</code></td><td><code>string</code></td><td>Optional. Gas price in wei per gas unit, as a hex quantity string. Legacy (type-0) gas pricing.</td></tr></tbody></table>

#### The `isApproved` field

`isApproved` is only populated when a separate token `approve()` is part of the flow. Treat it as a tri-state:

<table><thead><tr><th width="268.59765625">Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>isApproved: true</code></td><td>An approval is required for this order and the allowance is already in place. Nothing extra to broadcast.</td></tr><tr><td><code>isApproved: false</code></td><td>An approval is required and the wallet must broadcast the accompanying <a href="#the-approvaltx-object"><code>approvalTx</code></a> <strong>before</strong> signing the order.</td></tr><tr><td>Field omitted</td><td>No approval is required — the sell asset is a native asset, or the chain has no allowance model (UTXO chains, for instance).</td></tr></tbody></table>

Whether the field appears is a property of the **sell asset and its chain**, not of the provider.

{% hint style="warning" %}
Treat a missing `isApproved` as "nothing to broadcast", **not** as `false`.
{% endhint %}

***

## 3. Submit the signed artifact

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/limit/submit`

Attaches the wallet signature (signed-intent orders) or the on-chain deposit tx hash (deposit orders) to the order, advancing it from `PENDING` to `SUBMITTED`. The poller then watches for provider acknowledgement and flips `SUBMITTED` → `OPEN`.

### Request schema

Send `signature` **or** `depositTxHash`, depending on which artifact `/v3/limit/build` returned.

<table><thead><tr><th width="142.90234375">Parameter</th><th width="131.2578125">Type</th><th width="153.3828125">Required for</th><th>Description</th></tr></thead><tbody><tr><td><code>orderId</code></td><td><code>string</code></td><td>All orders</td><td>The ID of the order to submit. Obtained from a previous <code>/v3/limit/build</code> response</td></tr><tr><td><code>signature</code></td><td><code>string</code></td><td>Signed intent</td><td>Wallet signature over the <code>typedData</code> returned by <code>/v3/limit/build</code>. Relayed to the provider's orderbook.</td></tr><tr><td><code>depositTxHash</code></td><td><code>string</code></td><td>Deposit</td><td>Hash of the broadcast deposit transaction. Accepted case-insensitively and stored lowercase — it surfaces back as <code>depositHash</code> on <code>/v3/limit/orders/:orderId</code>.</td></tr></tbody></table>

```json
// Signed intent — wallet signature over typedData
{ "orderId": "ord_…", "signature": "0x…" }

// Deposit — hash of the broadcast deposit transaction
{ "orderId": "ord_…", "depositTxHash": "0x… | …btc txid…" }
```

### Submit response schema

<table><thead><tr><th width="180.109375">Field</th><th width="198.86328125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>orderId</code></td><td><code>string</code></td><td>UUID of this specific order.</td></tr><tr><td><code>orderHash</code></td><td><code>string</code></td><td>Canonical on-chain identifier of the order, <code>0x</code>-prefixed.</td></tr><tr><td><code>status</code></td><td><code>enum</code></td><td>Always <code>SUBMITTED</code> on success. See the <a href="#order-status-lifecycle">order status lifecycle</a>.</td></tr><tr><td><code>createdAt</code></td><td><code>ISO 8601 string</code></td><td>When the order was created.</td></tr></tbody></table>

{% hint style="warning" %}
**One shot — the order must still be `PENDING`.** Re-submitting an order, submitting one that has already advanced to `SUBMITTED` / `OPEN` / `FILLED`, or submitting one the stale-unsubmitted sweep has moved to `EXPIRED`, all return `409` with error code `limitOrderInvalidState`. Call `/build` and `/submit` back-to-back — the grace window before the sweep fires is 1 hour by default.
{% endhint %}

***

## 4. Single-order detail

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/v3/limit/orders/:orderId`

Returns one order. Syncs with the upstream provider on every call before returning, so the response always reflects post-sync state.

### Order object

<table><thead><tr><th width="181.5546875">Field</th><th width="185.2109375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>orderId</code></td><td><code>UUID string</code></td><td>UUID of this specific order. Stable across the order's lifetime.</td></tr><tr><td><code>orderHash</code></td><td><code>string</code></td><td>Canonical order identifier, <code>0x</code>-prefixed, stable for the order's whole lifetime. <strong>Signed-intent orders</strong>: the digest of the signed payload. <strong>Deposit orders</strong>: a deterministic hash over the order parameters — it is <strong>not</strong> replaced by the deposit tx hash after <code>/submit</code>; that hash surfaces separately as <code>depositHash</code>.</td></tr><tr><td><code>chainId</code></td><td><code>string enum</code></td><td><code>ChainId</code> of the sell asset. Numeric-string for EVM (<code>"1"</code>, <code>"42161"</code>, <code>"8453"</code>, …); slug for non-EVM (<code>"bitcoin"</code>, <code>"solana"</code>, <code>"thorchain-1"</code>, …).</td></tr><tr><td><code>provider</code></td><td><code>enum</code></td><td>Provider that routed this order.</td></tr><tr><td><code>maker</code></td><td><code>string</code></td><td>Blockchain address to send the asset from — the address that owns the order on the sell chain. Format varies per chain (EVM hex, BTC bech32, etc.).</td></tr><tr><td><code>receiver</code></td><td><code>string</code> or <code>null</code></td><td>Recipient blockchain address to send the asset to on the buy chain. Nullable only for legacy orders created without one.</td></tr><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>The asset being sold (e.g. <code>"ETH.ETH"</code>)</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>The asset being bought (e.g. <code>"BTC.BTC"</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>decimal string</code></td><td>Amount of the sell asset. <strong>Human-readable decimal, not smallest units</strong> — unlike <code>/v3/quote</code> and <code>/v3/swap</code>.</td></tr><tr><td><code>buyAmount</code></td><td><code>decimal string</code></td><td>Amount of the buy asset the order is resting for. Same human-readable decimal convention as <code>sellAmount</code>.</td></tr><tr><td><code>limitPrice</code></td><td><code>decimal string</code></td><td><code>buyAsset</code> per 1 <code>sellAsset</code>, in human-readable decimal.</td></tr><tr><td><code>filledSellAmount</code>, <code>filledBuyAmount</code></td><td><code>decimal string</code></td><td><code>"0"</code> before any fill. On providers that settle all-or-nothing they jump straight to the full amounts; on providers that support partial fills they climb incrementally.</td></tr><tr><td><code>txHashes</code></td><td><code>TxHash[]</code></td><td>On-chain transactions in the order's lifecycle, chronological. Always present; <code>[]</code> before anything lands. Populated by provider sync or backfill.</td></tr><tr><td><code>usdValueOpen</code></td><td><code>string</code> or <code>null</code></td><td>Sell-side notional in USD at build time. Approximate (cached price); <code>null</code> if the lookup missed.</td></tr><tr><td><code>usdValueClose</code></td><td><code>string</code> or <code>null</code></td><td>Realized buy-side notional in USD at the terminal transition. Same approximation; <code>null</code> until the order is terminal.</td></tr><tr><td><code>status</code></td><td><code>enum</code></td><td><code>PENDING</code>, <code>SUBMITTED</code>, <code>OPEN</code>, <code>PARTIAL</code>, <code>FILLED</code>, <code>CANCELLED</code>, <code>EXPIRED</code>, <code>FAILED</code>. See the <a href="#order-status-lifecycle">order status lifecycle</a>.</td></tr><tr><td><code>fees</code></td><td><code>Fee[]</code></td><td>List of fees applied to the order (liquidity, affiliate, service, network) — <a href="#fees-breakdown">see below</a>. Pre-fill: projected amounts. Post-fill: actual settled values where the provider reports them.</td></tr><tr><td><code>depositHash</code></td><td><code>string?</code></td><td>L1 tx hash of the deposit, lowercased. Chain-native format (<code>0x</code>-prefixed on EVM, raw 64-hex on Bitcoin). Populated by the provider sync once the deposit lands; omitted until then, and on models with no deposit leg.</td></tr><tr><td><code>withdrawHash</code></td><td><code>string?</code></td><td>L1 tx hash of the settlement (withdraw), lowercase <code>0x</code>-prefixed. Populated after the order fills; omitted before.</td></tr><tr><td><code>expiresAt</code></td><td><code>ISO 8601 string</code></td><td>TTL. SwapKit flips the order to <code>EXPIRED</code> locally once <code>expiresAt</code> is in the past; providers that escrow funds refund them at this point.</td></tr><tr><td><code>createdAt</code>, <code>updatedAt</code></td><td><code>ISO 8601 string</code></td><td>Server timestamps. <code>updatedAt</code> moves every time sync writes new state.</td></tr></tbody></table>

### Fees breakdown

Fees are categorized into different types based on their role in the order's lifecycle. Not every provider charges every type.

<table><thead><tr><th width="215.77734375">Fee Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>Liquidity</strong></td><td>Fee applied by the liquidity provider to facilitate the swap.</td></tr><tr><td><strong>Affiliate</strong></td><td>Fee paid to the specified affiliate, projected from the API key config.</td></tr><tr><td><strong>Service</strong></td><td>SwapKit's service fee. Currently <code>0</code>.</td></tr><tr><td><strong>Network</strong></td><td>Blockchain transaction fee for processing the order — destination-chain gas / outbound fee.</td></tr></tbody></table>

Each entry in `fees[]` has the following shape:

<table><thead><tr><th width="166.53125">Field</th><th width="178.91796875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td><code>enum</code></td><td><code>liquidity</code>, <code>affiliate</code>, <code>service</code> or <code>network</code>.</td></tr><tr><td><code>amount</code></td><td><code>string</code></td><td>Fee amount in human-readable decimal.</td></tr><tr><td><code>amountBps</code></td><td><code>number</code></td><td>Fee in basis points (100 bps = 1%).</td></tr><tr><td><code>asset</code></td><td><code>string</code></td><td>SwapKit asset identifier the fee is denominated in.</td></tr><tr><td><code>chain</code></td><td><code>string</code></td><td>Chain where the fee is paid or extracted.</td></tr><tr><td><code>protocol</code></td><td><code>enum</code></td><td>Provider that charges the fee.</td></tr></tbody></table>

```json
{
  "type":      "liquidity",
  "amount":    "0.00123",
  "amountBps": 30,
  "asset":     "ETH.USDT-0x…",
  "chain":     "ETH",
  "protocol":  "HARBOR"
}
```

{% hint style="info" %}
**Projected vs settled fees.** Before a fill, `fees[]` is a build-time projection priced on the sell chain. Once the order fills, providers that report settlement figures — Harbor today — replace their `liquidity` and `network` entries with actual values, and each replaced entry's `chain` becomes the fee asset's chain (`"ETH"` for a USDT fee). `affiliate` and `service` stay projected. So don't assume an entry's `chain` holds steady across the order's lifetime, and keep projected and settled entries apart when totalling per chain.

**Multi-affiliate encoding.** When an API key carries both a SwapKit fee and an integrator fee, providers that support two recipients encode both — Harbor's `o:` memo lists SwapKit first: `…:sk/<integrator>:<skBps>/<integratorBps>`. Where the orderbook takes only one recipient (1inch today), the SwapKit slot is fixed at 0.
{% endhint %}

### Errors

<table><thead><tr><th width="120.71484375">Status</th><th width="211.43359375">Error code</th><th>Scenario</th></tr></thead><tbody><tr><td>404</td><td><code>limitOrderNotFound</code></td><td>Unknown <code>orderId</code>, or the order belongs to a different API key. Ownership is enforced — the response does not leak existence across keys.</td></tr><tr><td>401</td><td><code>apiKeyInvalid</code></td><td>Missing or unrecognized <code>x-api-key</code> header.</td></tr></tbody></table>

***

## 5. Paginated order list

**Method:** `GET`\
**URL:** `https://api.swapkit.dev/v3/limit/orders`

Returns a paginated list of orders owned by the requesting API key, newest first. It does **not** re-sync with the provider on each call — use [`/v3/limit/orders/:orderId`](#id-4.-single-order-detail) when freshness matters.

### Query parameters

All optional. Filters are AND-combined except for `sourceAddress` / `destinationAddress`, whose join is controlled by `joinType`. Unknown query keys are silently ignored. API-key scoping is always applied — you cannot see another partner's orders even by querying a known address.

<table><thead><tr><th width="205.73046875">Parameter</th><th width="167.65625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>Filter by the blockchain address the asset is sold from (<code>Order.maker</code>). EVM addresses match case-insensitively, so checksummed and lowercase forms return the same orders. Non-EVM addresses (base58, bech32) match exactly, since their casing is significant.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>Filter by the recipient blockchain address (<code>Order.receiver</code>). Same casing rules as <code>sourceAddress</code>.</td></tr><tr><td><code>sourceChain</code></td><td><code>string enum</code></td><td>Filter by the <em>sell-asset</em> chain. Stringified EVM ids (<code>"1"</code>, <code>"42161"</code>, <code>"8453"</code>, …) or a non-EVM slug (<code>"bitcoin"</code>, <code>"solana"</code>, <code>"thorchain-1"</code>).</td></tr><tr><td><code>destinationChain</code></td><td><code>string enum</code></td><td>Filter by the <em>buy-asset</em> chain. Same value space as <code>sourceChain</code>.</td></tr><tr><td><code>joinType</code></td><td><code>enum</code></td><td><code>fullOuter</code> (default) combines <code>sourceAddress</code> / <code>destinationAddress</code> with <strong>OR</strong>; <code>inner</code> uses <strong>AND</strong>. Only meaningful when both address filters are set.</td></tr><tr><td><code>status</code></td><td><code>enum</code> or <code>enum[]</code></td><td>Any of <code>PENDING</code>, <code>SUBMITTED</code>, <code>OPEN</code>, <code>PARTIAL</code>, <code>FILLED</code>, <code>CANCELLED</code>, <code>EXPIRED</code>, <code>FAILED</code> — see the <a href="#order-status-lifecycle">order status lifecycle</a>. Accepts a single value (<code>?status=FILLED</code>), a repeated key (<code>?status=PENDING&#x26;status=FILLED</code>), or a comma-separated list (<code>?status=PENDING,FILLED</code>).</td></tr><tr><td><code>cursor</code></td><td><code>ISO 8601 string</code></td><td>Opaque pagination cursor — echo back the <code>nextCursor</code> from the previous page. Don't parse or manufacture values; the server compares strictly (<code>createdAt &#x3C; cursor</code>).</td></tr><tr><td><code>limit</code></td><td><code>int</code> (1–100)</td><td>Default 50. Values outside the range return 400.</td></tr></tbody></table>

There is no provider filter — filter by `sourceChain` / `destinationChain` instead.

### List response schema

| Field        | Type                | Description                                                                                                                                     |
| ------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `orders`     | `Order[]`           | Orders owned by the API key, sorted by `createdAt` descending. `Order` schema is [explained above](#order-object).                              |
| `nextCursor` | `string` (optional) | Pagination cursor for the next page. Present **iff** the page is full (`orders.length === limit`); its absence means the last page was reached. |

```json
{
  "orders":     [],
  "nextCursor": "2026-04-23T23:01:32.952Z"
}
```

To iterate: start with no cursor, then keep passing `nextCursor` until the response omits it.

{% hint style="warning" %}
**Terminal states are opt-in.** Omitting `status` does not return *all* statuses. The default is `[PENDING, SUBMITTED, OPEN, PARTIAL]` — active orders only. If you need terminal orders (`FILLED` / `CANCELLED` / `EXPIRED` / `FAILED`), list them explicitly, e.g. `?status=PENDING,SUBMITTED,OPEN,PARTIAL,FILLED,CANCELLED,EXPIRED,FAILED`.
{% endhint %}

{% hint style="warning" %}
**`sourceAddress` + `destinationAddress` are OR by default.** When both are passed, the default join is `fullOuter` (OR) — orders matching *either* side come back. If you need only orders matching *both* addresses, pass `joinType=inner` explicitly. Other filters (`sourceChain`, `destinationChain`, `status`, …) are still AND-combined with whatever the address join produces.
{% endhint %}

### Examples

{% tabs %}
{% tab title="cURL" %}

```bash
# Default — active orders only (PENDING / SUBMITTED / OPEN / PARTIAL)
curl 'https://api.swapkit.dev/v3/limit/orders?limit=50' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'
# → { "orders": [ …50 rows… ], "nextCursor": "2026-04-20T12:34:56.789Z" }

# Next page
curl 'https://api.swapkit.dev/v3/limit/orders?limit=50&cursor=2026-04-20T12:34:56.789Z' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'
# → { "orders": [ …23 rows… ] }   # no nextCursor → end of stream

# Filtered: filled orders from a specific maker address
curl 'https://api.swapkit.dev/v3/limit/orders?sourceAddress=bc1q…&status=FILLED&limit=20' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'

# Filter by chain — BTC → ETH orders
curl 'https://api.swapkit.dev/v3/limit/orders?sourceChain=bitcoin&destinationChain=1' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'

# Address join — fullOuter (default, OR): maker = bc1q… OR receiver = 0xabc…
curl 'https://api.swapkit.dev/v3/limit/orders?sourceAddress=bc1q…&destinationAddress=0xabc…' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'

# Address join — inner (AND): maker = bc1q… AND receiver = 0xabc…
curl 'https://api.swapkit.dev/v3/limit/orders?sourceAddress=bc1q…&destinationAddress=0xabc…&joinType=inner' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'

# Multi-status — repeat the key or use the comma form
curl 'https://api.swapkit.dev/v3/limit/orders?status=PENDING&status=FILLED' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'
curl 'https://api.swapkit.dev/v3/limit/orders?status=PENDING,FILLED' \
  -H 'x-api-key: YOUR_VARIABLE_HERE'
```

{% endtab %}
{% endtabs %}

### Errors

| Status | Error code         | Scenario                                                                                                                                                                        |
| ------ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `validation_error` | Bad `limit` (`<1` or `>100`), unparseable `cursor`, or an invalid value in `sourceChain` / `destinationChain` / `status` / `joinType`. The body lists the accepted enum values. |
| 401    | `apiKeyInvalid`    | Missing or unrecognized `x-api-key` header.                                                                                                                                     |

***

## 6. Cancel an order

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/limit/cancel/build`

Force-syncs the order with the provider and returns the appropriate signing artifact for cancellation. One request shape; the artifact you get back depends on how the order's venue and sell chain expect a cancellation to be authorised.

### Request schema

| Parameter | Type     | Required | Description                                                                                     |
| --------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
| `orderId` | `string` | Yes      | The ID of the order to cancel. Obtained from `/v3/limit/build` or a `/v3/limit/orders` response |

### Cancel build response schema

Exactly one of the three artifacts is non-null. Branch on which field is populated, not on the provider.

| Field                  | Type                | Description                                                                                                                            |
| ---------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `orderId`              | `string`            | UUID of the order being cancelled.                                                                                                     |
| `cancelTx`             | `object` (optional) | Ready-to-broadcast cancel transaction, for venues that cancel on-chain. `null` otherwise. **1inch today.**                             |
| `typedData`            | `object` (optional) | Off-chain cancel payload for the wallet to sign, for venues that accept a signed cancellation. `null` otherwise. **Harbor EVM today.** |
| `btcSignaturePreimage` | `string` (optional) | Canonical-JSON string to sign as a Bitcoin Signed Message, for Bitcoin-side orders. `null` otherwise. **Harbor BTC today.**            |
| `nextActions`          | `object[]`          | Data needed for the next request in the flow (`/v3/limit/cancel/submit` call).                                                         |

```json
{
  "orderId": "ord_…",
  "cancelTx": null,
  "typedData": null,
  "btcSignaturePreimage": null,
  "nextActions": []
}
```

{% tabs %}
{% tab title="cancelTx — broadcast on-chain" %}
The wallet broadcasts a cancel transaction to the venue's contract. Any existing token allowance is unaffected — only the specific order is invalidated. On 1inch this is a `cancelOrder(makerTraits, orderHash)` call on the aggregation router.

```json
"cancelTx": {
  "to": "0x…",
  "data": "0x…",
  "value": "0",
  "chainId": 1
}
```

The wallet broadcasts it; you then post the resulting `txHash` to `/v3/limit/cancel/submit`.
{% endtab %}

{% tab title="typedData — sign off-chain" %}
The wallet signs an EIP-712 cancellation message. Nothing goes on-chain from your side. Harbor's EVM payload uses `CancelAndWithdraw`:

```json
"typedData": {
  "primaryType": "CancelAndWithdraw",
  "types": {
    "CancelAndWithdraw": [
      { "name": "action",    "type": "string"  },
      { "name": "chain",     "type": "string"  },
      { "name": "l1Address", "type": "address" },
      { "name": "nonce",     "type": "uint256" },
      { "name": "expiry",    "type": "uint256" },
      { "name": "payload",   "type": "CancelAndWithdrawPayload" }
    ],
    "CancelAndWithdrawPayload": [
      { "name": "orderId", "type": "string" }
    ]
  },
  "domain": {},
  "message": {
    "action":    "cancel_and_withdraw",
    "chain":     "ETH",
    "l1Address": "0x…",
    "nonce":     1714500000,
    "expiry":    1714500600,
    "payload":   { "orderId": "trd-<uuid>" }
  }
}
```

Sign the payload exactly as returned. Note that `payload.orderId` is the **provider's** internal order id, *not* the SwapKit `orderId` or `orderHash` — on Harbor it's the `clientOrderId` (`trd-…`). `nonce` and `expiry` are JSON numbers (uint256 wire-encoded as JS numbers; values stay below `Number.MAX_SAFE_INTEGER`).
{% endtab %}

{% tab title="btcSignaturePreimage — Bitcoin Signed Message" %}
The wallet signs the canonical-JSON string as a Bitcoin Signed Message (BIP-137).

```json
"btcSignaturePreimage": "{\"action\":\"cancel_and_withdraw\",\"chain\":\"BTC\",\"domain\":\"harbor.orderbook.trading\",\"expiry\":1714500600,\"l1_address\":\"bc1q…\",\"nonce\":1714500000,\"payload\":{\"orderId\":\"trd-<uuid>\"}}"
```

Submit the base64-encoded compact secp256k1 signature.
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**Sign the preimage byte-for-byte.** The `btcSignaturePreimage` string is canonical JSON — keys **alphabetically sorted**, **no whitespace**, applied **recursively** — and the signature is verified over exactly those bytes. Sign the string as returned rather than re-serialising the object.

Two field-naming traps if you do rebuild it: the Bitcoin payload uses **snake\_case** `l1_address` where the EVM payload uses camelCase `l1Address`, and its `domain` is a **flat string** (`"harbor.orderbook.trading"`) rather than the EVM nested `{ name, version, … }` object.
{% endhint %}

{% hint style="warning" %}
**Preconditions.** `/v3/limit/cancel/build` force-syncs the order with the provider before responding. If the provider hasn't acknowledged the order yet, the call fails with `limitOrderInvalidState` — on Harbor, with the message *"Harbor clientOrderId not yet known — wait until status reaches OPEN"*. Wait for the order to advance past `SUBMITTED` before attempting cancellation.
{% endhint %}

***

## 7. Submit the cancellation

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/v3/limit/cancel/submit`

Records the broadcast cancel transaction or the wallet's cancellation signature, depending on which artifact `/v3/limit/cancel/build` returned.

### Request schema

| Parameter   | Type     | Required for                        | Description                                                                                                                                  |
| ----------- | -------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `orderId`   | `string` | All cancellations                   | The ID of the order being cancelled. Obtained from a previous `/v3/limit/cancel/build` response                                              |
| `txHash`    | `string` | `cancelTx`                          | Hash of the broadcast cancel transaction.                                                                                                    |
| `signature` | `string` | `typedData`, `btcSignaturePreimage` | Wallet's cancellation signature — `0x`-hex for an EIP-712 signature, base64 for a BIP-137 one. The provider verifies the format server-side. |

```json
// Broadcast cancel tx
{ "orderId": "ord_…", "txHash": "0x…" }

// EIP-712 signature (0x-hex)
{ "orderId": "ord_…", "signature": "0x…" }

// BIP-137 signature (base64)
{ "orderId": "ord_…", "signature": "H4sIAAA…" }
```

### Cancel submit response schema

| Field     | Type                | Description                                                                                              |
| --------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
| `orderId` | `string`            | UUID of the cancelled order.                                                                             |
| `status`  | `enum`              | `CANCELLED` on success. See the [order status lifecycle](#order-status-lifecycle).                       |
| `txHash`  | `string` (optional) | Hash of the broadcast cancel transaction. Returned only for the `cancelTx` path; omitted for signatures. |

```json
{
  "orderId": "ord_…",
  "status":  "CANCELLED",
  "txHash":  "0x…"
}
```

{% hint style="warning" %}
**Cancel payloads can expire.** Signed cancel payloads carry a server-side `expiry` — Harbor's is set **10 minutes** after `/v3/limit/cancel/build`. If the wallet takes longer than that to sign and you submit a stale payload, `/v3/limit/cancel/submit` returns `limitOrderInvalidState` with a message such as *"cancel payload expired — call /v3/limit/cancel/build again"*. Read `expiry` off the payload rather than hard-coding the window, and have hardware-wallet flows re-build before signing if they're running close to it.
{% endhint %}

### Errors

| Status | Error code               | Scenario                                                                                                                                                                                                               |
| ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 409    | `limitOrderInvalidState` | The order is in a state that doesn't allow cancellation: a terminal state, the provider hasn't acknowledged it yet, the payload is past its expiry, or it's already cancelled. The body carries a descriptive message. |
| 400    | `limitOrderCancelFailed` | The provider rejected the cancellation.                                                                                                                                                                                |
| 404    | `limitOrderNotFound`     | Unknown `orderId`, or it belongs to a different API key.                                                                                                                                                               |

***

## Order status lifecycle

`PENDING` → `SUBMITTED` → `OPEN` → `PARTIAL` → `FILLED` / `CANCELLED` / `EXPIRED` / `FAILED`

Everything past `SUBMITTED` is driven by a poller that reconciles `SUBMITTED` / `OPEN` / `PARTIAL` orders against the provider every 30 minutes, publishing each transition to the [webhook deliverer](#webhooks). `GET /v3/limit/orders/:orderId` force-syncs on every call — use it when you need state fresher than the poller interval.

| Status                                     | Description                                                                                                                                                                                                        |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PENDING`                                  | The order has been built but `/v3/limit/submit` hasn't been called yet. No signature or deposit hash on file.                                                                                                      |
| `SUBMITTED`                                | `/v3/limit/submit` has attached the wallet signature or the deposit tx hash, but the provider hasn't acknowledged the order as resting yet (still in the mempool, or not yet indexed).                             |
| `OPEN`                                     | Emitted by the poller once the provider confirms the order is resting. **Not monotonic** — if a provider temporarily has no record of an order, sync can regress it `OPEN` → `SUBMITTED` until visibility returns. |
| `PARTIAL`                                  | The order has been partly filled. Only emitted by providers that support partial fills; all-or-nothing paths go straight to `FILLED`.                                                                              |
| `FILLED`, `CANCELLED`, `EXPIRED`, `FAILED` | Terminal.                                                                                                                                                                                                          |

{% hint style="warning" %}
**Auto-expiry of unsubmitted orders.** At the start of every poller tick, SwapKit sweeps `PENDING` orders that never received a `/v3/limit/submit` (no signature, no deposit hash) and flips them to `EXPIRED` when either the order's own `expiresAt` has passed *or* it has been sitting unsubmitted for longer than the grace window (**1 hour by default**, configurable via `LIMIT_ORDER_SUBMIT_GRACE_SECONDS`). The sweep is provider-agnostic. Orders are marked `EXPIRED` rather than deleted so they remain in the audit history. Attempts to submit an already-expired order return `limitOrderInvalidState`.
{% endhint %}

***

## Webhooks

Configure `apiKey.settings.LIMIT_ORDER_WEBHOOK_URL` (under `ApiKeySettingsSchema`, alongside `NOTIFICATION` and `VAULT_SWAPS`; `apiKey.config` is reserved strictly for fee configuration). The webhook deliverer POSTs every status transition over HTTP, for orders on any provider.

Verify authenticity via the `x-swapkit-signature` header, which carries an HMAC-SHA256 of the raw body keyed to the webhook secret.

### Headers

| Header                       | Description                                                   |
| ---------------------------- | ------------------------------------------------------------- |
| `x-swapkit-signature`        | Hex HMAC-SHA256 of the raw body, keyed to the webhook secret. |
| `content-type`               | Always `application/json`.                                    |
| `x-swapkit-event-id`         | Matches `eventId` in the body. Dedupe on it.                  |
| `x-swapkit-delivery-attempt` | 1-based delivery attempt number for this event.               |

### Body schema

<table><thead><tr><th width="239.48046875">Field</th><th width="178.68359375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>type</code></td><td><code>string</code></td><td>Always <code>"limitOrder.statusChanged"</code>.</td></tr><tr><td><code>eventId</code></td><td><code>string</code></td><td>Unique id for this delivery event. Use it to dedupe redeliveries.</td></tr><tr><td><code>orderId</code></td><td><code>string</code></td><td>UUID of this specific order.</td></tr><tr><td><code>orderHash</code></td><td><code>string</code></td><td>Canonical on-chain identifier of the order, <code>0x</code>-prefixed.</td></tr><tr><td><code>apiKeyId</code></td><td><code>number</code></td><td>Numeric id of the API key that owns the order.</td></tr><tr><td><code>provider</code></td><td><code>string</code></td><td>Provider that routed the order.</td></tr><tr><td><code>chainId</code></td><td><code>string</code></td><td>ChainId of the sell asset.</td></tr><tr><td><code>maker</code></td><td><code>string</code></td><td>Sell-side address that owns the order.</td></tr><tr><td><code>sellAsset</code>, <code>buyAsset</code></td><td><code>string</code></td><td>SwapKit asset identifiers.</td></tr><tr><td><code>sellAmount</code>, <code>buyAmount</code></td><td><code>decimal string</code></td><td>Order amounts, human-readable decimal.</td></tr><tr><td><code>limitPrice</code></td><td><code>decimal string</code></td><td><code>buyAsset</code> per 1 <code>sellAsset</code></td></tr><tr><td><code>previousStatus</code></td><td><code>enum</code></td><td>The status the order transitioned from.</td></tr><tr><td><code>status</code></td><td><code>enum</code></td><td>The new status. See the <a href="#order-status-lifecycle">order status lifecycle</a>.</td></tr><tr><td><code>previousFilledSellAmount</code>, <code>previousFilledBuyAmount</code></td><td><code>decimal string</code></td><td>Filled amounts before this transition.</td></tr><tr><td><code>filledSellAmount</code>, <code>filledBuyAmount</code></td><td><code>decimal string</code></td><td>Filled amounts after this transition.</td></tr><tr><td><code>integratorFeeBps</code></td><td><code>number</code> or <code>null</code></td><td>Integrator fee encoded on the order.</td></tr><tr><td><code>createdAt</code></td><td><code>ISO 8601 string</code></td><td>When the transition was recorded.</td></tr></tbody></table>

***

## Integrator checklist

1. **Obtain an API key configured for limit orders.** The affiliate fee and integrator recipient live on the key's config — see [Monetization](/monetization).
2. **Call** [**`/v3/limit/quote`**](#id-1.-price-the-pair)**.** Pass the pair plus **any two** of `sellAmount` / `buyAmount` / `limitPrice`. Optionally pre-send `sourceAddress` / `destinationAddress` for early screening.
3. **Inspect the route.** `spotPriceDeviationBps` shows the user how far their limit is from spot; `minExpirationSeconds` / `maxExpirationSeconds` bound the `expiresAt` you can pass to `/v3/limit/build`.
4. **Call** [**`/v3/limit/build`**](#id-2.-build-the-signing-artifact) with `routeId`, `sourceAddress`, `destinationAddress`, and `expiresAt`.
5. **Handle the token approval.** Check [`isApproved`](#the-isapproved-field) and broadcast the accompanying [`approvalTx`](#the-approvaltx-object) first if it's `false`.
6. **Sign per the** [**signing model**](#signing-models)**.** If `typedData` is non-null, the wallet signs it and you post `{ orderId, signature }` to [`/v3/limit/submit`](#id-3.-submit-the-signed-artifact). If `tx` is non-null, the wallet signs and broadcasts it — using `txMeta.txType` to pick the right codec — and you post `{ orderId, depositTxHash }`.
7. **Track state.** Poll [`GET /v3/limit/orders/:orderId`](#id-4.-single-order-detail) (which also refreshes from the provider) or paginate via [`GET /v3/limit/orders`](#id-5.-paginated-order-list). Treat `OPEN` as [non-monotonic](#order-status-lifecycle).
8. **Cancel when needed.** [`POST /v3/limit/cancel/build`](#id-6.-cancel-an-order) → the wallet signs or broadcasts whichever artifact came back → [`POST /v3/limit/cancel/submit`](#id-7.-submit-the-cancellation).
9. **Optional: webhooks.** Configure `apiKey.settings.LIMIT_ORDER_WEBHOOK_URL` to receive `PENDING` → `SUBMITTED` → `OPEN` → `PARTIAL` → `FILLED` / `CANCELLED` / `EXPIRED` transitions over HTTP.

{% hint style="info" %}
**Staying forward-compatible.** Branch on the artifact (`typedData !== null`), never on `provider`. Switch on `txMeta.txType` rather than on the chain, so an unrecognised value fails loudly instead of being signed with the wrong codec. Treat `provider`, `warnings[].code`, `txMeta.txType`, and `fees[].type` as open enums with an explicit fallback — new values ship with new providers and chains. And read `minExpirationSeconds` / `maxExpirationSeconds` and `fees[]` off each quote rather than hard-coding today's values. An integration written that way picks up new providers without a code change.
{% endhint %}


# Introduction

A drop-in, non-custodial cross-chain swap interface for your site. The widget  handles wallets, quoting, approvals, and execution so you don't have to.

The SwapKit Widget is a drop-in, embeddable swap interface for your website or app. It gives your users a complete cross-chain swapping experience (pick what they pay, pick what they receive, connect a wallet, and swap) without sending them to a third-party site, and without you having to build any of the swap flow yourself.

Under the hood it is powered by the same liquidity, routing, and pricing as SwapKit's API, so your users get full cross-chain coverage and you get none of the integration work.

You can try the potential of SwapKit's Widget in [this demonstration page](https://swap.swapkit.dev/).

### What your users can do

From a single embedded component, an end user can:

* Choose a source asset and a destination asset across many chains.
* Connect a wallet, or paste a destination address for the receiving chain.
* See live quotes and routes, with fees and estimated time.
* Approve token spending when a swap requires it.
* Review, confirm, and broadcast the swap from their own wallet.
* Track their recent swaps from a built-in history view.

Every swap is non-custodial. Funds move directly from the user's wallet along the route, and the user signs in their own wallet.

### What's handled for you

The point of the widget is that the hard parts are abstracted away. You do not implement, and do not maintain:

* Wallet connection and the many wallet SDKs across chains.
* Quoting, route selection, and price discovery.
* Token approvals and transaction building.
* Broadcasting and swap status tracking.
* Mobile vs desktop wallet handling, chain-support filtering, and other edge cases.

You bring a set of credentials and an optional bit of styling and asset selection; the widget brings the entire swap experience.

<figure><img src="/files/VEH6UNSIvVvcmrUYOdxv" alt="" width="375"><figcaption><p>One of the widget's default themes</p></figcaption></figure>

### Make it yours

The widget is designed to sit inside your product, not look like a bolt-on:

* **Theming.** Match your brand with your own colors. The widget exposes color tokens for background, surfaces, accent, buttons, text, and borders, but can also ship with sensible light and dark default theme options.
* **Wallets.** Offer every supported wallet, or narrow the list to just the ones you want.
* **Default assets.** Preselect the pay and receive assets your users are most likely to want.
* **Swapping options.** Limit the swapping options to what is relevant to your users.
* **Sizing.** The widget fills its container, so you decide where it sits and how large it is.

All of this is configured visually, with a live preview, in **Widget Studio**. No code is required to restyle or re-scope the widget.

#### Two ways to embed

The widget ships in two equivalent forms, so it fits any stack:

* A **web component** `<swapkit-widget>` you can drop into any HTML page, including no-code platforms like WordPress.
* A **React component** `<SwapKitWidget />` for your React / Next.js app, with full TypeScript types.

Both render the same interface and behave identically at runtime. See the instructions on how to integrate SwapKit's Widget for the hands-on setup.

### Charge affiliate fees

You can select to apply fees to every swap routed through your widget integration and monetize the swaps done on your site.

Affiliate fees are not applied automatically to widget swaps. To earn fees on swaps done through the widget, add your affiliate configuration in the app's **Affiliate Config** tab. Once configured, the fees are applied to every swap routed through that app's widget, so this step is required if you intend to charge fees.\
See the [monetization page](/monetization) for the full setup instructions, it is the same setup used for SwapKit's regular API integration.

### How a typical integration goes

1. [**Create your widget keys**](/swapkit-widget/creating-your-widget-keys)**:** register your domain in the dashboard and get a Widget ID and Widget Key.
2. [**Configure in Widget Studio**](/swapkit-widget/widget-studio)**:** set your theme, wallets, and default assets with a live preview. See Settings & Configuration.
3. [**Integrate**](/swapkit-widget/integrating-swapkits-widget)**:** copy the generated snippet, or install the React package, and place it on your site.


# Creating your widget keys

Register your domain in the dashboard to get the Widget ID and Signing Secret your widget uses to authenticate.

Before you can configure or embed the widget, you need a widget key. A widget key is a **Widget ID** + **Signing Secret** pair, created in the SwapKit dashboard and bound to the domain where the widget will run. Everything else (Widget Studio, the embed snippet, all settings) starts from this key, so this is the first step of any integration.

### Prerequisites

* A [SwapKit dashboard](https://dashboard.swapkit.dev) account with an app. You can register in our dashboard to access it.
* The domain (or domains) where the widget will be embedded.

### Add a domain

<figure><img src="/files/b2rmKpZgCIdXE5GXsmea" alt="" width="563"><figcaption><p>Key setup example</p></figcaption></figure>

1. Open your application in the [SwapKit dashboard](https://dashboard.swapkit.dev) and enter or create your app.
2. Open the **Widget Keys** tab.
3. Click **+ Add Domain**.
4. Enter the domain where the widget will be embedded, without `https://`. The widget will only work on that domain.
   * Use a wildcard prefix (e.g. `*.example.com`) to allow all subdomains. The base domain (`example.com`) is included automatically.&#x20;

<figure><img src="/files/hzlicdF5zGyINaawWVDV" alt="" width="563"><figcaption></figcaption></figure>

5. Confirm. The dashboard creates a **Widget ID** and **Widget Key** string scoped to that domain.&#x20;

> The **Signing Secret** is only shown when the key is created or rotated. Copy it somewhere safe at creation time. If you lose it, rotate the key to generate a new one.\
> Rotating invalidates the old secret immediately, so any live widget still using it will stop authenticating until you update your deployed integration with the new secret.

### Managing keys

<figure><img src="/files/v267fHqnOpkTT1svPCbH" alt="" width="563"><figcaption></figcaption></figure>

From the Widget Keys table you can:

* Copy the **Widget ID**.
* Open the key in **Widget Studio**.
* Enable or disable a key.
* Rotate the Widget Key string (open the dropdown when hovering the key).
* Delete a key.

Keep in mind:

* Rotating a Widget Key invalidates the old key immediately. Update your deployed snippet with the new key at the same time.
* Disabling or deleting a key stops the widget from authenticating on that domain.
* Keys are domain-bound. A key created for one domain will not authenticate on another, so create a separate domain entry (or use a wildcard) for each place the widget runs.

### Affiliate fees

As mentioned earlier, affiliate fees won't be charged until you set it up explicitly beforehand. Click on the Affiliate Config tab at the top of the dashboard and follow the steps on the [monetization page](/monetization).

<figure><img src="/files/7UYniIoORqvMpstogAdJ" alt=""><figcaption></figcaption></figure>

#### Next

With a key created and Studio open, continue to [Settings & Configuration](/swapkit-widget/configuring-in-code) to theme the widget, choose wallets, and generate your embed snippet, or start by [trying out the default integration](/swapkit-widget/integrating-swapkits-widget).


# Widget Studio

Use our Widget Studio to configure SwapKit's Widget

Widget Studio is a UI to configure the widget to your needs and liking. It can be accessed from the dashboard through the Widget Studio button, or by selecting a specific Widget ID by the arrow symbol.

You can also test the widget studio [on this page](https://widget.swapkit.dev/studio) without the need to register, however this link isn't directly filled with your widget credentials.

<figure><img src="/files/rb8rFO5bafQBRTdNHk7k" alt=""><figcaption></figcaption></figure>

The configuration is separated into three different tabs:

* Settings: default asset selection, chain configuration and wallet configuration.
* Design: tune the widget's visual design to adapt it to your needs.
* Integrate: copy the embedded snippet into your site.

<figure><img src="/files/cUiwhvzRHOu9U0iy9N98" alt="" width="375"><figcaption></figcaption></figure>

### Settings

Widget ID and Widget Key are the preferred method of authentication and chosen by default. Authentication by SwapKit's API Key can expose it to your users.

#### Default assets

You can choose which assets are displayed to your users first. By default, SwapKit selects them based on asset popularity across the enabled chains, but you can toggle this off and pick whichever you think are most relevant to your site.

<figure><img src="/files/sVgrt27oVfbC4MjV4hEZ" alt="" width="375"><figcaption></figcaption></figure>

#### Enabled chains

Select which chains you want to offer as swapping options. Use the presets to choose preferences like EVM-Only or BTC+ETH, among others, or add and remove chains individually.

<figure><img src="/files/5sVVYrcPtNoAUvCxhMMg" alt="" width="563"><figcaption></figcaption></figure>

#### Configuring wallets

Wallet configuration is separated between enabling and disabling, which is similar to chain configuration, and configuring wallet connection. You can follow the links to help on how to get access to the specific wallets.

<figure><img src="/files/G7XtSrJuhdT4JlYZKVLK" alt="" width="375"><figcaption></figcaption></figure>

All selected wallets will be available to users when they connect to the widget to swap.

<figure><img src="/files/jMZQzy9vfDTDKMmeGkef" alt="" width="563"><figcaption><p>Connecting a wallet to use with the widget</p></figcaption></figure>

### Design

The default widget color palette follows SwapKit's own, but all items can be customized to better fit your website. There are three preset designs for each of the dark and light themes, but you can edit the colors of every item to your preference.

<figure><img src="/files/co5d3Ph0PI6baXietsqC" alt="" width="375"><figcaption></figcaption></figure>

Beyond the color theme, you can also adjust two shape-related settings: the border radius and the font style.

<figure><img src="/files/Zrnzur7hVtS7tXe5zoEb" alt="" width="375"><figcaption></figcaption></figure>

### Integrate

The integration tab contains the snippets to copy into your application. It is shared as a HTML web component and in React as a configured react component. Remember that you need to have `@swapkit/ui` installed.\
Both options render the same interface and behave identically at runtime, so pick the one that matches your stack.

### Configuring in code

If you want to tailor the experience for users on the go, check out the [documentation on how to edit the widget with code](/swapkit-widget/configuring-in-code). We recommend starting with an integration copied from the Widget Studio, but you can tweak details depending on your needs or on the specific page your users are visiting.


# Integrating SwapKit's Widget

Add the widget to your site as a web component on any HTML page, or as a React component in a React or Next.js app.

The widget ships in two equivalent forms. Both render the same interface and behave identically at runtime, so pick the one that matches your stack.

| Form                                    | Best for                                       | Distribution       |
| --------------------------------------- | ---------------------------------------------- | ------------------ |
| `<swapkit-widget>` **web component**    | Plain HTML, WordPress, or any non-React stack. | CDN script tag     |
| `<SwapKitWidget />` **React component** | React / Next.js apps, with TypeScript types.   | npm: `@swapkit/ui` |

### Web component (CDN)

Load the bundle once, then place the `<swapkit-widget>` element where you want it. Wrap it in a container that controls its size, since the widget fills the width and height of its host element.

```html
<!-- SwapKit Widget -->
<script type="module" src="https://cdn.swapkit.dev/widget/latest/swapkit-widget.js"></script>

<div style="max-width: 560px; min-height: 640px;">
  <swapkit-widget
    widget-id="YOUR_WIDGET_ID"
    widget-key="YOUR_WIDGET_KEY"
  ></swapkit-widget>
</div>
```

The script self-registers the `<swapkit-widget>` element and injects the widget's styles into the page once. Styles are scoped (under the `sk-ui-*` class prefix and a scoped preflight) so they won't collide with your site's CSS.

#### Use the snippet from Widget Studio

In practice, you should not hand-build this snippet. Configure the widget in **Widget Studio** and copy the snippet from the **Integrate** tab, which automatically fills in your credentials, selected wallets, theme, default assets, and any wallet-provider configuration. A generated snippet looks like this:

```html
<script type="module" src="https://cdn.swapkit.dev/widget/latest/swapkit-widget.js"></script>

<swapkit-widget
  widget-id="YOUR_WIDGET_ID"
  widget-key="YOUR_WIDGET_KEY"
  wallets="METAMASK,PHANTOM,WALLETCONNECT"
  color-primary="220 18% 8%"
  color-accent="155 86% 62%"
  config='{"apiKeys":{"walletConnectProjectId":"YOUR_PROJECT_ID"}}'
></swapkit-widget>
```

Paste it anywhere in your page and wrap it in your own sizing container. The attributes have no required order and you can edit them freely. See [Settings & Configuration](/swapkit-widget/configuring-in-code) for what each one does.

<figure><img src="/files/YHgeJicuGNWUJT0qa8p2" alt="" width="368"><figcaption></figcaption></figure>

### React (npm)

Install `@swapkit/ui`, import the stylesheet once at your app root, then render the component:

```tsx
import "@swapkit/ui/swapkit.css";
import { SwapKitWidget } from "@swapkit/ui/react";

export function SwapWidget() {
  return (
    <SwapKitWidget
      widgetId={process.env.NEXT_PUBLIC_SWAPKIT_WIDGET_ID!}
      widgetKey={process.env.SWAPKIT_WIDGET_KEY!}
      inputAsset="BTC.BTC"
      outputAsset="ETH.USDT-0xdAC17F958D2EE523A2206206994597C13D831EC7"
    />
  );
}
```

<figure><img src="/files/6BTWqIUpL11F8xsbCA90" alt="" width="337"><figcaption></figcaption></figure>

Notes:

* Importing `@swapkit/ui/swapkit.css` is required. Without it the widget renders unstyled.
* The widget fills its parent, so place it inside a container set to your desired width and height.
* React 19 is a peer dependency, and your bundler must support ESM.

See [Settings & Configuration](/swapkit-widget/configuring-in-code) for the full list of props.


# Configuring in code

Configure the widget directly in code: every web component attribute and React prop, and how to update them at runtime to adapt to the user or the page.

Most partners should configure these settings visually in [Widget Studio](/swapkit-widget/widget-studio) and copy the generated snippet, rather than writing attributes by hand. The reference tables here are for when you need to understand or fine-tune a value the snippet produced, perhaps to adapt the behaviour to the specific user visiting the site or to the content of the page they are viewing.

Every setting is exposed in two places, and the two map one-to-one:

* **Web component:** kebab-case HTML attributes on `<swapkit-widget>` (e.g. `input-asset`).
* **React:** camelCase props on `<SwapKitWidget />` (e.g. `inputAsset`).

You can set them statically in your embed, or set them programmatically so the widget reflects the current context. The full lists are in [Full attribute reference](#full-attribute-reference-web-component) and [Full props reference](#full-props-reference-react).

### Updating settings at runtime

This is the main reason to configure in code rather than from a static snippet: you can change the widget to match the user or the page they are on.

{% tabs %}
{% tab title="HTML" %}
Registered attributes are observed, so setting an attribute with JavaScript re-renders the widget in place. For example, on a token page, preselect that token as the receive asset and narrow the wallet list:

```js
const widget = document.querySelector("swapkit-widget");
widget.setAttribute("output-asset", "ETH.USDT-0xdAC17F958D2EE523A2206206994597C13D831EC7");
widget.setAttribute("wallets", "METAMASK,WALLETCONNECT");
```

{% endtab %}

{% tab title="React" %}
Props are reactive, so drive them from state, the route, or the user's session and the widget updates when they change:

```tsx
function PageWidget({ tokenForThisPage }: { tokenForThisPage: string }) {
  const [theme, setTheme] = useState<SwapKitThemeTokens>(darkTheme);

  return (
    <SwapKitWidget
      widgetId={process.env.NEXT_PUBLIC_SWAPKIT_WIDGET_ID!}
      widgetKey={process.env.SWAPKIT_WIDGET_KEY!}
      outputAsset={tokenForThisPage}
      theme={theme}
    />
  );
}
```

{% endtab %}
{% endtabs %}

Common things to drive dynamically:

* **Default assets** to match the product, token, or pair that the page focuses on.
* **Wallet list** to suit the user's region or device.
* **Theme** to follow a light/dark toggle on the host page.

Caveats:

* Only attributes in the attribute reference are observed; setting an unrecognized attribute has no effect. Every documented attribute (including all theme tokens) re-renders on change.
* Do not swap auth values (`widget-id` / `widget-key` / `api-key`) at runtime in normal use. Set them once.

***

### Authentication

The widget supports two mutually exclusive authentication modes.

| Mode                          | Web component              | React props              | Notes                                                                            |
| ----------------------------- | -------------------------- | ------------------------ | -------------------------------------------------------------------------------- |
| **Widget auth** (recommended) | `widget-id` + `widget-key` | `widgetId` + `widgetKey` | Recommended for all browser embeds. Both values are required together.           |
| **API key**                   | `api-key`                  | `apiKey`                 | Supported, but exposes an API key client-side. Use only in trusted environments. |

Behaviour to be aware of:

* **Both widget values are required.** If only one of `widget-id` / `widget-key` is present, the widget logs a warning and API requests fail authentication.
* **API key takes precedence.** If an API key is present alongside a widget pair, the widget uses the API key and clears the widget auth. For public embeds, set only the widget pair.
* **Widget auth is domain-bound.** Keys are issued for an exact domain (`example.com`) or a wildcard (`*.example.com`, which also covers the base domain). The widget only authenticates on the registered domain.

To create a widget key, add a domain, or rotate the key, see [Creating your widget keys](/swapkit-widget/creating-your-widget-keys).

***

### Theming

The widget is themed with a set of tokens. Set them with the `theme` prop (React) or individual `color-*` / `border-radius` / `font-family` attributes (web component).

Color values are HSL channels without the `hsl()` wrapper, for example `155 86% 62%`, not `hsl(155 86% 62%)`. Use the form `"H S% L%"` or `"H S% L% / A"` (the `/ A` adds an alpha channel). The widget composes these into `hsl(...)` internally.

#### Color tokens

All of these color tokens are observed at runtime. Every one except Accent Text is also exposed in Studio's Design tab and emitted in the generated snippet when changed from its default.

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="118">Token</th><th>Web component attribute</th><th width="160">React theme key</th><th>Default</th><th>Affects</th></tr></thead><tbody><tr><td>Background</td><td><code>color-primary</code></td><td><code>background</code></td><td><code>140 6% 8%</code></td><td>Main widget background</td></tr><tr><td>Surface</td><td><code>color-secondary</code></td><td><code>surface</code></td><td><code>120 3% 13%</code></td><td>Elevated surfaces (cards, dialogs)</td></tr><tr><td>Accent</td><td><code>color-accent</code></td><td><code>accent</code></td><td><code>140 87% 79%</code></td><td>Accent color (steppers, highlights)</td></tr><tr><td>Accent Text</td><td><code>color-accent-foreground</code></td><td><code>accentForeground</code></td><td><code>140 6% 8%</code></td><td>Text on accent backgrounds</td></tr><tr><td>Button</td><td><code>color-primary-button</code></td><td><code>primaryButton</code></td><td><code>0 0% 100% / 0.92</code></td><td>Primary button background</td></tr><tr><td>Button Text</td><td><code>color-primary-button-foreground</code></td><td><code>primaryButtonForeground</code></td><td><code>140 6% 8%</code></td><td>Primary button text</td></tr><tr><td>Text</td><td><code>color-text</code></td><td><code>text</code></td><td><code>0 0% 100% / 0.92</code></td><td>Main text</td></tr><tr><td>Muted Text</td><td><code>color-muted-foreground</code></td><td><code>mutedForeground</code></td><td><code>0 0% 100% / 0.64</code></td><td>Secondary text</td></tr><tr><td>Border</td><td><code>color-border</code></td><td><code>border</code></td><td><code>0 0% 100% / 0.12</code></td><td>Borders</td></tr><tr><td>Hover</td><td><code>color-bg-hover</code></td><td><code>hover</code></td><td><code>0 0% 100% / 0.08</code></td><td>Hover state</td></tr><tr><td>Active</td><td><code>color-bg-active</code></td><td><code>active</code></td><td><code>0 0% 100% / 0.12</code></td><td>Active / pressed state</td></tr><tr><td>Overlay</td><td><code>color-bg-overlay</code></td><td><code>overlay</code></td><td><code>0 0% 0% / 0.8</code></td><td>Modal backdrop overlay</td></tr></tbody></table>

#### Shape tokens

<table><thead><tr><th width="126">Token</th><th width="143">Web component attribute</th><th width="115">React theme key</th><th width="130">Default</th><th>Affects</th></tr></thead><tbody><tr><td>Border radius</td><td><code>border-radius</code></td><td><code>radius</code></td><td><code>0.5rem</code></td><td>Corner rounding (CSS length, not HSL)</td></tr><tr><td>Font family</td><td><code>font-family</code></td><td><code>fontFamily</code></td><td>system stack</td><td>Font applied to the widget root</td></tr></tbody></table>

Example:

{% tabs %}
{% tab title="HTML" %}

```html
<swapkit-widget
  widget-id="YOUR_WIDGET_ID"
  widget-key="YOUR_WIDGET_KEY"
  color-primary="220 18% 8%"
  color-secondary="220 14% 13%"
  color-accent="155 86% 62%"
  color-primary-button="155 86% 62%"
  color-primary-button-foreground="220 18% 8%"
></swapkit-widget>
```

{% endtab %}

{% tab title="React" %}
Use the `theme` prop:

```tsx
<SwapKitWidget
  widgetId="YOUR_WIDGET_ID"
  widgetKey="YOUR_WIDGET_KEY"
  theme={{
    background: "220 18% 8%",
    surface: "220 14% 13%",
    accent: "155 86% 62%",
    primaryButton: "155 86% 62%",
    primaryButtonForeground: "220 18% 8%",
    radius: "0.75rem",
  }}
/>
```

{% endtab %}
{% endtabs %}

***

### Default assets

You can set which assets the widget loads with. Assets use the format `CHAIN.SYMBOL` (e.g. `BTC.BTC`) or `CHAIN.SYMBOL-CONTRACT` for tokens (e.g. `ETH.USDT-0xdAC17F958D2EE523A2206206994597C13D831EC7`).

<table><thead><tr><th width="135"></th><th width="137">Web component</th><th width="124">React prop</th><th>Default</th></tr></thead><tbody><tr><td>Pay asset</td><td><code>input-asset</code></td><td><code>inputAsset</code></td><td><code>BTC.BTC</code></td></tr><tr><td>Receive asset</td><td><code>output-asset</code></td><td><code>outputAsset</code></td><td><code>ETH.USDT-0xdAC17F958D2EE523A2206206994597C13D831EC7</code></td></tr></tbody></table>

These are good candidates for runtime updates: set `output-asset` (or `outputAsset`) to whichever token the current page is about.

### Chain selection

By default, the widget operates on every API-supported chain. You can restrict the set with an allowlist or blocklist. Dropping a chain hides it from the asset selector and auto-disables any wallet whose only chains were removed (for example, removing `XRP` also removes Xaman).

{% tabs %}
{% tab title="HTML" %}
There are three attributes, each taking a comma-separated list of chains. Set only one of them. (If you set more than one, the widget uses the first it finds, in the order `chains`, then `chains-include`, then `chains-exclude`.)

Show only the chains you list:

```html
<swapkit-widget chains="BTC,ETH,SOL" ...></swapkit-widget>
```

`chains-include` is an alternative way to write that same allowlist:

```html
<swapkit-widget chains-include="BTC,ETH,SOL" ...></swapkit-widget>
```

Or start from every chain and remove a few, with `chains-exclude`:

```html
<swapkit-widget chains-exclude="TRON,DASH" ...></swapkit-widget>
```

To operate on every chain (the default), either omit these attributes or set `chains="all"`.
{% endtab %}

{% tab title="React" %}
Pass the `chains` prop:

```tsx
type ChainConfig =
  | Chain[]                  // explicit list
  | "all"                    // every API-supported chain (default)
  | { include: Chain[] }     // allowlist
  | { exclude: Chain[] };    // blocklist
```

{% endtab %}
{% endtabs %}

#### Supported chains

Chain identifiers are the SwapKit chain keys. Unlike `wallets`, they are case-sensitive on the web component attribute, so pass them exactly as listed. Supported chains:

`BTC`, `ETH`, `TRON`, `BSC`, `SOL`, `ZEC`, `XRP`, `ARB`, `BASE`, `OP`, `POL`, `AVAX`, `ADA`, `GAIA`, `SUI`, `NEAR`, `DOGE`, `LTC`, `BCH`, `DASH`, `GNO`, `XLAYER`, `THOR`, `MAYA`, `BERA`, `MONAD`, `XRD`, `KUJI`, `TON`, `STRK`, and `HOOD`.

***

### Wallet selection

By default the widget shows every available wallet. You can restrict the list with an allowlist or blocklist.

**Web component**: use one of `wallets`, `wallets-include`, or `wallets-exclude` (mutually exclusive; precedence is `wallets`, then `include`, then `exclude`):

```html
<swapkit-widget wallets="METAMASK,LEDGER,WALLETCONNECT" ...></swapkit-widget>
```

**React**: pass the `wallets` prop:

```ts
type WalletConfig =
  | WalletOption[]                  // explicit list
  | "all"                           // every available wallet (default)
  | "none"                          // disable wallet connection
  | { include: WalletOption[] }     // allowlist
  | { exclude: WalletOption[] };    // blocklist
```

Common `WalletOption` values: `METAMASK`, `LEDGER`, `TREZOR`, `KEYSTORE`, `WALLETCONNECT`, `PHANTOM`, `COINBASE_WEB`, `COINBASE_MOBILE`, `XAMAN`, `RADIX_WALLET`, `PASSKEYS`. But there are more than 20 different wallets.

The visible wallet list is also filtered automatically at runtime:

* **Mobile filtering.** Hardware wallets and desktop-only extensions are hidden on mobile user agents. Wallets with a mobile path (MetaMask, Coinbase, Phantom) stay visible because they may load inside the wallet's in-app browser.
* **Experimental wallets** (currently `KEEPKEY` and `VULTISIG`) are hidden in production and only surface when Developer Mode is enabled.
* **Direct-signing support.** A wallet is only shown if it supports at least one widget-supported chain.

***

### Wallet-provider credentials

Some wallet providers require additional credentials before they can connect. Fill the corresponding fields in Studio's Settings tab and they are serialized automatically into the generated snippet's `config` JSON attribute (empty fields are omitted). You rarely need to write this JSON by hand.

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="174">Wallet / integration</th><th>Required fields</th><th>Recommended fields</th><th>Where to get it</th></tr></thead><tbody><tr><td><strong>WalletConnect</strong></td><td><code>apiKeys.walletConnectProjectId</code></td><td>None</td><td>https://cloud.reown.com</td></tr><tr><td><strong>Xaman (XRPL)</strong></td><td><code>apiKeys.xaman</code></td><td>None</td><td>https://apps.xumm.dev</td></tr><tr><td><strong>Radix Wallet</strong></td><td><code>integrations.radix.dAppDefinitionAddress</code>, <code>applicationName</code>, <code>applicationVersion</code></td><td>Network ID / Name, Dashboard Base URL (have defaults)</td><td>https://console.radixdlt.com</td></tr><tr><td><strong>KeepKey</strong></td><td><code>integrations.keepKey.basePath</code>, <code>url</code></td><td><code>name</code>, <code>imageUrl</code></td><td>https://docs.keepkey.com</td></tr><tr><td><strong>Passkeys</strong></td><td><code>apiKeys.passkeys</code></td><td>None</td><td>Issued by SwapKit / dashboard</td></tr><tr><td><strong>Trezor</strong></td><td>None hard-required</td><td><code>integrations.trezor.email</code>, <code>appUrl</code></td><td>https://docs.trezor.io/trezor-suite/packages/connect</td></tr><tr><td><strong>Coinbase Wallet</strong></td><td>None hard-required</td><td><code>integrations.coinbase.appName</code>, <code>appLogoUrl</code></td><td>https://docs.cdp.coinbase.com/wallet-sdk/docs/installing</td></tr><tr><td><strong>NEAR Wallet Selector</strong></td><td><code>integrations.nearWalletSelector.contractId</code></td><td>None</td><td>Your NEAR contract/account setup</td></tr></tbody></table>

#### The `config` payload

The full shape accepted by the `config` attribute:

```json
{
  "apiKeys": {
    "walletConnectProjectId": "...",
    "xaman": "...",
    "passkeys": "...",
    "keepKey": "..."
  },
  "integrations": {
    "coinbase": { "appName": "...", "appLogoUrl": "https://..." },
    "nearWalletSelector": { "contractId": "..." },
    "trezor": { "email": "support@example.com", "appUrl": "https://example.com" },
    "keepKey": { "name": "...", "imageUrl": "https://...", "basePath": "...", "url": "..." },
    "radix": {
      "dAppDefinitionAddress": "...",
      "applicationName": "...",
      "applicationVersion": "...",
      "network": { "networkId": 1, "networkName": "mainnet", "dashboardBase": "https://dashboard.radixdlt.com" }
    }
  }
}
```

To use it, for the different integration options:

***

### Developer settings

These control which environment the widget talks to. Production embeds should omit all of them.

<table><thead><tr><th width="192">Setting</th><th width="147">Web component</th><th width="122">React</th><th>Notes</th></tr></thead><tbody><tr><td>API endpoint override</td><td><code>api-base-url</code></td><td><code>apiBaseUrl</code></td><td>Override the SwapKit API base URL (default <code>https://api.swapkit.dev</code>). Use only for staging/dev.</td></tr><tr><td>Developer Mode</td><td><code>develop-mode</code></td><td>None</td><td>Boolean attribute. Surfaces experimental wallets and dev behavior, and switches Studio's generated CDN to <code>cdn-dev.swapkit.dev</code>. Omit in production.</td></tr><tr><td>Dev API URL</td><td><code>dev-api-url</code></td><td>None</td><td>API URL used when Developer Mode is enabled.</td></tr></tbody></table>

A development embed looks like this (note the dev CDN):

```html
<script type="module" src="https://cdn-dev.swapkit.dev/widget/latest/swapkit-widget.js"></script>

<swapkit-widget
  widget-id="YOUR_DEV_WIDGET_ID"
  widget-key="YOUR_DEV_WIDGET_KEY"
  develop-mode
></swapkit-widget>
```

***

### URL sync

Set `syncUrl` (React) to mirror the selected assets and amount into the page's query string. The parameters are `input_asset`, `output_asset`, and `amount` (for example `?input_asset=BTC.BTC&output_asset=ETH.ETH&amount=0.1`). This is a React-only prop; there is no web component attribute for it. Most partner embeds leave it off (default `false`). It is useful if you want the current selection to survive a reload or be shareable as a link.

***

### Full attribute reference (web component)

<table><thead><tr><th width="165">Attribute</th><th width="167">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>widget-id</code></td><td>string</td><td>Widget ID from the dashboard. Pair with <code>widget-key</code>.</td></tr><tr><td><code>widget-key</code></td><td>string</td><td>Widget Key from the dashboard. Pair with <code>widget-id</code>.</td></tr><tr><td><code>api-key</code></td><td>string</td><td>Alternative auth mode. Takes precedence over widget auth.</td></tr><tr><td><code>api-base-url</code></td><td>URL</td><td>Override the SwapKit API base URL.</td></tr><tr><td><code>develop-mode</code></td><td>boolean</td><td>Enable development behavior. Presence (or <code>"true"</code>) is treated as enabled.</td></tr><tr><td><code>dev-api-url</code></td><td>URL</td><td>Development API URL used when <code>develop-mode</code> is set.</td></tr><tr><td><code>input-asset</code></td><td>asset string</td><td>Initial pay asset. Default <code>BTC.BTC</code>.</td></tr><tr><td><code>output-asset</code></td><td>asset string</td><td>Initial receive asset. Default Ethereum USDT.</td></tr><tr><td><code>wallets</code></td><td><code>all</code>, <code>none</code>, or CSV</td><td>Restrict which wallet options are shown.</td></tr><tr><td><code>wallets-include</code></td><td>CSV</td><td>Explicit allowlist.</td></tr><tr><td><code>wallets-exclude</code></td><td>CSV</td><td>Blocklist, showing all wallets except these.</td></tr><tr><td><code>chains</code></td><td><code>all</code> or CSV</td><td>Restrict which chains the widget operates on.</td></tr><tr><td><code>chains-include</code></td><td>CSV</td><td>Explicit chain allowlist.</td></tr><tr><td><code>chains-exclude</code></td><td>CSV</td><td>Chain blocklist, showing all chains except these.</td></tr><tr><td><code>config</code></td><td>JSON string</td><td>SDK config patch for wallet/provider credentials.</td></tr><tr><td><code>sentry-dsn</code></td><td>string</td><td>Override the bundled telemetry DSN (web component only).</td></tr><tr><td><code>color-*</code></td><td>HSL string</td><td>Color theme tokens. See Theming.</td></tr><tr><td><code>border-radius</code></td><td>CSS length</td><td>Corner rounding. See Theming.</td></tr><tr><td><code>font-family</code></td><td>font stack</td><td>Widget font. See Theming.</td></tr></tbody></table>

Registered attributes are observed: mutating one re-renders the widget.

### Full props reference (React)

<table><thead><tr><th width="130">Prop</th><th width="188">Type</th><th width="136">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>widgetId</code></td><td><code>string</code></td><td>None</td><td>Widget ID. Pair with <code>widgetKey</code>.</td></tr><tr><td><code>widgetKey</code></td><td><code>string</code></td><td>None</td><td>Widget Key. Pair with <code>widgetId</code>.</td></tr><tr><td><code>apiKey</code></td><td><code>string</code></td><td>None</td><td>Alternative auth mode. Takes precedence over widget auth.</td></tr><tr><td><code>apiBaseUrl</code></td><td><code>string | null</code></td><td><code>https://api.swapkit.dev</code></td><td>Override the API base URL. Missing schemes are normalized.</td></tr><tr><td><code>inputAsset</code></td><td><code>string | null</code></td><td><code>BTC.BTC</code></td><td>Initial pay asset.</td></tr><tr><td><code>outputAsset</code></td><td><code>string | null</code></td><td><code>ETH.USDT-0x…</code></td><td>Initial receive asset.</td></tr><tr><td><code>theme</code></td><td><code>SwapKitThemeTokens</code></td><td>None</td><td>Theme tokens (colors, <code>radius</code>, <code>fontFamily</code>). See Theming.</td></tr><tr><td><code>colors</code></td><td><code>SwapKitThemeTokens</code></td><td>None</td><td>Deprecated alias for <code>theme</code>, kept for backwards compatibility.</td></tr><tr><td><code>wallets</code></td><td><code>WalletConfig</code></td><td><code>"all"</code></td><td>Wallet allow/deny configuration.</td></tr><tr><td><code>chains</code></td><td><code>ChainConfig</code></td><td><code>"all"</code></td><td>Chain allow/deny configuration.</td></tr><tr><td><code>config</code></td><td><code>{ apiKeys?; integrations? }</code></td><td>None</td><td>SDK credentials payload for wallet providers.</td></tr><tr><td><code>developMode</code></td><td><code>boolean</code></td><td><code>false</code></td><td>Enable developer mode (experimental wallets, dev API URL).</td></tr><tr><td><code>devApiUrl</code></td><td><code>string | null</code></td><td>None</td><td>API URL used when <code>developMode</code> is <code>true</code>.</td></tr><tr><td><code>className</code></td><td><code>string</code></td><td>None</td><td>Extra class name on the widget root.</td></tr><tr><td><code>disableTelemetry</code></td><td><code>boolean</code></td><td><code>false</code></td><td>Disable the widget's Sentry telemetry.</td></tr><tr><td><code>syncUrl</code></td><td><code>boolean</code></td><td><code>false</code></td><td>Sync selected assets and amount to URL query params.</td></tr></tbody></table>

The React component exposes `disableTelemetry` rather than a DSN override; `sentry-dsn` exists only on the web component path.

***

### Troubleshooting

**API requests fail authentication.** Confirm that either `api-key` is present, or both `widget-id` and `widget-key` are present. For dashboard-created embeds, prefer the widget pair.

**Works locally but fails in production.** Confirm the production domain matches the domain registered under Widget Keys, and that you did not include `https://` when creating the domain key.

**A rotated key stopped the widget.** Update the deployed snippet with the new Widget Key. The old one stops working immediately after rotation.

**A wallet does not appear.** Check `wallets` / `wallets-include` / `wallets-exclude`, confirm the wallet supports at least one widget-supported chain, and note that hardware and desktop-only options are hidden on mobile and experimental wallets are hidden outside Developer Mode.

**Custom colors do not apply.** Use HSL channel values without `hsl()`: `155 86% 62%`, not `hsl(155 86% 62%)`.

**The widget renders unstyled (React).** Import `@swapkit/ui/swapkit.css` once at your app root.


# Transaction Payload Signing

SwapKit can cryptographically sign the transaction payloads returned by the Swap API so integrators can verify that responses originate from SwapKit and have not been tampered with in transit. This page describes the **generic ES256 signing flow** that works for any swap, on any chain, with any token.

> SLIP-0024 is a separate envelope format used specifically to render human-readable confirmation screens on hardware wallets. It is signed with a different scheme (ECDSA over **secp256k1** of a binary-encoded payment request) and is **not** the signature described here. See the separate [SLIP-0024 documentation](https://docs.swapkit.dev/spotlights/slip-0024-transaction-payload-signing).

***

#### Overview

* Each API key can be associated with a **secp256r1 (P-256 / ES256)** key pair.
* SwapKit holds the private key; integrators receive the **public key** when the key pair is created.
* When a swap response includes a transaction, SwapKit signs it and returns, on **each route's `meta` object**:
  * `meta.signedTx` — the JWS payload component (see below). It is **not** the raw transaction.
  * `meta.signature` — the ES256 signature, encoded as a Flattened JWS signature.
  * `meta.signedTxString` — the exact serialized transaction bytes SwapKit hashed and signed (the signature **pre-image**). It is the RFC 8785 canonical JSON of an object transaction, or the raw string of a string transaction. Use it to run the binding check without re-serializing the `tx` yourself.
* The signature is a **Flattened JWS (RFC 7515)**: SwapKit serializes the transaction to its **canonical form** (see [Transaction serialization](/spotlights/transaction-payload-signing#transaction-serialization) below), computes the SHA-256 digest of that canonical byte string as a lowercase hex string, base64url encodes that hex string as the JWS payload (`meta.signedTx`), and signs the JWS Signing Input. The integrator verifies `meta.signature` against the reconstructed JWS Signing Input using the stored public key.

If no key pair is configured for the API key — or the response does not include a transaction — the swap response is returned **unsigned** and `meta.signedTx` / `meta.signature` / `meta.signedTxString` are omitted. Note that sending `disableBuildTx: true` suppresses transaction building and therefore signing: if you build your own transaction from our response, there is nothing for SwapKit to sign.

> **The signature covers a digest of the transaction, not the transaction directly.** Verifying the signature is necessary but not sufficient — see Verify the signature for the two checks you must perform.

#### Activate signing for your API key

Signing is activated by SwapKit on your behalf — there is no self-serve endpoint. To enable it, **contact your SwapKit account manager** and request a signing key pair for your API key.

Once provisioned, you will receive a **public key** in PEM format, for example:

```
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
```

Notes:

* Store the public key securely on your side — this is the only value you need to verify signatures.
* SwapKit holds the corresponding private key. It is never exposed to integrators and is stored encrypted at rest using Google Cloud KMS under a per-tenant encryption key.
* A key pair cannot be overwritten in place. To rotate, request a rotation through your account manager.
* `secp256r1` (ES256) is the current default. Other key types may be added in the future.

#### Receive signed swap responses

Once signing is active, a swap response that includes a transaction carries the signature in each route's `meta`:

```json
{
  "routes": [
    {
      "tx": { "...": "..." },
      "meta": {
        "signedTx": "<base64url(SHA-256 hex digest of the canonical tx)>",
        "signedTxString": "<exact canonical tx bytes that were hashed & signed — RFC 8785 JSON for object txs, or the raw string for string txs>",
        "signature": "<base64url ES256 signature — see Signature specification below>"
      }
    }
  ]
}
```

None of these fields is the raw transaction. `meta.signedTx` is the **JWS payload** — `BASE64URL(SHA-256 hex digest of the canonical tx)`; `meta.signature` is the JWS signature over the **JWS Signing Input** `eyJhbGciOiJFUzI1NiJ9.<meta.signedTx>`; and `meta.signedTxString` is the exact byte string that was hashed and signed, provided so you can run the binding check by hashing it directly. The exact byte layout of each field is in the Signature specification below.

#### Transaction serialization

The signature is over a digest of the transaction's **canonical serialization**, which depends on the transaction shape:

* **Object transactions** (EVM, Cosmos, Tron, Starknet, TON, …) are serialized with **RFC 8785 — the JSON Canonicalization Scheme (JCS)**: object keys sorted by UTF-16 code unit, array order preserved, no insignificant whitespace. Unlike `JSON.stringify`, the output byte string is deterministic regardless of object property insertion order, so it can be reproduced byte-for-byte by any platform or language that implements JCS.
* **String transactions** (PSBT, base64, CBOR, and other pre-serialized formats) are the exact signable bytes already, so they are used **verbatim**, with no canonicalization.

> **Why this matters.** SwapKit previously serialized object transactions with `JSON.stringify`, which does not guarantee key ordering across languages/platforms and is therefore not safe to reproduce for the binding check. Signing now uses RFC 8785 so the hashed pre-image is deterministic and reproducible byte-for-byte. SwapKit also returns the exact pre-image in `meta.signedTxString`, so you never have to re-serialize the transaction yourself.

The exact serializer SwapKit uses is a standard RFC 8785 (JCS) implementation:

```typescript
/**
 * Serialize a JSON value to its RFC 8785 (JSON Canonicalization Scheme, JCS)
 * canonical form: object keys sorted by UTF-16 code unit, array order preserved,
 * and no insignificant whitespace. The output byte string is deterministic
 * regardless of object property insertion order, so it can be reproduced
 * byte-for-byte by any platform/language that implements JCS.
 *
 * BigInt and non-finite numbers are rejected rather than coerced (JCS/JSON have
 * no bigint type, and JSON.stringify would silently emit non-finite numbers as
 * null) — neither occurs in transaction data, where amounts are strings.
 * Properties whose value is `undefined` are omitted, matching JSON.stringify.
 */
export function canonicalizeJson(value: unknown): string {
  if (typeof value === "bigint") {
    throw new TypeError(
      `canonicalizeJson: cannot canonicalize a BigInt (${value}n) — tx amounts must be strings`,
    );
  }

  if (typeof value === "number" && !Number.isFinite(value)) {
    throw new TypeError(`canonicalizeJson: cannot canonicalize non-finite number (${value})`);
  }

  if (value === null || typeof value !== "object") {
    // Primitives: string/number/boolean. JSON.stringify produces the canonical
    // representation for strings (minimal escaping) and integers.
    return JSON.stringify(value) ?? "null";
  }

  if (Array.isArray(value)) {
    return `[${value.map((item) => canonicalizeJson(item)).join(",")}]`;
  }

  const entries = Object.entries(value as Record<string, unknown>)
    .filter(([, v]) => v !== undefined)
    .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));

  const members = entries.map(([key, v]) => `${JSON.stringify(key)}:${canonicalizeJson(v)}`);

  return `{${members.join(",")}}`;
}
```

In other languages, use a maintained JCS/RFC 8785 library (for example `canonicaljson` in Python, `gibson042/canonicaljson-go` in Go, `erdtman/java-json-canonicalization` in Java) rather than your language's default JSON encoder.

#### Verify the signature

Verification has **two independent checks** — both are required:

1. **Signature check** — the JWS signature is valid for the JWS Signing Input under your public key. This proves SwapKit produced the signature.
2. **Binding check** — the signed digest equals `SHA-256` of the canonical serialization of the `tx` you are about to broadcast. This proves the signature is bound to the transaction you will send, not some other transaction.

Skipping step 2 leaves you exposed: an attacker could leave a valid `signedTx`/`signature` pair untouched while swapping out `tx`, and a signature-only check would still pass.

> **Run the binding check against the `tx` you will broadcast.** `meta.signedTxString` is the exact pre-image SwapKit signed, so hashing it directly reproduces the signed digest with no re-serialization. That only proves those *bytes* are authentic, though — to bind the check to what you actually send, confirm the transaction you broadcast is the one `meta.signedTxString` represents. The trust-minimizing way to do that is to canonicalize your own `tx` and require it to equal `meta.signedTxString`; the example below does exactly that.

You do not need a SwapKit-specific SDK. The simplest correct approach is a **JWS/JOSE library**, because the signature is a Flattened JWS. A raw ES256 verifier also works if you reconstruct the JWS Signing Input and handle the signature encoding (see Recommended libraries).

**Signature specification**

| Field                            | Value                                                                                                                      |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Envelope                         | Flattened JWS JSON Serialization (RFC 7515 §7.2.2)                                                                         |
| Curve                            | `secp256r1` (also known as P-256 / prime256v1)                                                                             |
| Hash                             | `SHA-256`                                                                                                                  |
| Algorithm                        | ECDSA (ES256, RFC 7518)                                                                                                    |
| Public key format                | PEM, `SubjectPublicKeyInfo` (as delivered on activation)                                                                   |
| Protected header                 | `{"alg":"ES256"}` → base64url `eyJhbGciOiJFUzI1NiJ9`                                                                       |
| Signed bytes (JWS Signing Input) | `eyJhbGciOiJFUzI1NiJ9.<meta.signedTx>` (ASCII)                                                                             |
| Canonical tx string              | Object tx → **RFC 8785 (JCS)** JSON; string tx (PSBT, base64, CBOR, …) → used verbatim. Returned as `meta.signedTxString`. |
| `meta.signedTx` (JWS payload)    | `BASE64URL(SHA-256 hex digest of the canonical tx string)`                                                                 |
| `meta.signedTxString`            | The exact canonical tx string (signature pre-image). Hash it to reproduce the digest without re-serializing `tx`.          |
| Signature encoding               | **JOSE: raw `r \|\| s`, 64 bytes** (concatenated, fixed-width). **Not DER.**                                               |
| Signature transport encoding     | **base64url** (`meta.signature`) — not standard base64, not hex                                                            |

**TypeScript example using `jose` library**

```typescript
import * as jose from "jose";
import * as crypto from "crypto";
import { canonicalizeJson } from "./canonicalizeJson"; // the RFC 8785 serializer shown above

// Public key delivered when signing was activated for your API key.
const publicKeyPem = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...\n-----END PUBLIC KEY-----";

// A single route from a swap response
const route = {
  tx: {
    to: "0x569a904F8478c66fD495d2B4E8e272B6507feDB3",
    from: "0x569a904F8478c66fD495d2B4E8e272B6507feDB3",
    gas: "0x5208",
    gasPrice: "0x1805fa7",
    value: "2000000000000000",
    data: "0x",
  },
  meta: {
    signedTx: "NjFhNGM1ZTJmMWIzZC4uLg....",       // base64url(SHA-256 hex digest of the canonical tx)
    signedTxString: '{"data":"0x","from":"0x569a...","gas":"0x5208","gasPrice":"0x1805fa7","to":"0x569a...","value":"2000000000000000"}', // exact signed pre-image
    signature: "cceWPI6Ak-....",                  // base64url ES256 signature (raw r || s)
  },
};

async function verifyRoute(route: {
  tx: unknown;
  meta: { signedTx: string; signedTxString: string; signature: string };
}): Promise<void> {
  const { signedTx, signedTxString, signature } = route.meta;

  // Import the public key (PEM SubjectPublicKeyInfo).
  const ecPublicKey = await jose.importSPKI(publicKeyPem, "ES256");

  // 1) SIGNATURE CHECK — verify the Flattened JWS.
  //    jose reconstructs the signing input (eyJhbGciOiJFUzI1NiJ9.<signedTx>)
  //    and decodes the raw r || s signature internally.
  //    It throws on an invalid signature, wrong key, or unexpected algorithm.
  const { payload } = await jose.flattenedVerify(
    {
      payload: signedTx,
      signature,
      protected: Buffer.from(JSON.stringify({ alg: "ES256" })).toString("base64url"),
    },
    ecPublicKey,
  );

  // 2) BINDING CHECK — tie the tx you will broadcast to the signed pre-image.
  //    Canonicalize your OWN tx (RFC 8785 for objects, raw string for strings)
  //    and require it to equal the pre-image SwapKit signed. This is what makes
  //    the signature bind to the transaction you send — key order can never drift.
  const canonicalTx = typeof route.tx === "string" ? route.tx : canonicalizeJson(route.tx);
  if (canonicalTx !== signedTxString) {
    throw new Error("Transaction does not match the signed pre-image");
  }

  //    The verified JWS payload is the base64url-decoded signedTx, i.e. the hex
  //    digest string. Recompute it over the canonical tx and compare.
  const expectedDigest = crypto.createHash("sha256").update(Buffer.from(canonicalTx)).digest("hex");
  const signedDigest = Buffer.from(payload).toString("utf8");
  if (signedDigest !== expectedDigest) {
    throw new Error("Digest mismatch — transaction does not match the signed payload");
  }

  // Both checks passed — the tx is authentic and unmodified. Safe to broadcast.
}

await verifyRoute(route);
```

> If you cannot implement RFC 8785 canonicalization in your language, you may hash `meta.signedTxString` directly for the digest — but you must then broadcast the transaction that string represents (parse `meta.signedTxString`), not a separately-held `tx`, or the binding no longer covers what you send.

**Recommended libraries**

The signature is a **Flattened JWS** with a `r || s` (non-DER) signature, base64url-encoded. Pick one of two paths:

**Path A — JWS/JOSE library (recommended).** Hand it the protected header `{"alg":"ES256"}`, `meta.signedTx` as the payload, `meta.signature`, and your public key. It reconstructs the signing input and handles the `r || s` encoding for you.

* **Node.js / Browser** — [`jose`](https://github.com/panva/jose): `jose.flattenedVerify({ protected, payload, signature }, key)`. This is what SwapKit uses; see the example above.
* **Python** — [`jwcrypto`](https://jwcrypto.readthedocs.io/) or [`joserfc`](https://jose.authlib.org/en/) (both actively maintained). Avoid `python-jose` — it is effectively unmaintained and has had CVEs.
* **Go** — [`go-jose`](https://github.com/go-jose/go-jose) (`jose.ParseSigned` / `JSONWebSignature.Verify`)
* **Java** — [`nimbus-jose-jwt`](https://connect2id.com/products/nimbus-jose-jwt) (`JWSObject` / `ECDSAVerifier`)
* **Rust** — [`josekit`](https://docs.rs/josekit/). (Note: the `jsonwebtoken` crate only handles compact JWT, not arbitrary Flattened JWS payloads.)

**Path B — raw ES256 verifier.** If you use a generic ECDSA-P256 verifier instead, you must: (a) build the signing input string `eyJhbGciOiJFUzI1NiJ9.<meta.signedTx>` and pass its **UTF-8 bytes** as the data; (b) base64url-decode `meta.signature` to the raw 64-byte `r || s`; and (c) match the signature format your verifier expects. The verifier applies `SHA-256` to the data itself.

| Verifier                                                                                                                                       | Signature format expected                               | Conversion from base64url `r \|\| s`                                                          |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [Node `crypto.verify`](https://nodejs.org/api/crypto.html#cryptoverifyalgorithm-data-key-signature-callback)                                   | raw `r \|\| s` via `{ key, dsaEncoding: "ieee-p1363" }` | none (default `"der"` would reject it)                                                        |
| [WebCrypto `SubtleCrypto.verify`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify) (`{ name: "ECDSA", hash: "SHA-256" }`) | raw `r \|\| s`                                          | none                                                                                          |
| [Python `cryptography`](https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/)                                                    | DER                                                     | `encode_dss_signature(r, s)`, then `public_key.verify(der, input, ec.ECDSA(hashes.SHA256()))` |
| [Go `crypto/ecdsa`](https://pkg.go.dev/crypto/ecdsa)                                                                                           | two big integers                                        | split `r`/`s`, `ecdsa.Verify(pub, sha256.Sum256(input)[:], r, s)` (or DER + `VerifyASN1`)     |
| [Java `Signature` `SHA256withECDSA`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/Signature.html)                | DER                                                     | convert `r \|\| s` to DER first                                                               |

Whichever path you choose, the binding check (recomputing the digest from the canonical `tx` and comparing to `meta.signedTx`) is the same and still required.

#### What to do on verification failure

If either check fails:

* **Do not** broadcast the transaction.
* **Do not** retry against a different endpoint or relax the check.
* Treat the response as untrusted and surface the error to the caller or log it for investigation.

A failure means the response did not come from SwapKit, was modified in transit, or the integration is using a stale public key after a rotation.

***

#### FAQ

**Why do I have to recompute the digest if the signature already verifies?** Because the signature only proves the digest is authentic. Without comparing the signed digest to the digest of *your* `tx`, a tampered transaction with an intact (but unrelated) `signedTx`/`signature` pair would pass the signature check.

**Does signing work for tokens that aren't in SLIP-0044?** Yes. The ES256 signature is over a digest of the raw transaction payload SwapKit returns. It is independent of any token registry — there is no SLIP-0044 lookup involved, and token coverage is not limited by it.

**Can I have multiple key pairs per API key?** No. One key pair per API key — which also means an API key is provisioned for either ES256 transaction payload signing or SLIP-0024 hardware-wallet signing, never both. To rotate, request a rotation through your SwapKit point of contact.


# SLIP-0024 Transaction payload signing

Including SLIP-0024 to your SwapKit integration

### Overview

When a hardware wallet receives transaction data from a third-party API, there's no built-in way to verify the data hasn't been tampered with in transit. A man-in-the-middle attacker could alter addresses or amounts before they reach the signing device, and the wallet would have no way to detect it.

SwapKit's `/v3/swap` endpoint now supports [SLIP-0024](https://github.com/satoshilabs/slips/blob/master/slip-0024.md) signed payloads to solve this problem. SLIP-0024 is a SatoshiLabs standard that defines a secure payment request format originally designed for hardware wallets like Trezor and BitBox. When activated for your integration, the `/v3/swap` response includes a `slip24` object containing a cryptographically signed representation of the transaction. Your device or application can verify this signature using SwapKit's public key, ensuring the payload is authentic and unmodified.

### How It Works

**Signature Flow**

1. Your application calls `/v3/swap` as normal (see [Quote and Swap Implementation Flow](https://docs.swapkit.dev/swapkit-api/quote-and-swap-implementation-flow)).
2. SwapKit builds the transaction (`tx`) and generates the `slip24` object.
3. The `slip24` payload is serialized to a buffer using the configured encoding scheme, then signed with SwapKit's private key.
4. The response includes both the standard `tx` field and the `slip24` object with its `signature`.
5. Your application or hardware wallet firmware verifies the signature against SwapKit's public key. If valid, the device can display a human-readable confirmation to the user (e.g., "Send 0.00298567 BTC to SWAPKIT (NEAR)") instead of raw addresses.

**Key Management**

* SwapKit generates a dedicated key pair for each integrator. The **private key** is stored encrypted, only the production service can decrypt it. Even SwapKit developers cannot access the plaintext key.
* The **public key** is shared privately with the integrator during onboarding. It is not sensitive and is what your application uses to verify signatures.
* Supported key pair algorithms: **ES256** and **secp256k1**.

### Signature Schemes

Three signature schemes are available. The scheme is configured per integration during onboarding. The default is `basic`.

* **basic (raw): t**he `tx` property from the `/v3/swap` response is converted directly to a buffer and signed. This is the default scheme.
* **slip24\_hex: t**he `tx` property is converted to its SLIP-0024 representation, serialized into a **hex-encoded** buffer, and then signed.
* **slip24\_base64: s**ame as `slip24_hex`, but the SLIP-0024 representation is serialized into a **base64-encoded** buffer before signing.

| Scheme          | Payload Source                   | Encoding   | Default |
| --------------- | -------------------------------- | ---------- | ------- |
| `basic` / `raw` | `tx` field directly              | raw buffer | Yes     |
| `slip24_hex`    | SLIP-0024 representation of `tx` | hex        | No      |
| `slip24_base64` | SLIP-0024 representation of `tx` | base64     | No      |

### Response Payload

**The slip24 Object**

When SLIP-0024 signing is active for your API key, the `/v3/swap` response includes a `slip24` field alongside the standard fields documented in the [/v3/swap endpoint reference](https://docs.swapkit.dev/swapkit-api/v3-swap-obtain-swap-transaction-details).

```json
{
  "slip24": {
    "recipientName": "SWAPKIT (NEAR)",
    "nonce": null,
    "memos": [
      {
        "type": "coinPurchase",
        "coinPurchase": {
          "coinType": 0,
          "amount": "0.00298567 BTC",
          "address": "bc1qa2gkyk5xx8v706fesud0fd0skf8dvtjz45fmxn"
        }
      }
    ],
    "outputs": [
      {
        "amount": 10000000000000000,
        "address": "0xEC2F9Cd3E2aFbaeb83f959382584aE2b8BB66Ac3"
      }
    ],
    "signature": "Qz1+JXGa7L4YPZLxCmGeILFpx1VewPLbHD9tlxERjrJuSEHw5QuNWYkxRv6CFomIplFZ3pvEjbKINVBXFXMlYg=="
  },
  "swapId": "62fbdbf5-bc8f-445b-920a-9b5c4a778606"
}
```

**Field Reference**

* **`recipientName`**: Human-readable name displayed on the hardware wallet screen (e.g., `"SWAPKIT (NEAR)"`). Identifies the service and destination chain.
* **`nonce`**: Reserved for replay protection. Currently `null`.
* **`memos`**: Array of memo objects describing the transaction. Each has a **`type`** and a corresponding data object.
  * **`coinPurchase.coinType`**: SLIP-0044 coin type identifier (e.g., `0` for Bitcoin).
  * **`coinPurchase.amount`**: Human-readable amount string (e.g., `"0.00298567 BTC"`).
  * **`coinPurchase.address`**: Destination address for the purchased asset.
* **`outputs`**: Array of transaction outputs with raw **`amount`** (in smallest denomination) and **`address`**.
* **`signature`**: Cryptographic signature over the SLIP-0024 payload. Verify this against SwapKit's public key using the configured algorithm.

### Verification

From the integrator's perspective, the verification flow is:

1. Extract the `slip24` object from the `/v3/swap` response.
2. Serialize the payload (excluding the `signature` field itself) using the agreed-upon encoding scheme.
3. Verify the `signature` against the serialized buffer using SwapKit's public key and the configured algorithm (ES256 or secp256k1).
4. If verification passes, the transaction data is authentic. The hardware wallet can safely display the human-readable details (recipient name, amount, and address) to the user for confirmation.
5. If verification fails, reject the transaction. The payload may have been tampered with.

### Best Practices

* **Always verify before displaying**: Never show transaction details to the user before the signature has been validated.
* **Pin the public key**: Store SwapKit's public key securely in your application or firmware. Don't fetch it dynamically at verification time.
* **Handle verification failures gracefully**: If signature verification fails, block the transaction and surface a clear error to the user. Do not fall back to unsigned mode silently.
* **Use `recipientName` for UX**: The `recipientName` field is designed for display on constrained hardware wallet screens. Show it alongside the amount to give users a clear confirmation prompt.
* **Stay coordinated on key changes**: If SwapKit rotates keys or adds new signature schemes, you'll be notified through the onboarding relationship. Ensure your firmware update pipeline can accommodate key changes.

### Getting Started

SLIP-0024 signing is not self-service. To activate it for your integration, contact SwapKit directly. During onboarding, SwapKit will:

* Generate a dedicated key pair for your integration
* Share the public key privately
* Configure your preferred signature scheme (`basic`, `slip24_hex`, or `slip24_base64`) and algorithm (`ES256` or `secp256k1`)

SwapKit is open to working with any hardware wallet manufacturer or payment processor that needs cryptographic verification of transaction payloads.

***

For questions or to begin integration, reach out to SwapKit directly through [the partnership portal](https://partnerships.swapkit.dev) or [Discord](https://discord.gg/swapkit).


# Transaction Formats by txType

Reference examples of the transaction payload SwapKit returns for each txType.

Every `/v3/quote` and `/v3/swap` route that includes a transaction returns two related fields:

* **`tx`** — the transaction payload to sign and broadcast.
* **`txType`** — a tag identifying the payload's format, so you know how to decode and sign it.

The **shape** of `tx` depends on `txType`. Some chains return a JSON **object** (EVM, Cosmos, Tron); others return an already-serialized **string** (Bitcoin PSBT, Solana, Ripple, NEAR, Sui, Cardano, Zcash). This page catalogs the exact format for each `txType` with a real example response.

> For how these payloads are signed and how to verify the signature, see [Transaction Payload Signing](https://docs.swapkit.dev/spotlights/transaction-payload-signing). Object `tx` types are canonicalized with RFC 8785 (JCS) before hashing; string `tx` types are hashed verbatim.

### How to reproduce an example

To fill in a section below, run one real swap on that chain and paste the response:

1. Call **`/v3/quote`** with a `sellAsset` on the target chain and a funded `sourceAddress`, then pick a route.
2. Request the transaction using that route's **`nextActions`** entry — it gives you the `method`, `url`, and `payload` to send, so you don't build the `/swap` call yourself.
3. From the response, copy the route's `txType`, `tx`, and — if signing is enabled — `meta.signedTx` / `meta.signature` / `meta.signedTxString` into the matching section.

See the [/v3/quote](https://docs.swapkit.dev/swapkit-api/v3-quote-request-a-swap-quote) and [/v3/swap](https://docs.swapkit.dev/swapkit-api/v3-swap-obtain-swap-transaction-details) docs for the full request/response contract.

***

## Object `tx` types

These return `tx` as a JSON object. The signed pre-image is the RFC 8785 (JCS) canonical JSON of the object — see [Transaction Payload Signing](https://docs.swapkit.dev/spotlights/transaction-payload-signing).

### EVM

* **`txType`:** `EVM`
* **`tx` shape:** object (`{ from, to, data, value, gas, gasPrice, ... }`)
* **Chains:** Ethereum, BSC, Avalanche, Arbitrum, Base, Polygon
* **Example sell asset:** `ETH.ETH`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "EVM",
  "tx": {
    "to": "0xRECIPIENT_ADDRESS",
    "from": "0xYOUR_SOURCE_ADDRESS",
    "gas": "0x6270",
    "gasPrice": "0x7ecbe4e",
    "value": "0x2386f26fc10000",
    "data": "0x"
  },
  "meta": {
    "txType": "EVM",
    "signedTxString": "{\"data\":\"0x\",\"from\":\"0xYOUR_SOURCE_ADDRESS\",\"gas\":\"0x6270\",\"gasPrice\":\"0x7ecbe4e\",\"to\":\"0xRECIPIENT_ADDRESS\",\"value\":\"0x2386f26fc10000\"}",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `meta.signedTxString` has the keys in **alphabetical** order (`data, from, gas, gasPrice, to, value`) — not the order they appear in `tx`. That's the RFC 8785 canonical form.

### COSMOS

* **`txType`:** `COSMOS`
* **`tx` shape:** object (Cosmos SDK transaction)
* **Chains:** Cosmos Hub (GAIA), and other Cosmos SDK chains
* **Example sell asset:** `GAIA.ATOM`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "COSMOS",
  "tx": {
    "memo": "=:e:0xYOUR_DEST:659671/1/0:-_/nc:15/0",
    "accountNumber": 3695266,
    "sequence": 0,
    "chainId": "cosmoshub-4",
    "msgs": [
      {
        "typeUrl": "/cosmos.bank.v1beta1.MsgSend",
        "value": {
          "amount": [{ "amount": "10000000", "denom": "uatom" }],
          "fromAddress": "cosmos1YOUR_SOURCE",
          "toAddress": "cosmos1VAULT"
        }
      }
    ],
    "fee": { "amount": [{ "denom": "uatom", "amount": "5000" }], "gas": "200000" }
  },
  "meta": {
    "txType": "COSMOS",
    "signedTxString": "{\"accountNumber\":3695266,\"chainId\":\"cosmoshub-4\",\"fee\":{\"amount\":[{\"amount\":\"5000\",\"denom\":\"uatom\"}],\"gas\":\"200000\"},\"memo\":\"=:e:0xYOUR_DEST:659671/1/0:-_/nc:15/0\",\"msgs\":[{\"typeUrl\":\"/cosmos.bank.v1beta1.MsgSend\",\"value\":{\"amount\":[{\"amount\":\"10000000\",\"denom\":\"uatom\"}],\"fromAddress\":\"cosmos1YOUR_SOURCE\",\"toAddress\":\"cosmos1VAULT\"}}],\"sequence\":0}",
    "signedTx": "<base64url digest>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a JSON **object**, canonicalized with **RFC 8785 (JCS)** before hashing — keys sorted **recursively** (nested objects too). Reproduce with a JCS canonicalizer, or just hash `meta.signedTxString`

### TRON

* **`txType`:** `TRON`
* **`tx` shape:** object (Tron transaction; may be an EVM-style object for TRC-20/contract calls)
* **Chains:** Tron
* **Example sell asset:** `TRON.TRX`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "TRON",
  "tx": {
    "visible": true,
    "txID": "0f29b0bced8061478386652a4b07c0c8654561a502daf67ad83039db32c7d1f1",
    "raw_data": {
      "contract": [
        {
          "parameter": {
            "value": {
              "owner_address": "TYOUR_SOURCE_ADDRESS",
              "to_address": "TVAULT_ADDRESS",
              "amount": 55000000
            },
            "type_url": "type.googleapis.com/protocol.TransferContract"
          },
          "type": "TransferContract"
        }
      ],
      "ref_block_bytes": "f952",
      "ref_block_hash": "4b675bed33dccae0",
      "expiration": 1785273270000,
      "timestamp": 1785272972258,
      "data": "3d3a653a…2f30"
    },
    "raw_data_hex": "0a02f95222084b675bed33dccae0…e2e7bed4fa33"
  },
  "meta": {
    "txType": "TRON",
    "signedTxString": "{\"raw_data\":{\"contract\":[{\"parameter\":{\"type_url\":\"type.googleapis.com/protocol.TransferContract\",\"value\":{\"amount\":55000000,\"owner_address\":\"TYOUR_SOURCE_ADDRESS\",\"to_address\":\"TVAULT_ADDRESS\"}},\"type\":\"TransferContract\"}],\"data\":\"3d3a653a…2f30\",\"expiration\":1785273270000,\"ref_block_bytes\":\"f952\",\"ref_block_hash\":\"4b675bed33dccae0\",\"timestamp\":1785272972258},\"raw_data_hex\":\"0a02f952…bed4fa33\",\"txID\":\"0f29…d1f1\",\"visible\":true}",
    "signedTx": "<base64url digest>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a JSON **object** (native Tron format: `visible, txID, raw_data, raw_data_hex`), canonicalized with **RFC 8785 (JCS)** — keys sorted recursively (top-level → `raw_data, raw_data_hex, txID, visible`; inside `raw_data` → `contract, data, expiration, ref_block_bytes, ref_block_hash, timestamp`; etc.).

***

## String `tx` types

These return `tx` as an already-serialized string. The string is the exact signable bytes and is hashed verbatim (no canonicalization) — see [Transaction Payload Signing](https://docs.swapkit.dev/spotlights/transaction-payload-signing).

### PSBT

* **`txType`:** `PSBT`
* **`tx` shape:** string (base64-encoded PSBT)
* **Chains:** Bitcoin, Litecoin, Dogecoin, Bitcoin Cash, Dash
* **Example sell asset:** `BTC.BTC`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "PSBT",
  "tx": "cHNidP8BAKQCAAAAAyFxJZDlxNYA3/36dLs5h2/…BcYepUBX5AAA",
  "meta": {
    "txType": "PSBT",
    "signedTxString": "cHNidP8BAKQCAAAAAyFxJZDlxNYA3/36dLs5h2/…BcYepUBX5AAA",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: For a `PSBT` (string) tx, `meta.signedTxString` is **identical to `tx`** — string txs are hashed verbatim, not canonicalized.

### SOLANA

* **`txType`:** `SERIALIZED_BASE64`
* **`tx` shape:** string (base64-serialized Solana transaction)
* **Chains:** Solana
* **Example sell asset:** `SOL.SOL`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "SERIALIZED_BASE64",
  "tx": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…AwEGBAICAA==",
  "meta": {
    "txType": "SERIALIZED_BASE64",
    "signedTxString": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…AwEGBAICAA==",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a base64-serialized Solana transaction — a **string**, hashed **verbatim** (`signedTxString === tx`)

### RIPPLE

* **`txType`:** `RIPPLE`
* **`tx` shape:** string
* **Chains:** XRP Ledger
* **Example sell asset:** `XRP.XRP`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "RIPPLE",
  "tx": "{\"Account\":\"rYOUR_XRP_ADDRESS\",\"Amount\":\"10000000\",\"Destination\":\"rVAULT_ADDRESS\",\"TransactionType\":\"Payment\",\"Memos\":[{\"Memo\":{\"MemoData\":\"<hex-encoded THORChain memo>\"}}],\"Flags\":0,\"Sequence\":105916205,\"Fee\":\"12\",\"LastLedgerSequence\":105916293}",
  "meta": {
    "txType": "RIPPLE",
    "signedTxString": "<identical to tx>",
    "signedTx": "<base64url digest>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a **JSON string** (an XRPL Payment serialized with `JSON.stringify`). Even though it *contains* JSON, `RIPPLE` is a **string** `txType` — hashed **verbatim**. Do **not** `JSON.parse` + re-stringify it and do **not** canonicalize it: the keys are in XRPL order (`Account, Amount, Destination,...`), and any re-serialization changes the bytes and breaks verification. Hash the raw string exactly as received (`meta.signedTxString`, which equals `tx`).

### NEAR

* **`txType`:** `NEAR`
* **`tx` shape:** string
* **Chains:** NEAR
* **Example sell asset:** `NEAR.NEAR`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "NEAR",
  "tx": "QAAAAGY5ZDEwZjY3YzE1YTU5YTI2MDIzNzNkYjZlMmRmZDEx…QLzQGAAAAAAA=",
  "meta": {
    "txType": "NEAR",
    "signedTxString": "QAAAAGY5ZDEwZjY3YzE1YTU5YTI2MDIzNzNkYjZlMmRmZDEx…QLzQGAAAAAAA=",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a serialized NEAR transaction (a **string**, hashed **verbatim**: `signedTxString === tx`). String type → no canonicalization; binding check is `sha256(signedTxString)`.

### SUI

* **`txType`:** `SUI`
* **`tx` shape:** string
* **Chains:** Sui
* **Example sell asset:** `SUI.SUI`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "SUI",
  "tx": "AAACAAgAuh3SBQAAAAAgKbUATN1/dlfYgu135boj9uTDvIhgZ/V1lsp0J7yK5J4CAg…gPD6AgAAAAAA",
  "meta": {
    "txType": "SUI",
    "signedTxString": "AAACAAgAuh3SBQAAAAAgKbUATN1/dlfYgu135boj9uTDvIhgZ/V1lsp0J7yK5J4CAg…gPD6AgAAAAAA",
    "signedTx": "<base64url digest>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is base64 Sui transaction bytes (`txBytes.toBase64()`) — a **string**, hashed **verbatim** (`signedTxString === tx`). No canonicalization.

### CARDANO

* **`txType`:** `CBOR`
* **`tx` shape:** string (CBOR-encoded transaction)
* **Chains:** Cardano
* **Example sell asset:** `ADA.ADA`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "CBOR",
  "tx": "84a40081825820bdfd6582d231bc8f4db1a2973d98159dda73e719461bff1a0edc7ef64bf3d62d00…031a0b8bccd1a0f5f6",
  "meta": {
    "txType": "CBOR",
    "signedTxString": "84a40081825820bdfd6582d231bc8f4db1a2973d98159dda73e719461bff1a0edc7ef64bf3d62d00…031a0b8bccd1a0f5f6",
    "signedTx": "<base64url digest>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is an unsigned Cardano transaction as a **CBOR hex string** — hashed **verbatim** (`signedTxString === tx`). No canonicalization.

### STELLAR

* **`txType`:** `STELLAR`
* **`tx` shape:** string (base64 XDR)
* **Chains:** Stellar
* **Example sell asset:** `XLM.XLM`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "STELLAR",
  "tx": "AAAAAgAAAABJsaHU9Ev9qTHMGCvskZX0JUWmI+brCIUIPhCqEsbi5w…AAAAAAA==",
  "meta": {
    "txType": "STELLAR",
    "signedTxString": "AAAAAgAAAABJsaHU9Ev9qTHMGCvskZX0JUWmI+brCIUIPhCqEsbi5w…AAAAAAA==",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a base64 **XDR**-encoded Stellar transaction envelope (a **string**, hashed **verbatim**: `signedTxString === tx`). String type → no canonicalization; binding check is `sha256(signedTxString)`.

### Zcash (unsigned)

* **`txType`:** `zcash-unsigned`
* **`tx` shape:** string
* **Chains:** Zcash
* **Example sell asset:** `ZEC.ZEC`

**Example response (`tx` + `meta`)**

```json
{
  "txType": "zcash-unsigned",
  "tx": "UENaVAEAAAAFis6ctQLbrJS9AwEA8t7RAY…AAAA==",
  "meta": {
    "txType": "zcash-unsigned",
    "signedTxString": "UENaVAEAAAAFis6ctQLbrJS9AwEA8t7RAY…AAAA==",
    "signedTx": "<base64url(SHA-256 hex of signedTxString)>",
    "signature": "<base64url ES256 signature>"
  }
}
```

Notes: `tx` is a base64 **PCZT** (Partially Created Zcash Transaction — the **shielded** path; transparent ZEC returns `PSBT`). A **string**, hashed **verbatim**.


# API Migration to v3

Migrate your v2 implementation to our API v3 endpoints

### Overview

SwapKit v3 API presents a new flow that clearly separates responsibilities into 2 distinct services. This simplifies the integration and introduces a more deterministic\
developer experience, and as a result of reduced computation, latency has been lowered.

On top of that, SwapKit’s fees are reduced from 0.20% on swaps of all sizes to:

* 0.15% on swaps between $0 and $500k.
* 0.12% on swaps between $500k and $1m.
* 0.10% on swaps +$1m.
* 0.01% on stable <> stable swaps.

Previously, the `includeTx` request parameter would be used to signal building of transactions. This created an unclear separation of concerns and made it difficult for developers to create a clear flow which clearly separates retrieving quotes from the market, and building a transaction for a user's chosen route.

#### The New v3 Flow

v3 introduces a clear two-step process:

1. [`/v3/quote`](/swapkit-api/v3-quote-request-a-swap-quote) - Price discovery only. Returns available routes with expected amounts, fees, and execution times. Quotes are cached for 5 minutes.
2. [`/v3/swap`](/swapkit-api/v3-swap-obtain-swap-transaction-details) - Transaction execution. Takes a routeId from the quote and builds a ready-to-broadcast transaction.

This two-step process is also detailed in the [quote and swap implementation flow](/swapkit-api/quote-and-swap-implementation-flow). There are no changes to the other SwapKit endpoints.\
Migrating an existing v2 integration to v3 is a matter of separating the use of `"includeTx"` into calling two separate endpoints instead, and adapting to fetch the appropiate route identification `routeId` .

### Changes to the /quote endpoint

The v2 version of the `/quote` endpoint required passing the addresses involved to request a quote and setting the `"includeTx` flag on `true` or `false` to request swap data.

This is no longer necessary, we can compare two example payloads:

{% tabs %}
{% tab title="v3 Quote" %}

```json
{
  "sellAsset": "ETH.ETH",
  "buyAsset": "BTC.BTC",
  "sellAmount": "2",
  "slippage": 3
}
```

{% endtab %}

{% tab title="v2 Quote" %}

```json
{
  "sellAsset": "ETH.ETH",
  "buyAsset": "BTC.BTC",
  "sellAmount": "2",
  "sourceAddress": "0x0a4c79cE84202b03e95B7a692E5D728d83C44c76",
  "destinationAddress": "168w95Wf8tphgM7DPREXr8zLnVoE2wJCAN",
  "slippage": 3,
  "includeTx": false
}
```

{% endtab %}
{% endtabs %}

The data in the response is similar, except that you will not find transaction details here until you use `/v3/swap` to request them.\
Instead, each offered route is identified by a `routeId` string that can be used to request swap details and `"nextActions"` data in case the user needs to.

### The new /swap endpoint

The `/v3/swap/` or just `/swap` endpoint is called as a follow up to a chosen route from a `/v3/quote` response.\
The addresses are added in this step, and they will be checked for AML compliance.

The new `disableBuildTx` can be used in case you want to build the transaction yourself. Otherwise, we will provide a transaction ready to sign, similar to the v2 flow with `"includeTx": true` .

Without any optional parameters, it is just filling the missing information from a quote:

* Select a route
* Provide `sourceAddress` and `destinationAddress` .

{% tabs %}
{% tab title="v3 Swap" %}

```json
{
  "routeId": "5ea8cd3a-05ae-4737-b223-dbcd4c7a6493",
  "sourceAddress": "0x0a4c79cE84202b03e95B7a692E5D728d83C44c76",
  "destinationAddress": "168w95Wf8tphgM7DPREXr8zLnVoE2wJCAN"
}
```

{% endtab %}
{% endtabs %}

The response will include the transaction information to perform a swap. This informationmatches what v2 returned when `"includeTx": true` was included.\
As an example, for an EVM sell chain it could be something like this. In this case, this is a simple transfer without any contract being involved:

```json
"tx": {
        "to": "0x4e6960159d85254cb8e88483793f6fa8e9a49a4c",
        "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "gas": "0x5208",
        "gasPrice": "0x9129260",
        "value": "3000000000000000000",
        "data": "0x"
    },
```


# Chain-specific guides

Integration requirements and quirks for chains that need special handling beyond the standard swap flow.

### Overview

Some chains need more than the standard `/v3/quote` → `/v3/swap` flow to integrate correctly: extra address formats, memo rules, token types, or provider-specific routing. The guides below cover what's different on a per-chain basis so your integration handles each one properly.


# Near Chain and its assets

SwapKit provides comprehensive support for NEAR Chain, enabling seamless swaps for both native NEAR tokens and NEP-141 tokens.

### Supported Token Types

SwapKit's NEAR integration supports two primary token types:

#### Native NEAR Token

The native NEAR protocol token used for transaction fees, staking, and governance. In SwapKit, this is represented as `NEAR.NEAR`.

#### NEP-141 Tokens

NEAR's fungible token standard, equivalent to Ethereum's ERC-20. This includes:

* Wrapped NEAR (wNEAR) for DeFi interactions
* Bridged assets from other chains (ETH, wBTC, USDC, USDT)
* Native NEAR ecosystem tokens (DeFi, meme tokens, and project tokens)
* Tokens created through platforms like Meme Cooking

### Fetching Available NEAR Assets

#### Using the Provider Endpoint

To retrieve the complete list of tradeable NEAR assets, use the `/tokens` endpoint with a `GET` request to `https://api.swapkit.dev/tokens?provider=NEAR` You can see the `/tokens` endpoint [explained in detail here](/swapkit-api/tokens-list-and-search-supported-tokens).

Each token in the response includes:

* **identifier**: The SwapKit asset identifier (e.g., `NEAR.NEAR`, `NEAR.wNEAR-wrap.near`)
* **address**: The NEAR contract address (empty string for native NEAR)
* **decimals**: Token precision
* **symbol/ticker**: Token symbols
* **logoURI**: Token logo for UI display
* **extensions**: Additional metadata including the NEP-141 provider ID

#### Token Identification

NEAR tokens follow SwapKit's standard identifier format:

* **Native NEAR**: `NEAR.NEAR`
* **NEP-141 Tokens**: `NEAR.[TICKER]-[CONTRACT_ADDRESS]`
  * Example: `NEAR.wNEAR-wrap.near`
  * Example: `NEAR.USDC-17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1`

### Executing Swaps

#### Understanding NEAR wallet addresses

NEAR supports two address formats:

* **Implicit accounts**: 64-character hexadecimal addresses (like the example above)
* **Named accounts**: Human-readable names (e.g., `alice.near`)

Both formats are accepted in SwapKit's API.

#### Getting a Quote

Use the `/quote` [endpoint](/swapkit-api/v3-quote-request-a-swap-quote) to retrieve swap rates and routing information like you would for any other token. It can be a cross-chain swap or a single-chain Near Chain swap.

Example request payload - NEAR to AVAX:

```json
{
  "sellAsset": "NEAR.NEAR",
  "buyAsset": "AVAX.AVAX",
  "sellAmount": "10",
  "sourceAddress": "5c33c6218d47e00ef229f60da78d0897e1ee9665312550b8afd5f9c7bc6957d2",
  "destinationAddress": "0x0a4c79cE84202b03e95B7a692E5D728d83C44c76",
  "slippage": 3
}
```

#### Quote Response

The [quote response](broken://pages/WOr0HpWqgpVHmIid64yv) will include all the data you find in any other SwapKit quote. When using the `"includeTx": True` flag, the API also returns the necessary transaction payload that you can build the transaction with.

#### Executing the Swap

Swaps originating on Near Chain use the simplified deposit format that Near uses for any other chain. Tokens are transfered to an address without the need to include transaction data or to approve the tokens beforehand.

### Best Practices

#### Address Validation

Verify addresses before submitting:

* NEAR implicit addresses are 64 hexadecimal characters
* Named accounts end in `.near`, `.tg` or other registered TLD
* Destination addresses must be valid for the target chain

#### Token Decimals

NEAR uses 24 decimals for its native token, while NEP-141 tokens may use different decimal places. Always check the `decimals` field from the provider endpoint and adjust amounts accordingly.

#### Amount Formatting

When specifying `sellAmount`, use the base unit value:

* For 10 NEAR: Use "10" (not "10000000000000000000000000")
* SwapKit handles decimal conversion based on token metadata

### Common Use Cases

#### Acquiring NEAR

Swap from any supported asset to NEAR for:

* Paying transaction fees
* Staking with validators
* Participating in NEAR governance
* Using NEAR dApps

#### Trading NEP-141 Tokens

Access NEAR's DeFi ecosystem by swapping for:

* Wrapped assets (wNEAR, wBTC, ETH)
* Stablecoins (USDC, USDT)
* Ecosystem tokens (AURORA, Meta Pool, SWEAT)

### Error Handling

Common errors:

* Verify your NEAR address follows implicit (hex) or named account format
* Ensure you have enough NEAR/NEP-141 tokens plus gas fees
* Verify the token identifier matches the provider endpoint format

### Additional Resources

* NEAR Protocol Documentation: <https://docs.near.org>
* NEP-141 Token Standard: <https://nomicon.io/Standards/Tokens/FungibleToken>
* NEAR Wallets: <https://wallet.near.org/>
* NEAR Explorer: <https://nearblocks.io>

***

For support or questions about NEAR integration don't hesitate to contact us directly.


# Zcash shielded & unified addresses

Integration requirements for Zcash unified/shielded addresses — accepted formats, memo rules, and MAYAChain-only routing.

SwapKit supports **Zcash unified/shielded addresses** (`u1…`) as a swap source and destination, in addition to transparent addresses. Shielded deposits hide the sender, so the flow differs from a normal swap in a few important ways covered here. Unified Zcash swaps are routed through **MAYAChain only**.

### Supported address formats

| Format            | Prefix        | Supported | Notes                                              |
| ----------------- | ------------- | --------- | -------------------------------------------------- |
| Transparent       | `t1…` / `t3…` | Yes       | P2PKH / P2SH. Standard transparent flow.           |
| Unified (Orchard) | `u1…`         | Yes       | ZIP-316 unified address. Treated as shielded.      |
| Sapling           | `zs…`         | No        | Not supported — reject client-side to avoid a 400. |

### Shielded deposit flow

When the **source** is a shielded Zcash address (`u1…`), SwapKit **does not build the transaction**, skips the balance check, and skips address screening. The `/v3/swap` response returns the `memo`, the deposit `inboundAddress`, and a `shieldedMemo` object — your wallet builds and broadcasts the deposit itself.

You must, **in the same transaction**:

1. Send the value output to `inboundAddress`.
2. Send a **0-value shielded note** carrying the `memo` to `shieldedMemo.unifiedAddress`.

`shieldedMemo.uivk` (optional) is MAYAChain's unified incoming viewing key, used to decrypt the memo note.

For example, from a /swap response:

```json
{
  "routeId": "4af9fa2e-6f1f-4475-b326-1f538adba657",
  "providers": ["MAYACHAIN"],
  "sellAsset": "ZEC.ZEC",
  "sellAmount": "0.02",                         // decimal ZEC — convert to zats for the tx
  "buyAsset": "BTC.BTC",
  "expectedBuyAmount": "0.00013088",
  "expectedBuyAmountMaxSlippage": "0.00012876",
  "sourceAddress": "u1j0dx09lc9007ntxcxc00mkm4xcrkn9hmadk6uwgky3e208maktgkgdq887hnshlhuy8ljvdunkzxayshd6t99y78ze8865h80uml20ur630y3w9mxa7e6tll2pprqhh2q2cvr9szre88",
  "destinationAddress": "bc1qdddfwkvqs0x74ues8pcrcp5sz03g8a2kqcx75t",

  // ── the three fields you need to build the deposit ──────────────────────────
  "inboundAddress": "t1H49fQdV8eAweCugmPoL28oppuLRcnv95C",
  "shieldedMemo": {
    "unifiedAddress": "u1m76w6hk7f3gqn3w2pgsvrn7ckedd3mtyxu6kugeyqeag6zqypjt8nvw7mnwzu80rrs2dahv8kz0py6y34zsecr8vhngzzjgq9djux8dkakcj33h4ta2g4lqg27470ppztalnajg08e3e",
    "uivk": "uivk1exampleviewingkeydonotusethisisadummyplaceholdervalueforillustrationonly000000000000000000000000000000000000000000000000000000000000000000000000"
  },
  "memo": "=:b:bc1qdddfwkvqs0x74ues8pcrcp5sz03g8a2kqcx75t/u1j0dx09lc9007ntxcxc00mkm4xcrkn9hmadk6uwgky3e208maktgkgdq887hnshlhuy8ljvdunkzxayshd6t99y78ze8865h80uml20ur630y3w9mxa7e6tll2pprqhh2q2cvr9szre88:12876:_/ts:5/0",
  // ────────────────────────────────────────────────────────────────────────────

  "fees": [ /* … */ ],
  "estimatedTime": { "inbound": 75, "swap": 6, "outbound": 600, "total": 681 },
  "meta": { /* … */ },
  "swapId": "5ef1d95f-1bf6-4e41-85fc-d51a890b2468"
  // note: NO "tx" field — the wallet builds the deposit
}
```

The deposit: ONE transaction, TWO recipients

```json
[
  {
    // ← inboundAddress from /swap (transparent t1 vault). The VALUE goes here.
    "address": "t1H49fQdV8eAweCugmPoL28oppuLRcnv95C",
    "amount": 2000000,          // sellAmount "0.02" ZEC × 1e8 = 2,000,000 zatoshis
    "memo": null                // transparent output — no memo
  },
  {
    // ← shieldedMemo.unifiedAddress from /swap (Maya's u1 memo address).
    "address": "u1m76w6hk7f3gqn3w2pgsvrn7ckedd3mtyxu6kugeyqeag6zqypjt8nvw7mnwzu80rrs2dahv8kz0py6y34zsecr8vhngzzjgq9djux8dkakcj33h4ta2g4lqg27470ppztalnajg08e3e",
    "amount": 0,                // 0-value Orchard note — NEVER put value here
    "memo": "=:b:bc1qdddfwkvqs0x74ues8pcrcp5sz03g8a2kqcx75t/u1j0dx09lc9007ntxcxc00mkm4xcrkn9hmadk6uwgky3e208maktgkgdq887hnshlhuy8ljvdunkzxayshd6t99y78ze8865h80uml20ur630y3w9mxa7e6tll2pprqhh2q2cvr9szre88:12876:_/ts:5/0"
  }
]
```

### Refund address & fund-loss guard

For a shielded source, a refund address is embedded in the Maya memo so funds can be returned if the swap fails. SwapKit **defaults this to the swap's source address** — there's no separate `refundAddress` to pass on `/v3/swap`. The embedded address is validated as a Zcash address; an invalid value returns `invalidAddressForChain` (400).

{% hint style="warning" %}
**Fund-loss guard:** a shielded-source Zcash swap whose memo does not contain a valid refund address is aborted with `zcashShieldedRefundMissing` (500) rather than returning a build-able deposit. Never broadcast a shielded deposit without the refund address in the memo.

Never send funds to `shieldedMemo.unifiedAddress` .
{% endhint %}

### Memo size budgets

The Zcash memo is measured in **UTF-8 bytes**:

* **Transparent source:** 80 bytes (memo travels in an OP\_RETURN).
* **Shielded source:** 512 bytes (memo rides the Orchard shielded note).

### Errors

These Zcash-specific errors can surface during a shielded swap. See the full definitions in the [`/v3/swap` error references](/swapkit-api/v3-swap-obtain-swap-transaction-details#swap-errors).

* `zcashShieldedRefundMissing` (500) — fund-loss guard; refund address missing from the memo.
* `zcashMemoTooLong` (400) — memo exceeds the 80B (transparent) / 512B (shielded) budget.
* `zcashShieldedMemoUnavailable` (503) — MAYAChain shielded-memo support is off.
* `memoTooLongForSourceChain` (400) — generated memo too long for the source chain.
* `zcashUnifiedAddressUnsupported` (400, `/v3/quote` only) — non-Maya provider can't handle a unified address.

### Provider limitations

Unified/shielded Zcash is **MAYAChain-only**. NEAR Intents and Flashnet cannot quote unified Zcash addresses yet, only MAYAChain routes are returned.


# HyperCore signing & broadcasting

Selling from HyperCore means signing a Hyperliquid EIP-712 action and submitting it yourself — the payload, the signing rules, and the traps.

SwapKit supports **HyperCore** (Hyperliquid's exchange chain, `HYPE`) as a swap source and destination. Selling *from* HyperCore is the one flow where your wallet signs **EIP-712 typed data** and submits it to Hyperliquid itself, instead of building and broadcasting an ordinary chain transaction. HyperCore has no plain transfer primitive — value moves by signing a Hyperliquid *action* — so a deposit address alone is not actionable.

Depositing *into* HyperCore is unaffected: `X → HYPE.USDC` is a normal transaction on the sell chain, with the HyperCore account as the destination. Only the sell direction produces typed data.

### Chain & asset identifiers

HyperCore is chain **`HYPE`**. HyperEVM is a **separate** chain (`HYPEREVM`) and is not interchangeable with it.

| Identifier                                             | Notes                                                                                                                |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `HYPE.USDC-0xb88339CB7199b77E23DB6E890353E22632Ba630f` | The tradable HyperCore asset. The address is the **HyperEVM ERC-20 twin**, used as the canonical routing identifier. |
| `USDC:0x6d1e7cde53ba9467b783cb7c530ce054`              | The **HIP-1 token id**, used inside the signed action's `token` field. Not an EVM contract address.                  |

{% hint style="warning" %}
`HYPE` identifiers are **case-sensitive**. `Chain.Hype` is not an EVM chain, so no checksum normalization is applied — a lowercased contract address yields a misleading `noRoutesFound`.
{% endhint %}

Amounts are human-readable decimal strings everywhere, including inside the signed action (`"8"`, not base units).

### What /v3/swap returns

Discriminate on `meta.txType === "EIP_712_HYPE_SEND_ASSET"`. The `tx` object carries everything needed: the typed data to sign, the action to submit, and where to submit it.

```json
{
  "txHint": "simpleTransfer",
  "targetAddress": "0x4d434471b3337062c6cb74eaf8d5595dbbba6839",
  "swapId": "496b0ea3-4ff4-45ae-a222-75a0b4a3d2f7",
  "meta": { "txType": "EIP_712_HYPE_SEND_ASSET" },
  "fees": [
    {
      "type": "inbound",              // Hyperliquid account-activation fee — see below
      "amount": "1",
      "asset": "HYPE.USDC-0xb88339CB7199b77E23DB6E890353E22632Ba630f",
      "chain": "HYPE",
      "protocol": "FLASHNET"
    }
  ],
  "tx": {
    "typedData": {
      "domain": {
        "name": "HyperliquidSignTransaction",
        "version": "1",
        "chainId": 42161,
        "verifyingContract": "0x0000000000000000000000000000000000000000"
      },
      "primaryType": "HyperliquidTransaction:SendAsset",
      "types": {
        "HyperliquidTransaction:SendAsset": [
          { "name": "hyperliquidChain", "type": "string" },
          { "name": "destination",      "type": "string" },
          { "name": "sourceDex",        "type": "string" },
          { "name": "destinationDex",   "type": "string" },
          { "name": "token",            "type": "string" },
          { "name": "amount",           "type": "string" },
          { "name": "fromSubAccount",   "type": "string" },
          { "name": "nonce",            "type": "uint64" }
        ]
      },
      "message": {
        "hyperliquidChain": "Mainnet",
        "destination": "0x4d434471b3337062c6cb74eaf8d5595dbbba6839",
        "sourceDex": "spot",
        "destinationDex": "",
        "token": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",
        "amount": "8",
        "fromSubAccount": "",
        "nonce": 1786652874878
      }
    },
    "action": {
      "type": "sendAsset",
      "hyperliquidChain": "Mainnet",
      "signatureChainId": "0xa4b1",
      "destination": "0x4d434471b3337062c6cb74eaf8d5595dbbba6839",
      "sourceDex": "spot",
      "destinationDex": "",
      "token": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",
      "amount": "8",
      "fromSubAccount": "",
      "nonce": 1786652874878
    },
    "submitTo": "https://api.hyperliquid.xyz/exchange"
  }
}
```

`action` and `typedData.message` describe the same transfer. `signatureChainId` and `type` belong to the action envelope only — they are **not** part of the signed struct, which is exactly the eight fields listed in `types`.

### Signing

Sign `tx.typedData` with standard **`eth_signTypedData_v4`**. `primaryType` is given explicitly and `types` contains exactly that one struct — no `EIP712Domain` entry, so add one yourself if your signer requires it.

```typescript
const signature = await wallet.signTypedData(
  tx.typedData.domain,
  tx.typedData.types,
  tx.typedData.message,
);

const { r, s, v } = Signature.from(signature); // split the 65-byte signature
```

{% hint style="info" %}
The domain `chainId` is **42161 (Arbitrum)** and `verifyingContract` is the zero address. That is Hyperliquid's action-signing domain, not a contract call — nothing here touches Arbitrum, and everything stays on HyperCore. Don't "correct" it to 999 or 1337. `action.signatureChainId: "0xa4b1"` is the same 42161, hex-encoded, which is what Hyperliquid expects in the envelope.
{% endhint %}

### Broadcasting

POST to `tx.submitTo`, passing `action` through unchanged and reusing its `nonce`:

```json
{
  "action": { /* tx.action, byte-for-byte */ },
  "nonce": 1786652874878,
  "signature": {
    "r": "0x706b46cb902e5540d650b58955577d031341aca5d82be92db93c53a56d4681a5",
    "s": "0x512a9c2585bd9ffb776139dface8ee4776bc623c7c3e700c4d719e95df9cb89c",
    "v": 27
  },
  "vaultAddress": null
}
```

A success looks like `{"status":"ok","response":{"type":"default"}}`.

{% hint style="danger" %}
**Rejections also return HTTP 200.** A failed action comes back as `{"status":"err","response":"<reason>"}`. Checking only the HTTP status will report failures as successful swaps.
{% endhint %}

{% hint style="warning" %}
**`action.nonce` IS the submission nonce.** Signing one value and submitting a different one authenticates a different struct, and the transfer is rejected. Copy `action` through unchanged — do not re-order, re-derive, or re-normalize it after signing.

`nonce` is stamped server-side at `/v3/swap`, and Hyperliquid rejects nonces that drift far from its clock. Sign and submit promptly; if a payload has been sitting around, re-run `/v3/quote` → `/v3/swap` rather than reusing it.
{% endhint %}

### Why sendAsset, and what the dex fields mean

`sourceDex` and `destinationDex` name the HyperCore sub-account each side of the transfer touches: `""` is the default **perps** account, `"spot"` is the **spot** account.

* **`destinationDex` is always `""`.** Flashnet's `hypercore:USDC` route quotes default-perps USDC and cannot credit a spot deposit — a deposit sent with `"spot"` arrives but is not claimable by the route.
* **`sourceDex` is resolved for you.** SwapKit reads the sender's live perps and spot balances and picks whichever can fund the transfer, preferring perps.

{% hint style="warning" %}
Do not substitute Hyperliquid's `usdSend` or `spotSend` actions. Both are rejected outright on a **unified account** with `{"status":"err","response":"Action disabled when unified account is active"}`, and unified is Hyperliquid's recommended mode. `sendAsset` is the only transfer both account types can execute.

A unified account also reports every balance under `spotClearinghouseState`, with `clearinghouseState.withdrawable` reading `"0.0"` — so a balance check against the perps account alone will wrongly conclude the user has nothing.
{% endhint %}

### Account-activation fee

Hyperliquid charges the **sender** a one-time **1 USDC** fee when the destination account does not yet exist, **on top of** `amount`. Providers mint a fresh deposit address per swap, so in practice this applies to most HyperCore payins.

It is quoted as an `inbound` fee on the route (see `fees` above). The signed `amount` is unaffected — the user needs `amount + fee` available in the funding sub-account. When the balance falls short, the route carries an `insufficientBalance` warning and remains build-able, so present it before the user signs:

```json
{
  "code": "insufficientBalance",
  "display": "Insufficient balance",
  "tooltip": "The source account does not hold enough to cover this swap plus its network fees. The transaction can be built, but it will fail if submitted as-is."
}
```

### Verifying before you sign

Because the signature covers `typedData.message` while the funds move on `action`, a wallet should confirm the two agree before signing — every one of the eight signed fields, plus `action.destination` against the route's `targetAddress`. A disagreement means signing one transfer and submitting another.

### Tracking the payin

A `sendAsset` appears in Hyperliquid's ledger (`userNonFundingLedgerUpdates`) as `delta.type: "send"` — **not** `spotTransfer` or `internalTransfer`. Filtering for the older types will never match, and the transfer will look like it never happened:

```json
{
  "time": 1786652875123,
  "hash": "0x…",
  "delta": {
    "type": "send",
    "user": "0x05bf…",
    "destination": "0x4d43…",
    "sourceDex": "spot",
    "destinationDex": "",
    "token": "USDC",
    "amount": "8.0",
    "fee": "1.0",
    "feeToken": "USDC"
  }
}
```

Swap progress is tracked normally through [/track](/swapkit-api/track-request-the-status-of-a-swap), keyed on the deposit address.

### Don't confuse it with the Mayan HyperCore withdraw

Mayan's HyperCore route signs a different action (`HyperliquidTransaction:SendToEvmWithData`) under `meta.txType = EIP_712_HYPE_WITHDRAW`, in a `{domain, types, value}` shape — note `value`, not `message`, and no `primaryType`. That one **bridges off** HyperCore and is relayed by Mayan; this one stays on HyperCore and you submit it. The shapes are deliberately different so the two cannot be crossed — always branch on `meta.txType`.


# Integrate NEAR on an existing SwapKit integration

NEAR is a new swap provider for SwapKit that enables cross-chain swaps through NEAR Intents. If you already have a SwapKit integration, adding NEAR support requires minimal changes to your existing implementation.

### Integration Overview

NEAR follows the standard SwapKit quoting flow documented in the [Quote Implementation Flow](/swapkit-api/quote-and-swap-implementation-flow). Your existing integration will automatically include NEAR quotes when the traded assets are supported.

### Provider Identification

Responses that include NEAR as a provider will show `"NEAR"` in the `providers` field of a `/quote` response:

```json
{
  "providers": ["NEAR"],
  // ... other quote fields
}

```

### Provider Selection

NEAR will be included in quote responses when:

* The `providers` field is omitted from your `/quote` request (default behavior includes all available providers)
* The `providers` field explicitly includes `"NEAR"` in the array

Example request to specifically include NEAR:

```bash
curl -X POST "<https://api.swapkit.dev/v3/quote>" \\
  -H "Content-Type: application/json" \\
  -H "x-api-key: YOUR_VARIABLE_HERE" \\
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "providers": ["NEAR"]
  }'

```

### Supported Chains

NEAR currently supports the following blockchain networks:

#### Currently Available

* Ethereum
* Bitcoin
* Binance Smart Chain
* Solana
* Monad
* Dogecoin
* NEAR Chain
* TRON
* Ripple
* ZCash
* Sui
* Cardano
* Polygon
* Base
* Optimism
* Berachain
* Gnosis
* X Layer
* Litecoin
* Bitcoin Cash
* Avalanche
* Arbitrum

#### Coming Soon

* TON

#### Future Support

* Aptos
* Kaspa
* Stellar

### Key Difference: Transaction Architecture

The primary difference between NEAR and other SwapKit providers is the transaction structure when `includeTx: true` is used:

**NEAR transactions are transfers to exchange vaults** rather than direct smart contract interactions. When you receive a transaction object in the quote response, it represents a transfer to a specific vault designated for settlement with the market maker.

#### EIP-7702 Support

NEAR supports **Type 4 transactions**, introduced with [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), enabling advanced transaction capabilities on supported networks.

### Network Confirmations

When sending funds to the NEAR exchange vault, **2 network confirmations are required** before the swap begins processing. This confirmation requirement is accurately reflected in the `estimatedTime` field of quote responses:

```json
{
  "estimatedTime": {
    "inbound": 1200,    // Includes 2 block confirmation wait time
    "swap": 12,
    "outbound": 400,
    "total": 1612
  }
}

```

### Implementation Requirements

No additional implementation is required beyond your existing SwapKit integration. NEAR:

* Follows the same quote request/response structure
* Uses the same error handling patterns
* Supports the same `includeTx` flow for transaction building
* Returns standard SwapKit response formats

### Example Integration

If you're already integrated with SwapKit, NEAR quotes will automatically appear when available. Here's a typical flow, also explained in our [recommended quote implementation flow](/swapkit-api/quote-and-swap-implementation-flow):

1. **Initial Quote** (existing code, no changes needed):

```bash
curl -X POST "<https://api.swapkit.dev/v3/quote>" \\
  -H "Content-Type: application/json" \\
  -H "x-api-key: YOUR_VARIABLE_HERE" \\
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1"
  }'

```

You can also filter providers if you are already limitting them, but adding NEAR to the list.

2. **Provider Selection** (existing code, no changes needed):

Select the best option for the quote requested. Present it to the user for them to accept.

3. **Transaction Building**:

Request a new quote using `"includeTx": true` and adding a filter by provider.

```bash
curl -X POST "<https://api.swapkit.dev/quote>" \\
  -H "Content-Type: application/json" \\
  -H "x-api-key: YOUR_VARIABLE_HERE" \\
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "sourceAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
    "destinationAddress": "0x0a4c79cE84202b03e95B7a692E5D728d83C44c76",
    "providers": ["NEAR"],
    "includeTx": true
  }'

```

4. **Sign and broadcast** the returned transaction (existing wallet integration, no changes needed)

The only difference you'll notice is that NEAR transactions transfer funds to exchange vaults rather than interacting directly with contracts, but this is handled transparently by the SwapKit API.


# Version disclaimer

SwapKit's latest version is v3, it is the recommended version to integrate. The documentation for v2 is currently shared here until support for it is discontinnued to help current v2 integrations in their process to upgrade or mantain their integration if they have issues.

### How to use SwapKit's API v2

To get the most out of SwapKit's API v2 we recommend going through the endpoints in the following order:

1. **`/providers`** – Retrieve a list of available providers and their supported chains.
2. **`/tokens`** – Fetch a list of supported tokens across different chains and providers.
3. **`/quote`** – Request a swap quote to estimate the cost and parameters for a transaction.
4. **`/track`** – Track the status of a swap to monitor its progress and completion.

However, `/providers` and `/tokens` don't change often, and you can directly request a quote once you have everything set up.\
Additionally, the `/screen` endpoint can be used to validate AML compliance of the involved addresses. If it is something you need, use it before the transaction is offered for signing and not with every `/quote`.\
You can try the API yourself at <https://api.swapkit.dev/docs/> . \
You can also check out our transaction tracking interface at <https://track.swapkit.dev/>.\
Please read over our recommended [quote implementation flow](/swapkit-api/quote-and-swap-implementation-flow) to understand how to create a better performing integration.


# /quote v2 - Request a trade quote

### Endpoint

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/quote`

***

### Request parameters

```json
{
  "sellAsset": "string",
  "buyAsset": "string",
  "sellAmount": "string",
  "providers": [
    "string"
  ],
  "sourceAddress": "string",
  "destinationAddress": "string",
  "slippage": 100,
  "affiliate": "string",
  "affiliateFee": 1000,
  "allowSmartContractSender": true,
  "allowSmartContractReceiver": true,
  "disableSecurityChecks": true,
  "includeTx": true,
  "cfBoost": true
}
```

Here's a detailed description of the different parameters:

<table data-full-width="false"><thead><tr><th width="230">Parameter</th><th width="110">Type</th><th width="99">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>✅ Yes</td><td>The asset being sold (e.g. <code>"ETH.ETH"</code>).</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>✅ Yes</td><td>The asset being bought (e.g. <code>"BTC.BTC"</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>string</code></td><td>✅ Yes</td><td>Amount in basic units (decimals separated with a dot).</td></tr><tr><td><code>providers</code></td><td><code>array</code></td><td>❌ No</td><td>Limits the possible liquidity providers.</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>✅ Yes</td><td>Address of the sender.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>✅ Yes</td><td>Address of the recipient.</td></tr><tr><td><code>slippage</code></td><td><code>number</code></td><td>❌ No</td><td>Max slippage in percentage (5 = 5%).</td></tr><tr><td><code>affiliate</code></td><td><code>string</code></td><td>❌ No</td><td>Affiliate address for revenue sharing.</td></tr><tr><td><code>affiliateFee</code></td><td><code>number</code></td><td>❌ No</td><td>Fee percentage in basis points (50= 0.5%).</td></tr><tr><td><code>allowSmartContractSender</code></td><td><code>boolean</code></td><td>❌ No</td><td>Allow smart contract as sender. Do not use without consulting SwapKit devs first. </td></tr><tr><td><code>allowSmartContractReceiver</code></td><td><code>boolean</code></td><td>❌ No</td><td>Allow smart contract as receiver. Do not use without consulting SwapKit devs first. </td></tr><tr><td><code>disableSecurityChecks</code></td><td><code>boolean</code></td><td>❌ No</td><td>Bypass security checks. Do not use without consulting SwapKit devs first. </td></tr><tr><td><code>includeTx</code></td><td><code>boolean</code></td><td>❌ No</td><td>Include transaction details in the response. Read more about it <a href="/pages/acApilBPwysSkWkXWCqQ">here</a>.</td></tr><tr><td><code>cfBoost</code></td><td><code>boolean</code></td><td>❌ No</td><td>Enables Chainflip boost for better rates.</td></tr></tbody></table>

**Note:** Asset names for `sellAsset` and `buyAsset` should follow the following nomenclature:

* Chain.Asset (e.g., `"BTC.BTC"` or `"ARB.ETH"`)
* Chain.Asset-ContractAddress (e.g., `"ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"`)

This is the `identifier`as provided in the [`/tokens`](/swapkit-api/tokens-list-and-search-supported-tokens) endpoint.

{% hint style="warning" %}
When you test your integration, use an address with enough `sellAsset` balance to cover the trade. This will ensure you receive complete responses.
{% endhint %}

***

### Example request

A simple request to trade `ETH.ETH`to `BTC.BTC`may omit the `providers`list if you can manage them in the response, but should include the amount, the involved addresses and slippage settings.

The expiration time returned by the endpoint is an estimation. We recommend requesting a new quote at least every minute if the trade has not been executed yet.

{% hint style="info" %}
Using the header `x-api-key`in your `/quote`request automatically applies your affiliate addresses and fee values set up through our [partners dashboard](/monetization#step-3-set-affiliate-fee-tiers) in the response parameters.
{% endhint %}

{% tabs %}
{% tab title="cURL" %}

```sh
curl -X POST "https://api.swapkit.dev/quote" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "sellAsset": "ETH.ETH",
    "buyAsset": "BTC.BTC",
    "sellAmount": "0.1",
    "sourceAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
    "slippage": 10,
    "includeTx": true
  }'
```

{% endtab %}
{% endtabs %}


# /quote v2 - Implementation flow

Implement a correct quoting flow

An important part of our service is the possibility of including transaction data in a `/quote` response.\
This option is activated when the "`includeTx": true` request parameter is used, but it is important to keep a proper quote request flow for a better performing integration.

### 1. Initial Quote Request

First, fetch quotes with "`includeTx": false` (or unset, as false is the default). For this step, you may leave out `"sourceAddress"` and `"destinationAddress"` since they are not needed to process a quote:

```bash
curl -X POST "https://api.swapkit.dev/quote" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "slippage": 3,
    "includeTx": false
  }'
```

You can filter for the providers you have integrated using the `"providers"` argument if you want to limit the options shown.

This returns a regular quote response including pricing information, estimated timing and fees, and route details with the available providers, but without transaction data.

### 2. Identify the Provider route you want to use

After offering a price to the user of your application, the user would then accept it. This identifies what provider offers the best match for the route you quoted for.

Before the user gets offered a transaction to sign though, you will request a new quote filtering with this specific provider and requesting a transaction object.

### 3. Transaction Building

When the user is ready to execute the swap, request a new quote with "`includeTx": true` and filtering by provider:

```bash
curl -X POST "https://api.swapkit.dev/quote" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "sellAsset": "BTC.BTC",
    "buyAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "sourceAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
    "destinationAddress": "0x0a4c79cE84202b03e95B7a692E5D728d83C44c76",
    "providers": ["NEAR"],
    "slippage": 3,
    "includeTx": true
  }'
```

Setting `includeTx: true` results in slightly slower response times as the system performs additional validations and transaction building. This is why it is also best to now filter the providers used, so only the specific best quote is returned.

The quote response will now include additional transaction related fields, different for each provider. You can show the price to the user again for signing or compare internally to the previous value before offering it:

```json
{
  // ... all fields from above, plus:
  "targetAddress": "1FSHYruFaYjm5FQrzB3LmF15FMC3QwUFmy",
  "inboundAddress": "1FSHYruFaYjm5FQrzB3LmF15FMC3QwUFmy",
  "tx": "cHNidP8BAHUCAAAAAc86GvpcKcJoCV1YpX9orAcE/ZckHbLzuTXOPl0McccZAAAAAAD/////AoCWmAAAAAAAGXapFJ5Z+YVfu2g/WM/XGmu9FBGSeS5wiKx/LhEm4wAAABepFFKL8plEPOVGF9/52YVT+SKl3opehwAAAAAAAQEg/8SpJuMAAAAXqRRSi/KZRDzlRhff+dmFU/kipd6KXocAAAA=",
  "meta": {
    // ... existing meta fields, plus:
    "txType": "PSBT"
  }
}
```

***

### Transaction Building Process

When `includeTx: true` is set, the system performs the following validations:

#### 1. Balance Verification

The system first checks if the source address has sufficient balance to cover the `sellAmount`. If insufficient funds are detected, an `insufficientBalance` error is thrown for the entire request.

#### 2. Transaction Building

If the balance check passes, the system attempts to build the transaction. In certain edge cases, the wallet may have enough balance to match the `sellAmount` but insufficient funds to cover network fees. Since different providers have varying transaction sizes, some may return valid transaction data while others might fail with an `unableToBuildTransaction` error.

#### 3. Successful Response

If all validations pass, you'll receive a complete route with transaction data that can be directly signed and broadcast.

### Key Fields

* `targetAddress` The deposit address where funds should be sent, or the address of the contract that should be called.
* `inboundAddress` The address monitoring for incoming transactions.
* `tx` The transaction data ready for signing.
* `txType` The format of the transaction data (e.g., "PSBT" for Bitcoin or "EVM" for EVM chains).

### Error Handling

Be prepared to handle the following errors when using `includeTx: true`:

* `insufficientBalance` The source address doesn't have enough tokens for the swap.
* `insufficientAllowance`: The source address doesn't have enough tokens approved for the contract interaction. Relevant for tokens in EVM networks.
* `unableToBuildTransaction` The wallet has sufficient tokens but insufficient funds for network fees.

This is an example error for a wallet without enough balance:

```json
{
  "message": "Cannot build transaction. Insufficient balance for asset ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 amount 700 address 0x...",
  "error": "insufficientBalance",
  "data": {
    "chain": "ETH.USDC-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "amount": "700",
    "address": "0x..."
  }
}
```


# /quote v2 - Understanding the response

### Response structure

The response lists the available providers for the route, limited by the list used to quote it. If the parameter was not included the response lists all possible providers.\
Estimated output, expected slippage and estimated time are some of the information listed for each swap provider so you can choose the best routes. Additionally, SwapKit provides tags to aid you in the process and identify what we would consider ourselves.

{% hint style="warning" %}
When you test your integration, use an address with enough `sellAsset` balance to cover the trade. This will ensure you receive complete responses.
{% endhint %}

#### **`quoteId`**

A unique identifier for the quote request. You can optionally store it to reference the quote provided by SwapKit at a later date.

***

### `routes` Object

The `routes` array contains possible swap options, each identified by the `providers`object at the start.

<table><thead><tr><th width="304">Field</th><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>providers</code></td><td><code>array</code></td><td>List of providers available for this route (<code>CHAINFLIP</code>, <code>THORHCAIN</code>etc.).</td></tr><tr><td><code>sellAsset</code></td><td><code>string</code></td><td>The asset being sold (e.g., <code>"ETH.ETH"</code>).</td></tr><tr><td><code>buyAsset</code></td><td><code>string</code></td><td>The asset being bought (e.g., <code>"BTC.BTC"</code>).</td></tr><tr><td><code>sellAmount</code></td><td><code>string</code></td><td>Amount of the sell asset in smallest units.</td></tr><tr><td><code>expectedBuyAmount</code></td><td><code>string</code></td><td>Estimated amount of the buy asset to be received.</td></tr><tr><td><code>expectedBuyAmountMaxSlippage</code></td><td><code>string</code></td><td>Worst-case buy amount considering max slippage.</td></tr><tr><td><code>sourceAddress</code></td><td><code>string</code></td><td>Source address.</td></tr><tr><td><code>destinationAddress</code></td><td><code>string</code></td><td>Destination address.</td></tr><tr><td><code>targetAdddress</code></td><td><code>string</code></td><td>Address to send the initial transaction to.</td></tr><tr><td><code>approvalAddress</code></td><td><code>string</code></td><td>Address to approve spending to (when swapping tokens on EVM)</td></tr><tr><td><code>memo</code></td><td><code>string</code></td><td>Transaction memo, which can be used in UTXO chains directly.</td></tr><tr><td><code>fees</code></td><td><code>array</code></td><td>List of fees applied to the swap (inbound, network, affiliate).</td></tr><tr><td><code>tx</code></td><td><code>array</code></td><td>Transaction object for EVM chains, a <a href="https://en.bitcoin.it/wiki/BIP_0174">PSBT</a> object for BTC etc.</td></tr><tr><td><code>estimatedTime</code></td><td><code>object</code></td><td>Estimated time for different phases of the swap.</td></tr><tr><td><code>totalSlippageBps</code></td><td><code>number</code></td><td>Expected total slippage.</td></tr><tr><td><code>legs</code></td><td><code>array</code></td><td>The different involved steps in the swap.</td></tr><tr><td><code>warnings</code></td><td><code>array</code></td><td>Potential warnings about this swap provider.</td></tr><tr><td><code>meta</code></td><td><code>array</code></td><td>Other information about the transaction.</td></tr><tr><td><code>priceImpact</code></td><td><code>number</code></td><td>Price impact percentage in USD terms.</td></tr></tbody></table>

* The `tx`object is only provided if the `"includeTx":true`parameter was included. It can be used as the transaction data for EVM chains. It won't be provided if the token to swap is not approved for the `approvalAddress` . It is explained in more detail [here](/swapkit-api/quote-and-swap-implementation-flow).
* `targetAddress` is the address to send the transaction to, but Chainflip transactions need to use a [deposit channel](broken://pages/7EKJffHfdc6TqoRZPdvQ) instead and don't require approval.&#x20;

{% hint style="info" %}
It is necessary to approve `approvalAddress` as an spender if you are swapping tokens on an EVM chain.
{% endhint %}

In many cases `targetAddress` and `approvalAddress` will be the same but they will differ when using dex aggregation for a cross chain swap (swapping an ERC20 into BTC.BTC for example).

#### **Example `routes` Object:**

```json
{
    "providers": ["MAYACHAIN"],
    "sellAsset": "ETH.ETH",
    "sellAmount": "0.1",
    "buyAsset": "BTC.BTC",
    "expectedBuyAmount": "0.00275103",
    "expectedBuyAmountMaxSlippage": "0.00247592",
    "sourceAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
    "targetAddress": "0xe3985E6b61b814F7Cdb188766562ba71b446B46d",
    "inboundAddress": "0x62b54578b77d0ccfe74b4a009d9ecbc82777c9ff",
    "expiration": "1738931194",
    "memo": "=:b:357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P:247592:_/sk:20/100",
    "fees": [{
        ...
    }],
    "tx": {
        "to": "0xe3985E6b61b814F7Cdb188766562ba71b446B46d",
        "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "gas": "0x9714",
        "gasPrice": "0x5be199fd",
        "value": "100000000000000000",
        "data": "0x44bc937b00000000000000000000000062b54578b77d0ccfe74b4a009d9ecbc82777c9ff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016345785d8a000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000067a5fbfa00000000000000000000000000000000000000000000000000000000000000393d3a623a3335376133536f394362734e6642426746594143477678785336744d61446f6131503a3234373539323a5f2f736b3a32302f31303000000000000000"
    },
    "estimatedTime": {
        ...
    },
    "totalSlippageBps": 246,
    "legs": [{
        ...
    }],
    "warnings": [],
    "meta": {
        ...
    }
    "priceImpact": -0.38,
    "approvalAddress": "0xe3985E6b61b814F7Cdb188766562ba71b446B46d",
    }
```

***

### Fees breakdown

Fees are categorized into different types based on their role in the swap process.

<table><thead><tr><th width="297">Fee Type</th><th>Description</th></tr></thead><tbody><tr><td><strong>Inbound</strong></td><td>Fee for transferring the sell asset, paid from the user's wallet.</td></tr><tr><td><strong>Network</strong></td><td>Blockchain transaction fee for processing the swap.</td></tr><tr><td><strong>Affiliate</strong></td><td>Fee paid to the specified affiliate.</td></tr><tr><td><strong>Service</strong></td><td>SwapKit's service fee.</td></tr><tr><td><strong>Outbound</strong></td><td>Fee for transferring the buy asset to the destination address.</td></tr><tr><td><strong>Liquidity</strong></td><td>Fee applied by the liquidity provider to facilitate the swap.</td></tr></tbody></table>

Only the inbound fee will be paid from the source wallet. The other fees are deducted from the output, and the output values provided by the endpoint already take this into consideration.

***

### Transaction details

Transaction details are only included if the option `"includeTx": true` was used in the `/quote`request. It provides an object valid to initiate a transaction in the source chain. These objects can be used so sing and send the transaction using the main libraries for each blockchain.

***

### Estimated time

The estimated time in seconds for the swap is divided into the following phases:

* **Inbound:** Time taken to receive the sell asset.
* **Swap:** Time taken for the swap process.
* **Outbound:** Time taken to transfer the bought asset to the destination. This includes the provider outbound time, not only the transaction time.
* **Total:** The sum of all time estimates.

#### **Example estimated time:**

```json
"estimatedTime": {
    "inbound": 30,
    "swap": 6,
    "outbound": 624,
    "total": 660
}
```

***

### Transaction metadata

The `meta` section provides additional information about the swap.

| **priceImpact**              | The expected impact on market rates.                                         |
| ---------------------------- | ---------------------------------------------------------------------------- |
| **affiliate - affiliateFee** | Details of affiliate commissions.                                            |
| **tags**                     | \["FASTEST", "RECOMMENDED", "CHEAPEST"]                                      |
| **approvalAddress**          | The contract address for ERC-20 approvals.                                   |
| **chainflip**                | Information necessary to open a deposit channel for the Chainflip providers. |

#### **Example `meta` Object:**

```json
"meta": {
    "priceImpact": -1.69,
    "assets": [{
        "asset": "ETH.ETH",
        "price": 2752.14,
        "image": "https://storage.googleapis.com/token-list-swapkit/images/eth.eth.png"
    }, {
        "asset": "BTC.BTC",
        "price": 97648,
        "image": "https://storage.googleapis.com/token-list-swapkit/images/btc.btc.png"
    }],
    "approvalAddress": "0xD37BbE5744D730a1d98d8DC97c42F0Ca46aD7146",
    "tags": ["FASTEST"],
    "affiliate": "sk",
    "affiliateFee": "100",
    "txType": "EVM"
}
```

The `"tags"`can help recognize what SwapKit considers the best routes by filtering through the `["FASTEST", "RECOMMENDED", "CHEAPEST"]` identification.<br>

<table><thead><tr><th width="283">Tag</th><th>Description</th></tr></thead><tbody><tr><td><strong>RECOMMENDED</strong></td><td>Best overall route based on output and speed.</td></tr><tr><td><strong>CHEAPEST</strong></td><td>The route with the maximum output.</td></tr><tr><td><strong>FASTEST</strong></td><td>The route with the shortest total estimated time.</td></tr></tbody></table>

Additionally the `"chainflip"`object is added for `CHAINFLIP` and `CHAINFLIP_STREAMING`swaps. This is used to [open a deposit channel](broken://pages/7EKJffHfdc6TqoRZPdvQ), a necessary step to initiate a trade.

```json
"chainflip": {
    "sellAsset": {
        "chain": "Ethereum",
        "asset": "ETH"
    },
    "buyAsset": {
        "chain": "Bitcoin",
        "asset": "BTC"
    },
    "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
    "affiliateFees": [{
        "brokerAddress": "cFNwtr2mPhpUEB5AyJq38DqMKMkSdzaL9548hajN2DRTwh7Mq",
        "feeBps": 100
    }],
    "refundParameters": {
        "minPrice": "0x2c166186363d48000000000",
        "refundAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "retryDuration": 150
    }
}
```

***

### Warnings

Warnings highlight potential issues such as high price impact or insufficient liquidity.

#### **Example warning:**

```json
"warnings": [
      {
        "code": "highPriceImpact",
        "display": "-10%",
        "tooltip": "This swap has a high value impact given the current liquidity and network fees. There may be a large difference between the amount of your input token and what you will receive in the output token."
      }
]
```

***

### Provider errors

If certain providers cannot facilitate the swap, the response may include error messages under `providerErrors`.

#### **Example provider error:**

```json
"providerErrors": [{
    "provider": "CHAINFLIP_STREAMING",
    "errorCode": "swapAmountTooSmall"
}]
```


# /chainflip/broker/channel - Opening a Chainflip deposit channel

To initiate a swap through the `CHAINFLIP`or `CHAINFLIP_STREAMING`providers, a deposit channel must be opened first.

**Method:** `POST`\
**URL:** `https://api.swapkit.dev/chainflip/broker/channel`

The information needed to open a channel is included inside the `meta.chainflip`object of the `/quote`response, as in the example below. The object needs to be passed to the `/chainflip/broker/channel`endpoint, which creates a deposit channel.\
It contains a deposit address for the chain initiating the swap, where the tokens can be sent to.

```json
"meta": {
    ...
    "chainflip": {
        "sellAsset": {
            "chain": "Ethereum",
            "asset": "ETH"
        },
        "buyAsset": {
            "chain": "Bitcoin",
            "asset": "BTC"
        },
        "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
        "affiliateFees": [{
            "brokerAddress": "cFNwtr2mPhpUEB5AyJq38DqMKMkSdzaL9548hajN2DRTwh7Mq",
            "feeBps": 100
        }],
        "refundParameters": {
            "minPrice": "0x2c166186363d48000000000",
            "refundAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
            "retryDuration": 150
        }
    }
}
```

As an example a channel can be requested as:

{% tabs %}
{% tab title="cURL" %}

<pre class="language-sh"><code class="lang-sh"><strong>curl -X 'POST' \
</strong>  'https://api.swapkit.dev/chainflip/broker/channel' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
        "sellAsset": {
            "chain": "Ethereum",
            "asset": "ETH"
        },
        "buyAsset": {
            "chain": "Bitcoin",
            "asset": "BTC"
        },
        "destinationAddress": "357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P",
        "affiliateFees": [{
            "brokerAddress": "cFNwtr2mPhpUEB5AyJq38DqMKMkSdzaL9548hajN2DRTwh7Mq",
            "feeBps": 100
        }],
        "refundParameters": {
            "minPrice": "0x2c166186363d48000000000",
            "refundAddress": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
            "retryDuration": 150
        }
    }'
</code></pre>

{% endtab %}
{% endtabs %}

The response contains a `depositAddress`which funds can be sent to.\
No approval is needed for this address in the case of ERC-20 tokens, and a memo is not needed either.

```json
{
    "depositAddress": "0xd3f189d1cab37e5dc1f920639ed13bf61132ab16",
    "channelId": "6543210-Ethereum-1933",
    "explorerUrl": "https://scan.chainflip.io/channels/6543210-Ethereum-1984"
}
```

***

Here is an example integrating a `/quote`request with opening a channel:

```javascript
async function swapThroughChainflip () {
    const request = {
      sellAsset: "SOL.SOL",
      buyAsset: "BTC.BTC",
      sellAmount: "1",
      providers: ["CHAINFLIP", "CHAINFLIP_STREAMING"],
      sourceAddress: "sol_address",
      destinationAddress: "btc_address",
      slippage: 1.5,
    };

    const quoteResponse = await fetch('https://api.swapkit.dev/quote', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': apiKey,
        },
        body: JSON.stringify(request),
    });

    const [chainflipRoute, chainflipStreamingRoute] = await quoteResponse.json();

    // Pick deposit channel info from quote response

    const chainflipDepositChannelParams = chainflipRoute.meta.chainflip;

    // Use params to open deposit channel

    const depositChannelResponse = await fetch('https://api.swapkit.dev/chainflip/broker/channel', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': apiKey,
        },
        body: JSON.stringify(chainflipDepositChannelParams),
    });

    const depositChannel = await depositChannelResponse.json();

    
    const { depositAddress, explorerUrl } = depositChannel;
    // Send funds to deposit address
    ...

}
```


# /screen - Check AML compliance

### Endpoint

**Method:** `POST` \
**URL:** `https://api.swapkit.dev/screen`

The `/screen` endpoint is used to check the Anti-Money Laundering (AML) compliance status of cryptocurrency addresses. It evaluates the risk level of the provided addresses across different blockchain networks using external compliance providers.

We periodically update our own database of blacklisted addresses sourced from trusted providers, and any `/quote` request that involves addresses previously identified as non-compliant will not receive quotes. Despite that, you can choose to screen addresses after a quote to see if their status has changed.

The endpoint provides a compliance confirmation. Our own database is updated with the result if the address is considered risky and it won't receive further quotes.\
As an integrator, you can use this endpoint to check the addresses that interact with you.&#x20;

***

#### Request parameters

The request body should be a JSON object containing:

* `"addresses"`: A single address (string) or multiple addresses (array of strings).
* `"chains"`: The blockchain chain ID relevant to each address. You can [see them here](/swapkit-api/providers-providers-status-and-identifiers-mapping#chain-ids-and-corresponding-names), where we listed them before. They are standard identification for each chain.

{% tabs %}
{% tab title="One address" %}

```json
{
  "addresses": "0x2e1c9b2670802fDE14A789071b6703fe29bfbbFF",
  "chains": "1"
}
```

{% endtab %}

{% tab title="Multiple addresses" %}

```json
{
  "addresses": [
    "0x2e1c9b2670802fDE14A789071b6703fe29bfbbFF",
    "bc1qh56gtgsp3j088cwpzdezcg5lptnv869vh8jjf2"
  ],
  "chains": ["1", "bitcoin"]
}
```

{% endtab %}
{% endtabs %}

### Response

The response contains a boolean field named `"isRisky"` that indicates whether the provided addresses are flagged for anti-money laundering (AML) concerns.

* If the value is `"isRisky": false`, the addresses have passed the compliance check.
* If the value is `"isRisky": true`, at least one of the addresses has been flagged for AML concerns.

The response includes other parameters that internally help provide the source of the flag, but `"isRisky"` aggregates them.

### Important Considerations

1. Screening criteria:
   * The endpoint evaluates all addresses together and returns a single decision.
   * Even if one address is compliant, if any other address in the request is flagged the entire request may receive a `true` response.
2. Dynamic compliance checking:
   * Passing a compliance check at one point does not guarantee future compliance.
   * If an address was screened while it had no funds, it should be re-screened after receiving funds, as they may originate from tainted sources.
3. Decision responsibility:
   * SwapKit does not make the final decision on whether a trade is executed.
   * The integrator using this API is responsible for enforcing compliance policies based on the response.

\
This endpoint is not meant to be called on every `/quote`. Instead, you should only screen the addresses involved when the user has indicated intent to swap, before they sign the transaction. \
\
All addresses involved in the trade should be included in the request, and they should be re-checked if a new trade is offered or if the balances of the addresses have changed since they last passed AML compliance.

***

Here is an example request:

```sh
curl -X 'POST' \
  'https://api.swapkit.dev/screen' \
  -H 'Content-Type: application/json' \
  -H "x-api-key: YOUR_VARIABLE_HERE" \
  -d '{
    "addresses": [
      "0x2e1c9b2670802fDE14A789071b6703fe29bfbbFF",
      "bc1qh56gtgsp3j088cwpzdezcg5lptnv869vh8jjf2"
    ],
    "chains": ["1", "bitcoin"]
}'
```


