> ## 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.

# Test Offramps

> How to test offramp flows in the sandbox environment

<Info>
  **Prerequisite:** You need a KYC-approved wallet with tokens and an active bank account on your organization.
  See [Onboarding Customers](/guides/onboarding) for the full onboarding flow.
</Info>

## Offramp Flow

<Steps>
  <Step title="Discover Assets">
    <Expandable title="Details">
      Either of these endpoints will give you the asset identifiers needed for quotes:

      * [GET /ramp/assets](/api-reference/assets/get-rampable-assets) — 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](/api-reference/lookup/get-all-stablebonds) — 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.

      <Warning>
        **Asset identifier format differs by chain:**

        * **Solana** — Base58 mint address (e.g. `AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q`)
        * **Stellar** — `CODE:ISSUER` format (e.g. `CETES:GC3CW7...`)
        * **EVM (Base, Polygon)** — Raw contract address only (e.g. `0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29`)

        Using `SYMBOL:0x...` on EVM chains will return `UnsupportedBlockchain`.
      </Warning>
    </Expandable>
  </Step>

  <Step title="Create a Quote — POST /ramp/quote">
    <Expandable title="Details">
      All orders require a quote. Quotes expire after **2 minutes**. For offramps, set `type: "offramp"` with the token identifier as `sourceAsset` and the fiat currency as `targetAsset`. See [POST /ramp/quote](/api-reference/quotes/get-quote-for-conversion) for the full schema.

      <CodeGroup>
        ```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": "base",
            "quoteAssets": {
              "type": "offramp",
              "sourceAsset": "0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29",
              "targetAsset": "MXN"
            },
            "sourceAmount": "50"
          }'
        ```

        ```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": "base",
            "quoteAssets": {
              "type": "offramp",
              "sourceAsset": "0x834df4C1d8f51Be24322E39e4766697BE015512F",
              "targetAsset": "MXN"
            },
            "sourceAmount": "50"
          }'
        ```
      </CodeGroup>

      <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](/overview#partner-fees) for details.
      </Tip>
    </Expandable>
  </Step>

  <Step title="Create an Order — POST /ramp/order">
    <Expandable title="Details">
      The `quoteId` carries the direction, blockchain, and amounts from your quote — no need to repeat them. The response includes `orderId` and a `burnTransaction` — a pre-built, unsigned transaction that burns the customer's tokens. See [POST /ramp/order](/api-reference/orders/create-a-new-order) for the full schema.

      <Warning>
        **Do not build your own transaction** unless using [anchor mode](#anchor-mode-stellar-only). Always use the `burnTransaction` provided by Etherfuse — it contains the correct parameters, amounts, and contract calls.
      </Warning>

      <CodeGroup>
        ```bash Sandbox theme={null}
        curl -X POST https://api.sand.etherfuse.com/ramp/order \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "orderId": "<uuid>",              # You generate this UUID
            "bankAccountId": "<bank_account_uuid>", # From onboarding
            "cryptoWalletId": "<wallet_uuid>",   # From onboarding
            "quoteId": "<quote_id_from_step_1>"  # From Step 2
          }'
        ```

        ```bash Production theme={null}
        curl -X POST https://api.etherfuse.com/ramp/order \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "orderId": "<uuid>",              # You generate this UUID
            "bankAccountId": "<bank_account_uuid>", # From onboarding
            "cryptoWalletId": "<wallet_uuid>",   # From onboarding
            "quoteId": "<quote_id_from_step_1>"  # From Step 2
          }'
        ```
      </CodeGroup>

      <Tip>
        The response includes a `statusPage` URL — e.g. `https://devnet.etherfuse.com/ramp/order/<order_uuid>`. Open it in a browser to sign the burn transaction and track progress without building wallet integration into your test harness.
      </Tip>
    </Expandable>
  </Step>

  <Step title="Sign the Burn Transaction">
    <Expandable title="Details">
      Unlike onramps, offramps require an **on-chain action**: the customer must sign and submit the burn transaction to release their tokens. You can handle this two ways:

      1. **In your app** — Deserialize the `burnTransaction` from the order response or `order_updated` webhook, sign it with the customer's wallet, and broadcast it to the network.
      2. **Via the status page** — Use the `statusPage` URL from the order response (e.g. `https://devnet.etherfuse.com/ramp/order/<order_uuid>`) to review and sign the transaction in the Etherfuse UI.
    </Expandable>
  </Step>

  <Step title="Verify Completion — GET /ramp/order/{order_uuid}">
    <Expandable title="Details">
      Once the burn is confirmed on-chain, the system detects it and initiates the fiat payout automatically. Status progression: `created` → `funded` (burn confirmed) → `completed` (fiat sent) → `finalized` (reversal window passed). See [GET /ramp/order](/api-reference/orders/get-order-details) for the full response schema.

      <Info>
        **No fiat simulation needed.** Unlike onramps, there is no `fiat_received` step for offramps. The sandbox processes the fiat payout automatically once the burn transaction is confirmed on-chain.
      </Info>

      <CodeGroup>
        ```bash Sandbox theme={null}
        curl -H "Authorization: <api_key>" \
          https://api.sand.etherfuse.com/ramp/order/<order_uuid>
        ```

        ```bash Production theme={null}
        curl -H "Authorization: <api_key>" \
          https://api.etherfuse.com/ramp/order/<order_uuid>
        ```
      </CodeGroup>
    </Expandable>
  </Step>
</Steps>

## Anchor Mode (Stellar Only)

For Stellar offramps, you can use **anchor mode** instead of the default burn transaction flow. In anchor mode, you build and submit the payment transaction yourself — Etherfuse provides the destination address and memo.

This is useful when you want full control over transaction construction, or when integrating with wallets that support [SEP-24](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md)-style withdrawal flows.

<Steps>
  <Step title="Create Order with useAnchor">
    <Expandable title="Details">
      Add `"useAnchor": true` to your order request. The quote must be for a **Stellar offramp**.

      <CodeGroup>
        ```bash Sandbox theme={null}
        curl -X POST https://api.sand.etherfuse.com/ramp/order \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "orderId": "<uuid>",
            "bankAccountId": "<bank_account_uuid>",
            "cryptoWalletId": "<wallet_uuid>",
            "quoteId": "<quote_id>",
            "useAnchor": true
          }'
        ```

        ```bash Production theme={null}
        curl -X POST https://api.etherfuse.com/ramp/order \
          -H "Authorization: <api_key>" \
          -H "Content-Type: application/json" \
          -d '{
            "orderId": "<uuid>",
            "bankAccountId": "<bank_account_uuid>",
            "cryptoWalletId": "<wallet_uuid>",
            "quoteId": "<quote_id>",
            "useAnchor": true
          }'
        ```
      </CodeGroup>

      The response includes anchor-specific fields instead of a `burnTransaction`:

      ```json theme={null}
      {
        "offramp": {
          "orderId": "...",
          "withdrawAnchorAccount": "GABPM7AXX...",
          "withdrawMemo": "RkFLRU1FTU8xMjM0NTY3ODkw...",
          "withdrawMemoType": "hash"
        }
      }
      ```
    </Expandable>
  </Step>

  <Step title="Build and Submit Payment">
    <Expandable title="Details">
      Build a Stellar payment transaction to the `withdrawAnchorAccount` with:

      * **Asset & amount** from your quote (e.g., USDC, XLM, or CETES)
      * **Memo** — decode `withdrawMemo` from base64, then attach as a `Memo.hash`

      ```javascript theme={null}
      const memoBytes = Buffer.from(withdrawMemo, "base64");
      const tx = new StellarSdk.TransactionBuilder(sourceAccount, { fee, networkPassphrase })
        .addOperation(StellarSdk.Operation.payment({
          destination: withdrawAnchorAccount,
          asset: sendAsset,
          amount: quoteAmount,
        }))
        .addMemo(StellarSdk.Memo.hash(memoBytes.toString("hex")))
        .setTimeout(300)
        .build();
      ```

      Sign and submit the transaction to the Stellar network.
    </Expandable>
  </Step>

  <Step title="Track Order Status">
    <Expandable title="Details">
      The rest of the flow is identical to standard offramps. Once the payment is detected on-chain, the order progresses through `created` → `funded` → `completed` → `finalized`.

      <Warning>
        Payments sent **without a memo** or with an **unrecognized memo** will be automatically refunded to the sender address.
      </Warning>
    </Expandable>
  </Step>
</Steps>

<Expandable title="Chain-specific quote examples">
  **Solana** — Use the Base58 mint address as `sourceAsset`:

  ```json theme={null}
  {
    "blockchain": "solana",
    "quoteAssets": {
      "type": "offramp",
      "sourceAsset": "AvvetPGuuB5FD5m86fpw3LtDKyQoUFT1mG9WarNQLW4q",
      "targetAsset": "MXN"
    }
  }
  ```

  **Stellar** — Use `CODE:ISSUER` format:

  ```json theme={null}
  {
    "blockchain": "stellar",
    "quoteAssets": {
      "type": "offramp",
      "sourceAsset": "CETES:GC3CW7...",
      "targetAsset": "MXN"
    }
  }
  ```

  **Base / Polygon** — Use the raw contract address:

  ```json theme={null}
  {
    "blockchain": "base",
    "quoteAssets": {
      "type": "offramp",
      "sourceAsset": "0xcC77c598d42f2f78Beb42C91d12B9d4041a5cE29",
      "targetAsset": "MXN"
    }
  }
  ```
</Expandable>

<Expandable title="Common errors">
  | Error                                          | Cause                                            | Fix                                                  |
  | ---------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------- |
  | `UnsupportedBlockchain`                        | Using `SYMBOL:0x...` format on an EVM chain      | Use the raw contract address only                    |
  | `Terms and conditions have not been completed` | Wallet not KYC-approved                          | Complete the [onboarding flow](/guides/onboarding)   |
  | `Quote not found or expired`                   | Quote TTL is 2 minutes                           | Create a fresh quote                                 |
  | `Bank account not found`                       | Bank account not owned by the requesting org     | Verify the bank account belongs to your organization |
  | `quote_id is required`                         | Missing quote — the quoteless path is deprecated | Create a quote via `POST /ramp/quote` first          |
</Expandable>
