Crypto transfers
Send USDC or USDT to a crypto address, move USDC to another network, and convert between USDT and USDC.
This page shows how to send stablecoins on-chain, move USDC to another network, and convert between USDT and USDC. A crypto transfer sends a stablecoin on-chain from a customer's Gravv wallet or wallet-backed account. This table shows what you can send today:
| What | Status |
|---|---|
| USDC or USDT to an address on the same network | Available |
| USDC to a wallet on a different network | Not yet available. Ask support to confirm that Gravv has switched it on for your business |
| USDT to USDC, or back, between two wallets of the same customer | Available. No conversion has completed in production yet, so always run the price preview first |
| USDT to a different network | Not supported |
| Sending from an Avalanche or Sui wallet or account | Not supported |
| Anything on Tron | Not supported |
Example. A business's Polygon wallet holds 2,000.00 USDC. The business sends 500.00 USDC to a supplier's Polygon address. 500.00 USDC arrives, and Gravv debits the wallet 500.00 USDC plus the fee on your plan.
Same network
The destination must be on the same network as the sending wallet. The same 0x address exists on every EVM network, so ask the receiver which network the address is on.
curl --request POST \
--url https://api.gravv.xyz/v1/transfer \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 8c3f1e7a-2d5b-4c9e-a1f0-6b7c8d9e0f73' \
--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"
}
'
{
"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
}
- Send
source.crypto_asset(USDCorUSDT). Without it, Gravv sends USDT from a Tron wallet and USDC from a wallet on any other network. - Do not send a
destination.crypto_assetthat differs from the asset sent. A different asset means "convert", and a conversion works only between two wallets of the same customer. - For a Stellar exchange address, add
destination.blockchain_memoexactly as the exchange shows it. Without it, the exchange cannot tell whose deposit it is, and Gravv cannot recall it. - The source can also be a wallet-backed account (
"source_type": "internal_account"). The transfer then uses the account's network. amount_currencyandfee_currencyreadUSDeven when a USDC wallet pays.- Gravv does not check the format of a same-network address when you send. The network refuses a malformed address, an unsupported asset, or a Stellar address that has not enabled USDC. The transfer then fails within about 30 minutes, and Gravv refunds the fee.
- When it completes, the transaction has a
tx_hash. Find a transfer by hash withGET /v1/transactions?tx_hash=<hash>.
USDC to another network
Send USDC from a Gravv wallet on Polygon, Ethereum, Solana or Stellar to a wallet on Polygon, Ethereum, Solana, Stellar or Avalanche. The destination can be an outside address (crypto_wallet) or any Gravv wallet of your customers (internal_crypto_wallet with its id):
curl --request POST \
--url https://api.gravv.xyz/v1/transfer \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 3e9a6c2d-7f1b-4e8a-b5c4-2d3e4f5a6b84' \
--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": "76YbQbwcPcp8MTy6pP8efkicPhTUfYYxeEPuCQmo46K1",
"blockchain_network": "solana"
},
"amount": "1000.00",
"description": "October contract",
"client_reference": "pay-ctr-1009"
}
'
-
The address must match its network:
- On Polygon, Ethereum and Avalanche, the address is
0xand 40 hex characters. - On Solana, it is a base58 address.
- On Stellar, it is a
G…account. Gravv refuses a muxedM…address.
Gravv refuses a wrong format when you send, and the error names the network.
- On Polygon, Ethereum and Avalanche, the address is
-
A Stellar receiver must already accept USDC (a one-time "trustline" setting in the receiving wallet). Without it, the last step cannot complete, and the transfer goes to manual review.
-
The move has several on-chain steps, so Gravv sends several
transfer.status.pendingevents beforetransfer.status.completed. -
Send at most 2 decimals. Today Gravv does not refuse a cross-network move with more decimals. It moves the amount rounded to 2 decimals.
Convert USDT and USDC
A conversion moves one stablecoin out of a customer's wallet and the other into another wallet of the same customer. Price it first with the preview. The preview reserves and charges nothing:
curl --request GET \
--url 'https://api.gravv.xyz/v1/transfer/rates?source_wallet_id=b6705e3a-3c6c-49b0-b24f-d55aafd03635&destination_wallet_id=5485cc5d-3d4b-4b17-9033-95e408405f03&send_asset=USDC&receive_asset=USDT&amount=2.00' \
--header 'Api-Key: <API_KEY>'
{
"data": {
"items": [
{
"currency_pair": "USDC_USDT",
"from_currency": "USDC",
"to_currency": "USDT",
"rate": "0.249132",
"direction": "USDC_USDT",
"timestamp": "2026-10-12T13:40:00Z",
"send_amount": "2",
"fee": "1.501737",
"fee_currency": "USDC",
"receive_amount": "0.498263",
"min_receive_amount": "0.498263",
"source_network": "polygon",
"destination_network": "ethereum",
"expires_at": "2026-10-12T13:41:00Z"
}
]
},
"error": null
}
receive_amount is what should arrive, and min_receive_amount is the least that may arrive. Gravv takes the fee out of what arrives and never charges it on top. Part of the fee works like a fixed cost. In this example, 2.00 USDC brings about 0.50 USDT, so convert larger amounts. The preview is not binding. Gravv fetches the price again when you send.
To convert, send a transfer whose destination.crypto_asset differs from the asset sent:
curl --request POST \
--url https://api.gravv.xyz/v1/transfer \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 9b4d7e3f-1a6c-4f2b-8d9e-3c4d5e6f7a95' \
--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": "internal_crypto_wallet",
"id": "5485cc5d-3d4b-4b17-9033-95e408405f03",
"crypto_asset": "USDT"
},
"amount": "2.00",
"description": "Convert USDC to USDT"
}
'
The response adds receive_amount, min_receive_amount and receive_asset. The transfer.status.completed webhook says what arrived (receive_amount) and what the converter kept (provider_fee). For a failed conversion, transfer.status.failed carries refunded_amount and refund_asset.
You cannot convert and send in one call. To pay the converted funds to someone else, send a same-network transfer from the receiving wallet afterwards.
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, or the source of a cross-network move is an account rather than a wallet | Use a wallet on the destination's network, or move USDC across networks from an internal_crypto_wallet |
400 transfers from avalanche accounts are not currently supported |
Avalanche and Sui wallets and accounts cannot send | Move the funds to another network first |
400 transfers between different blockchain networks support USDC only, on avalanche, ethereum, polygon, solana and stellar |
You sent USDT, or used a network outside the list | Use USDC on a listed network. Convert USDT first |
400 destination.wallet_address is not a valid solana address |
The address does not match the network | Get the right address for that network |
400 destination.wallet_address is required for a crypto wallet transfer |
A field is missing | Send wallet_address and blockchain_network |
400 Both wallets must belong to the same customer to convert USDC to USDT. |
A conversion between different customers, or destination.crypto_asset set on a send to someone else |
Convert between one customer's wallets, then send |
400 We couldn't get a price right now. Please try again shortly. |
The converter is unavailable | Retry later with a new Idempotency-Key |
400 The amount is too small to convert. Try a larger amount. |
The fee would use up the amount | Convert more |
400 Use at most 2 decimal places. |
The conversion amount has more than 2 decimals, such as 2.005 |
Round to 2 decimals |
400 insufficient balance |
The amount plus the fee is more than the balance. Wallets on Tron and Arbitrum have no tracked balance, so they cannot send | Fund the wallet, or send from Polygon, Ethereum, Solana or Stellar |
pending, then transfer.status.failed about 30 minutes later, with the fee refunded |
The network refused the send | Fix the address or ask the receiver to enable USDC on Stellar, then send again with a new key |
Webhook transfer.status.failed, transaction still pending |
The transfer is in manual review, for example because a Stellar receiver has not enabled USDC | Do not send it again. Contact support with the reference |