> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Swaps

> How to test token-to-token swap flows in the sandbox environment

> [!NOTE]
>
> **Prerequisite:** You need a KYC-approved wallet. Register one with [POST /ramp/wallet](https://docs.etherfuse.com/api-reference/wallets/register-wallet.md) (set `claimOwnership: true` to inherit your organization's KYB approval), or onboard a customer wallet via [KYC WebSDK](https://docs.etherfuse.com/guides/kyc-websdk.md). Swaps do not require a bank account.
> For onboarding your customers, see [KYC Overview](https://docs.etherfuse.com/guides/onboarding.md).

> [!WARNING]
>
> Swaps are currently available on **Stellar**, **Solana**, and **Monad**, and the source and destination must be on the **same chain** (no cross-chain swaps). The other EVM chains (Base, Polygon, Avalanche) support direct on/off-ramps but do not yet support token-to-token swaps.

> [!NOTE]
>
> **Stellar swaps need a funded, trustlined wallet.** The wallet must already hold XLM and a trustline for the asset it will receive — otherwise Etherfuse can't build the swap transaction and no `swap_updated` webhook fires. See the [Stellar](https://docs.etherfuse.com/guides/chains/stellar.md#offramps-and-swaps-fund-the-wallet-first), [Solana](https://docs.etherfuse.com/guides/chains/solana.md), and [Monad](https://docs.etherfuse.com/guides/chains/monad.md) chain guides for per-chain setup.

## Swap Flow

**1. Discover Assets**

**Details**

Either of these endpoints will give you the asset identifiers needed for quotes:

* [GET /ramp/assets](https://docs.etherfuse.com/api-reference/assets/list-assets.md) — Requires auth. Returns all rampable stablecoins and stablebonds for the specified blockchain. The `currency` parameter controls sort priority (matching assets appear first). Best for building integrations where you already know the customer's wallet.

* [GET /lookup/stablebonds](https://docs.etherfuse.com/api-reference/lookup/list-stablebonds.md) — Public, no auth. Returns all stablebonds across all chains with pricing and supply data. Best for exploring what's available before onboarding a customer.

Use the `identifier` from either response as the asset value in your quote.

**Swap constraint:** A swap must involve at least one stablebond; the other side can be a stablecoin such as USDC, so both `USDC → CETES` and the reverse `CETES → USDC` are valid. Both assets must be stable, so non-stable tokens (SOL, XLM) return `Non-stable assets are not supported`, and stablecoin-to-stablecoin swaps (`USDC → USDT`) are not supported.

> [!WARNING]
>
> **Asset identifier format differs by chain:**
>
> * **Solana** — Base58 mint address (e.g. `AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q`)
> * **Stellar** — `CODE:ISSUER` format (e.g. `CETES:GC3CW7...`)
> * **Monad** — raw token contract address only (e.g. `0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29`)
>
> Using `SYMBOL:0x...` (Stellar format) on other chains will return `UnsupportedBlockchain`.

> [!TIP]
>
> **USDC addresses for swap sources:**
>
> * **Stellar sandbox:** `USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5`
> * **Stellar production:** `USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`
> * **Solana sandbox:** `4D4r1KuS9WxbPCzzAJnWtYUJSSzDEZzrF1CbwajWGVjg`
> * **Solana production:** `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`

**2. Create a Swap Quote — POST /ramp/quote**

**Details**

Quotes expire after **2 minutes**. For swaps, set `type: "swap"` with the source token identifier as `sourceAsset` and the stablebond identifier as `targetAsset`. The response includes `requiresSwap: true` and the `exchangeRate` between the two assets. See [POST /ramp/quote](https://docs.etherfuse.com/api-reference/quotes/get-quote.md) for the full schema.

```bash Sandbox theme={null}
curl -X POST https://api.sand.etherfuse.com/ramp/quote \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "<uuid>",        # You generate this UUID
    "customerId": "<org_uuid>",  # From onboarding
    "blockchain": "stellar",
    "quoteAssets": {
      "type": "swap",
      "sourceAsset": "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
      "targetAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
    },
    "sourceAmount": "10"
  }'
```

```bash Production theme={null}
curl -X POST https://api.etherfuse.com/ramp/quote \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "quoteId": "<uuid>",        # You generate this UUID
    "customerId": "<org_uuid>",  # From onboarding
    "blockchain": "stellar",
    "quoteAssets": {
      "type": "swap",
      "sourceAsset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
      "targetAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
    },
    "sourceAmount": "10"
  }'
```

> [!TIP]
>
> **Partner fees:** If your organization has a partner fee configured, it is applied automatically. To override per-quote, add `"partnerFeeBps": 100` (0–500) to the request body. See [Partner Fees](https://docs.etherfuse.com/fees.md#partner-fees) for details.

**3. Create the Swap — POST /ramp/swap**

**Details**

Unlike orders, the swap endpoint requires `blockchain` and `publicKey` in the request body (these are not derived from the quote). The endpoint returns an **empty 200** — all swap progress is delivered via webhooks. See [POST /ramp/swap](https://docs.etherfuse.com/api-reference/swaps/swap-assets.md) for the full schema.

```bash Sandbox theme={null}
curl -X POST https://api.sand.etherfuse.com/ramp/swap \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",               # You generate this UUID
    "quoteId": "<quote_uuid_from_step_2>", # From Step 2
    "publicKey": "<wallet_public_key>",  # From onboarding
    "blockchain": "stellar"
  }'
```

```bash Production theme={null}
curl -X POST https://api.etherfuse.com/ramp/swap \
  -H "Authorization: <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "orderId": "<uuid>",               # You generate this UUID
    "quoteId": "<quote_uuid_from_step_2>", # From Step 2
    "publicKey": "<wallet_public_key>",  # From onboarding
    "blockchain": "stellar"
  }'
```

> [!NOTE]
>
> The `orderId` here is a swap identifier you generate — it tracks this swap through webhooks. The optional `targetWallet` field lets you send the swapped tokens to a different wallet than `publicKey`.

**4. Sign the Transaction**

**Details**

After creating the swap, you'll receive a `swap_updated` webhook with the `sendTransaction` — a pre-built, encoded transaction ready to sign.

```json theme={null}
{
  "swap_updated": {
    "orderId": "<swap_uuid>",
    "customerId": "<org_uuid>",
    "sendTransaction": "AQAAAA...encoded_transaction",
    "status": "pending"
  }
}
```

Sign the `sendTransaction` using a wallet adapter and submit it to the testnet blockchain. Once confirmed, you'll receive another `swap_updated` webhook with `status: "completed"`.

> [!WARNING]
>
> **The first webhook is a call to action, not a progress notification.** Etherfuse has partially signed `sendTransaction`, but the swap does not advance until the customer's wallet adds its signature and submits it on-chain. Until then the swap stays at `pending` and no further webhooks fire.

> [!WARNING]
>
> **Do not build your own transaction.** Always use the `sendTransaction` provided in the webhook. This transaction is pre-built by Etherfuse with the correct swap parameters.

> [!TIP]
>
> **If the transaction expires before you submit it**, fetch a fresh one with [POST /ramp/swap/\{order\_id}/regenerate\_tx](https://docs.etherfuse.com/api-reference/swaps/regenerate-swap-tx.md) — the new `sendTransaction` arrives over the `swap_updated` webhook. (Stellar transactions expire in \~1–2 minutes.) `sendTransaction` is only present while funds are still owed; after they're received you instead get `sendTransactionHash` and `receiveTransactionHash`.

**5. Verify Completion**

**Details**

Monitor swap progress via `swap_updated` webhooks. The `status` field progresses `pending` → `funds_received` → `completed` (or `failed`). Register a webhook for the `swap_updated` event type via [POST /ramp/webhook](https://docs.etherfuse.com/api-reference/webhooks/create-webhook.md).

> [!WARNING]
>
> **`funds_received` fires on Stellar and Monad only.** On Solana the swap goes straight from `pending` to `completed`, so treat the middle event as optional rather than gating your state machine on it.

> [!NOTE]
>
> Unlike on/off-ramps, there is no polling endpoint for swap status; webhooks are the only way to track progress. Swaps are not stored as orders, so calling [GET /ramp/order/\{order\_id}](https://docs.etherfuse.com/api-reference/orders/get-order.md) with a swap `orderId` returns `404 Order not found`. The `swap_updated` payload carries everything the UI needs.

## Interactive Tutorial

Use the interactive tutorial below to test the swap flow with your sandbox API key. Each step builds on the previous one, and you can edit the request bodies before sending.

### Interactive Swap Tutorial

Sandbox only

Host:
`https://api.sand.etherfuse.com`

API Key

Find it under 'API Keys' at
[sandbox.etherfuse.com/ramp](https://sandbox.etherfuse.com/ramp?view=manage)

Your API key is stored in your browser's session storage and cleared when you close the tab.

Step 1
Select Customer & Wallet

Select the customer and wallet for the swap. Data is auto-loaded when you enter your API key.

Step 2
Select Assets

Choose which assets to swap. You can use the public lookup endpoint or get personalized opportunities via the ramp/assets endpoint.

Step 3
POST
/ramp/quote

Generate a quote for the swap.
[View API reference →](https://docs.etherfuse.com/api-reference/quotes/create-quote)

Step 4
POST
/ramp/swap

Execute the swap with the quote from the previous step.
[View API reference →](https://docs.etherfuse.com/api-reference/swaps/create-swap)

**Chain-specific swap examples**

**Stellar** — USDC to CETES:

```json theme={null}
{
  "blockchain": "stellar",
  "quoteAssets": {
    "type": "swap",
    "sourceAsset": "USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "targetAsset": "CETES:GC3CW7EDYRTWQ635VDIGY6S4ZUF5L6TQ7AA4MWS7LEQDBLUSZXV7UPS4"
  }
}
```

**Solana** — USDC to CETES:

```json theme={null}
{
  "blockchain": "solana",
  "quoteAssets": {
    "type": "swap",
    "sourceAsset": "4D4r1KuS9WxbPCzzAJnWtYUJSSzDEZzrF1CbwajWGVjg",
    "targetAsset": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q"
  }
}
```

**Common errors**

| Error | Cause | Fix |
| - | - | - |
| `Swaps must involve at least one stablebond asset` | Neither side is a stablebond (e.g. a stablecoin-to-stablecoin swap) | Make at least one side a stablebond — use identifiers from `/lookup/stablebonds` |
| `Non-stable assets are not supported` | A side is a non-stable token (SOL, XLM, ETH) | Both sides must be a stablebond or a supported stablecoin |
| `UnsupportedBlockchain` | Swaps not yet available on this chain, or using wrong identifier format | Swaps are currently available on Stellar, Solana, and Monad. Check your asset identifier format. |
| `Quote not found or expired` | Quote TTL is 2 minutes | Create a fresh quote |
| `Terms and conditions have not been completed` | Wallet not KYC-approved | Complete the [onboarding flow](https://docs.etherfuse.com/guides/onboarding.md) |
| `Temporarily unable to get a valid quote for this trading path` (`424`) | Sandbox liquidity for that path is thin right now, not a direction restriction (reverse `stablebond → USDC` swaps are valid) | Transient. Retry with backoff. See [Errors](https://docs.etherfuse.com/errors.md#status-codes). |
| `Unexpected` | Swap provider (Jupiter/SDEX) returned an error | Check that the source asset has testnet liquidity against the target |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.