---
title: "Send stablecoins to an outside wallet"
description: "Send USDC or USDT from a Gravv wallet to an outside address on the same network, including Stellar addresses that need a memo."
---
Use this recipe to send USDC or USDT from a Gravv wallet to an outside address on the same network, including a Stellar address that needs a memo.

**Who it is for.** An import business that pays a supplier's USDC address on Polygon, and sometimes withdraws to a crypto exchange's Stellar address that needs a memo.

**Status.** Available on the same network. Not supported from avalanche or sui wallets, or on tron.

## Money example

1. The business's Polygon wallet holds 2,000.00 USDC.
2. The business sends 500.00 USDC to the supplier's Polygon address. The transfer response gives the fee in `fee` and `fee_currency`. Say the fee is 1.00.
3. The supplier receives 500.00 USDC, and the wallet balance drops by 501.00.

## Before you start

- The source wallet has the balance. [`GET /v1/wallets/{wallet_id}`](/api-reference/wallets/get-v1-wallets-wallet-id) shows `assets[]` with `symbol` `USDC` and a `balance` of at least the amount plus the fee. A missing asset means Gravv has not read the balance yet, not that the balance is zero.
- The wallet is **not** on avalanche or sui, because wallets on those networks cannot send.
- You have the destination address **and its network**, and, for an exchange, its memo.

## Find out what is required

1. **The customer's wallets and networks.** List them with their balances:

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/wallets?customer_id=c7ae0522-6a4d-476f-a43c-5a9aac863c31&include_balances=true' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/wallets?customer_id=c7ae0522-6a4d-476f-a43c-5a9aac863c31&include_balances=true", {
  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?customer_id=c7ae0522-6a4d-476f-a43c-5a9aac863c31&include_balances=true",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "items": [
      {
        "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
        "address": "0x2fa8509655a24d40634fdac5fc0f5468025c13b6",
        "network": "polygon",
        "name": "Supplier payouts",
        "date_created": "2026-09-02T08:14:03Z",
        "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
        "wallet_type": "customer",
        "assets": [
          {
            "symbol": "USDC",
            "balance": "2000.000000",
            "decimals": 6,
            "updated_at": "2026-10-13T08:55:00Z"
          },
          {
            "symbol": "USDT",
            "balance": "0.000000",
            "decimals": 6,
            "updated_at": "2026-10-13T08:55:00Z"
          }
        ]
      }
    ],
    "page": 1,
    "items_per_page": 10,
    "total_items": 1,
    "total_pages": 1,
    "has_next_page": false,
    "has_previous_page": false
  },
  "error": null
}
```

2. **The same network.** Ask the supplier which network the address is on. The same `0x` address exists on every EVM network, so the address alone does not tell you.
3. **The asset.** Send `source.crypto_asset` as `USDC` or `USDT`. Do not send a different `destination.crypto_asset`, because that makes the transfer a conversion. See [Convert USDT and USDC](/recipes/remittance-and-crypto/convert-usdt-and-usdc).

## Steps

### 1. Send

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: b9d76632-1364-4759-ae26-3e31b70a4314' \
     --data '
{
  "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
  "source": {
    "source_type": "internal_crypto_wallet",
    "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
    "crypto_asset": "USDC"
  },
  "destination": {
    "destination_type": "crypto_wallet",
    "wallet_address": "0xdf19d8699ab5aef6fb2d6cd81c18c86fe4b21475",
    "blockchain_network": "polygon"
  },
  "amount": "500.00",
  "description": "PO 7781",
  "client_reference": "imp-po-7781"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "b9d76632-1364-4759-ae26-3e31b70a4314",
  },
  body: JSON.stringify({
    "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
    "source": {
      "source_type": "internal_crypto_wallet",
      "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
      "crypto_asset": "USDC"
    },
    "destination": {
      "destination_type": "crypto_wallet",
      "wallet_address": "0xdf19d8699ab5aef6fb2d6cd81c18c86fe4b21475",
      "blockchain_network": "polygon"
    },
    "amount": "500.00",
    "description": "PO 7781",
    "client_reference": "imp-po-7781"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "b9d76632-1364-4759-ae26-3e31b70a4314",
    },
    json={
        "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
        "source": {
            "source_type": "internal_crypto_wallet",
            "id": "b6705e3a-3c6c-49b0-b24f-d55aafd03635",
            "crypto_asset": "USDC",
        },
        "destination": {
            "destination_type": "crypto_wallet",
            "wallet_address": "0xdf19d8699ab5aef6fb2d6cd81c18c86fe4b21475",
            "blockchain_network": "polygon",
        },
        "amount": "500.00",
        "description": "PO 7781",
        "client_reference": "imp-po-7781",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "500",
    "amount_currency": "USD",
    "date_created": "2026-10-13T14:00:01.410Z",
    "fee": "0",
    "fee_currency": "USD",
    "local_amount": null,
    "local_currency": null,
    "reference": "47afd450-561e-40a6-b658-8e96c180eb26",
    "requires_approval": false,
    "transaction_id": "b937af87-991a-4ca0-bb59-e9eda2bf6f7e",
    "transfer_status": "pending"
  },
  "error": null
}
```

In the response, `amount_currency` is `USD`, not `USDC`. The source can also be a wallet-backed account (`"source_type": "internal_account"` with the account id). Its network is the account's `blockchain_network`.

### 2. To an exchange's Stellar address, add the memo

From a Stellar wallet, add `blockchain_memo` exactly as the exchange shows it. Without it, the exchange cannot tell whose deposit it is.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: fd39e5c6-1b73-4f61-bb6f-7832523531e6' \
     --data '
{
  "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
  "source": {
    "source_type": "internal_crypto_wallet",
    "id": "81194af2-92eb-41cf-9667-94e26d069aa3",
    "crypto_asset": "USDC"
  },
  "destination": {
    "destination_type": "crypto_wallet",
    "wallet_address": "GCEKNUKI5N5TWQVPA7YHUSCWQPYPI2CHUUSB2Y4J2R2RLNWUMJG3LMYQ",
    "blockchain_network": "stellar",
    "blockchain_memo": "4820176"
  },
  "amount": "250.00",
  "client_reference": "imp-xlm-withdraw-0031"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "fd39e5c6-1b73-4f61-bb6f-7832523531e6",
  },
  body: JSON.stringify({
    "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
    "source": {
      "source_type": "internal_crypto_wallet",
      "id": "81194af2-92eb-41cf-9667-94e26d069aa3",
      "crypto_asset": "USDC"
    },
    "destination": {
      "destination_type": "crypto_wallet",
      "wallet_address": "GCEKNUKI5N5TWQVPA7YHUSCWQPYPI2CHUUSB2Y4J2R2RLNWUMJG3LMYQ",
      "blockchain_network": "stellar",
      "blockchain_memo": "4820176"
    },
    "amount": "250.00",
    "client_reference": "imp-xlm-withdraw-0031"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "fd39e5c6-1b73-4f61-bb6f-7832523531e6",
    },
    json={
        "customer_id": "c7ae0522-6a4d-476f-a43c-5a9aac863c31",
        "source": {
            "source_type": "internal_crypto_wallet",
            "id": "81194af2-92eb-41cf-9667-94e26d069aa3",
            "crypto_asset": "USDC",
        },
        "destination": {
            "destination_type": "crypto_wallet",
            "wallet_address": "GCEKNUKI5N5TWQVPA7YHUSCWQPYPI2CHUUSB2Y4J2R2RLNWUMJG3LMYQ",
            "blockchain_network": "stellar",
            "blockchain_memo": "4820176",
        },
        "amount": "250.00",
        "client_reference": "imp-xlm-withdraw-0031",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

### 3. Wait for the chain

Poll [`GET /v1/transactions/b937af87-991a-4ca0-bb59-e9eda2bf6f7e`](/api-reference/transactions/get-v1-transactions-transaction-id) until its status is `completed` or `failed`. A completed transaction has `tx_hash`. To find a transfer by its hash later, use `GET /v1/transactions?tx_hash=…`.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `the source account (polygon network) and destination (ethereum network) must be on the same blockchain network` | The networks differ | Use a wallet on the destination's network |
| 400 `transfers from avalanche accounts are not currently supported` (also sui) | Wallets on these networks cannot send | Move the funds to another network first |
| 400 `insufficient balance` from a tron or arbitrum wallet | These wallets have no tracked balance, so they cannot send | Send from a polygon, ethereum, solana or stellar wallet |
| 400 `destination.wallet_address is required for a crypto wallet transfer` or `destination.blockchain_network is required for a crypto wallet transfer` | A required field is missing | Send both |
| 400 `Both wallets must belong to the same customer to convert USDC to USDT.` | `destination.crypto_asset` differs from the asset sent | Leave `destination.crypto_asset` out. To send the other asset, convert first |
| 400 `the source wallet was not found` or `the source wallet is not active (status: …)` | The wallet id is wrong | Find the right id with `GET /v1/wallets` |
| 400 `insufficient balance` | The amount plus the fee is more than the balance | Fund the wallet |
| Accepted, then `transfer.status.failed` soon after with no `tx_hash` | `amount` had more than 2 decimal places | Send at most 2 decimal places |
| Accepted, then `transfer.status.failed` about 30 minutes later, and the fee refunded | The network refused the send because of a malformed address, an unsupported asset, or a Stellar address that has not enabled USDC. Gravv does not check the format of same-network addresses when you send | Fix the address, or ask the receiver to enable USDC on Stellar, then send again |
| `pending` with no progress | Gravv is still waiting for the chain's response | Wait. If it lasts unusually long, contact support with the `reference` |
| Sent to an exchange without the memo | The funds reached the exchange's shared address | Contact the exchange with the `tx_hash`. You cannot recall an on-chain send |

## Webhooks you will receive

1. `transfer.status.pending`, with `workflow_type` `crypto_transfer`, usually twice. Both events already carry `event_data.tx_hash`.
2. `transfer.status.completed` with `event_data.tx_hash`, or `transfer.status.failed`.

## Testing in sandbox

A normal transfer with a sandbox key settles at once with sandbox test dollars. For example, 2.00 USDC from a polygon wallet to an outside polygon address completed with a `tx_hash` within 3 seconds (`channel` `polygon`, `transaction_type` `crypto_transfer`).

Sandbox wallets are on test networks, and public faucet USDC does not show in their balance. A sandbox Stellar wallet could not be funded, so the memo send returns `insufficient balance` after its request checks pass.

## Reference

- [List wallets](/api-reference/wallets/get-v1-wallets)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
- [Crypto transfers](/platform/transfers/crypto-transfers)
