---
title: "Sell USDC for naira into your bank account"
description: "Save your Nigerian bank account as a settlement instruction, then sell USDC from your wallet and have Gravv pay the naira to that account."
---
Use this recipe to save your Nigerian bank account as a settlement instruction, then sell USDC from your wallet and have Gravv pay the naira to that account. A settlement instruction is a saved record of where Gravv sends the currency you buy.

**Who it is for.** A crypto business that holds USDC on Gravv and needs naira in its own Nigerian bank account to pay local costs.

**Status.** Available once Gravv sets an OTC rate for your business.

## Money example

Your treasury wallet holds 5,000 USDC.

1. You save your bank account once as an NGN settlement instruction. This tells Gravv where to send your naira.
2. You get a quote to sell 1,000 USDC for NGN at a rate of 1,495.5. You receive 1,495,500 NGN.
3. You place the order with the instruction as the destination. 1,000 USDC leaves your wallet at once.
4. Gravv pays 1,495,500 NGN to your bank account, and the order status becomes `completed`.

## Before you start

- A USDC wallet or USDC account that belongs to your business and holds at least the amount you sell.
- An `active` settlement instruction whose `currency` is the currency you buy, NGN. You can create it in step 1.
- If you use a scoped key, it needs `fx:read`, `fx:order_create`, and `payee:create` to create the instruction.

## Find out what is required

First list your NGN instructions, so you can reuse one if it exists. The `currency` filter is exact and case-sensitive.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/settlement-instructions?currency=NGN' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/settlement-instructions?currency=NGN", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/settlement-instructions?currency=NGN",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

If a field is missing, the create call names it in the error. The order call checks that the instruction belongs to your business, is active, and is in the currency you buy.

## Steps

### 1. Save the bank account, once

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/settlement-instructions \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: b6f21d66-187b-4641-b121-dca35c4fd5c9' \
     --header 'Content-Type: application/json' \
     --data '
{
  "instruction_type": "bank_account",
  "account_name": "Harbor Digital Assets Ltd",
  "currency": "NGN",
  "is_default": true,
  "account_number": "3012345678",
  "bank_name": "Example Bank",
  "bank_code": "011",
  "country": "NG"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/settlement-instructions", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "b6f21d66-187b-4641-b121-dca35c4fd5c9",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "instruction_type": "bank_account",
    "account_name": "Harbor Digital Assets Ltd",
    "currency": "NGN",
    "is_default": true,
    "account_number": "3012345678",
    "bank_name": "Example Bank",
    "bank_code": "011",
    "country": "NG"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/settlement-instructions",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "b6f21d66-187b-4641-b121-dca35c4fd5c9",
        "Content-Type": "application/json",
    },
    json={
        "instruction_type": "bank_account",
        "account_name": "Harbor Digital Assets Ltd",
        "currency": "NGN",
        "is_default": True,
        "account_number": "3012345678",
        "bank_name": "Example Bank",
        "bank_code": "011",
        "country": "NG",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

The API returns HTTP 200 with the instruction. In this example its `id` is `7e0161a9-34d6-4935-b764-d9eb3a7825f7` and its `status` is `active`.

Gravv does **not** check these bank details with any bank. Read them back to the account owner to confirm them. Send the currency in upper case.

### 2. Check the USDC balance

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/wallets/b74d0fb1-32e7-4629-8fad-c1a606cb0fb3 \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/wallets/b74d0fb1-32e7-4629-8fad-c1a606cb0fb3", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/wallets/b74d0fb1-32e7-4629-8fad-c1a606cb0fb3",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

Check that the USDC `balance` is at least 1000.

### 3. Get a quote

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/fx/quote \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: a339813d-6ce1-4d8e-b105-ac6fac7deedd' \
     --header 'Content-Type: application/json' \
     --data '
{
  "currency_pair": "USDC_NGN",
  "amount": "1000"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/fx/quote", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "a339813d-6ce1-4d8e-b105-ac6fac7deedd",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "currency_pair": "USDC_NGN",
    "amount": "1000"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/fx/quote",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "a339813d-6ce1-4d8e-b105-ac6fac7deedd",
        "Content-Type": "application/json",
    },
    json={
        "currency_pair": "USDC_NGN",
        "amount": "1000",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "quote_id": "GRVOTC_C19E04",
    "from_currency": "USDC",
    "to_currency": "NGN",
    "to_amount": "1495500",
    "from_amount": "1000",
    "rate": "1495.5",
    "expires_at": "2026-10-13T11:05:00Z"
  },
  "error": null
}
```

### 4. Place the order to the instruction

Set the destination to the instruction. Here `external_account` means a **settlement instruction**, never a payee.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/fx/orders \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 7094c507-fe4d-434c-b98b-daff98c595e1' \
     --header 'Content-Type: application/json' \
     --data '
{
  "quote_id": "GRVOTC_C19E04",
  "source": {
    "type": "internal_crypto_wallet",
    "id": "b74d0fb1-32e7-4629-8fad-c1a606cb0fb3"
  },
  "destination": {
    "type": "external_account",
    "id": "7e0161a9-34d6-4935-b764-d9eb3a7825f7"
  },
  "client_reference": "treasury-ngn-2026-10-13"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/fx/orders", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "7094c507-fe4d-434c-b98b-daff98c595e1",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "quote_id": "GRVOTC_C19E04",
    "source": {
      "type": "internal_crypto_wallet",
      "id": "b74d0fb1-32e7-4629-8fad-c1a606cb0fb3"
    },
    "destination": {
      "type": "external_account",
      "id": "7e0161a9-34d6-4935-b764-d9eb3a7825f7"
    },
    "client_reference": "treasury-ngn-2026-10-13"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/fx/orders",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "7094c507-fe4d-434c-b98b-daff98c595e1",
        "Content-Type": "application/json",
    },
    json={
        "quote_id": "GRVOTC_C19E04",
        "source": {
            "type": "internal_crypto_wallet",
            "id": "b74d0fb1-32e7-4629-8fad-c1a606cb0fb3",
        },
        "destination": {
            "type": "external_account",
            "id": "7e0161a9-34d6-4935-b764-d9eb3a7825f7",
        },
        "client_reference": "treasury-ngn-2026-10-13",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "order_id": "6b65a6a4-8b81-48f6-b38a-088ca65ed389",
    "quote_id": "GRVOTC_C19E04",
    "status": "pending"
  },
  "error": null
}
```

### 5. Follow it

Poll `GET /v1/fx/orders/6b65a6a4-8b81-48f6-b38a-088ca65ed389` until the order status is `completed`. Then use the order's `destination_amount` and `client_reference` to match the naira payment on your bank statement.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `instruction type must be "bank_account" or "crypto_wallet": invalid input` | `instruction_type` is missing or not in lower case | Send it in lower case |
| 400 `account name is required: invalid input` / `currency is required: invalid input` | A required field is missing | Add the missing field |
| 400 `account number is required for bank accounts: invalid input` / `bank name is required for bank accounts: invalid input` | A bank instruction is missing one or both of these fields | Add both |
| 400 `bad destination: [400] invalid order destination` | The `id` is a payee id, an unknown or deleted instruction, another business's instruction, or an instruction that is not in the currency you buy | Use an `active` instruction whose `currency` is the quote's `to_currency` |
| 400 `invalid order destination` | `destination.type` is `ssi`, `account` or `wallet`, or the destination has no `id` | Send `{"type": "external_account", "id": "<instruction id>"}` |
| The list with `currency=ngn` is empty | The filter is exact and case-sensitive | Use the stored case, `NGN` |
| Two instructions for one bank account | Gravv does not check for duplicates | Reuse one and delete the other |
| Any quote or order error | Another page lists these errors | See [Convert naira to USDC](/recipes/fx/convert-naira-to-usdc#what-can-go-wrong) |

## Webhooks you will receive

None. Gravv sends no webhooks for settlement instructions or FX orders.

## Testing in sandbox

Settlement instructions work the same in sandbox as in live, because they are records only. To place an order, your sandbox business needs an OTC rate. Without one, the quote call returns `quote currency pair is not supported`. No real bank payment happens in sandbox.

## Reference

- [Manage settlement instructions](/platform/settlement-instructions/manage-settlement-instructions)
- [Place an OTC order](/platform/foreign-exchange/place-an-otc-order)
