Skip to main content
This guide sends 1.00 USDC on Solana devnet from an address that holds no SOL. Solana is the simplest scheme to start with: one signature over one message.

Before you start

  • A sandbox API key exported as PLATFORM_API_KEY. See Set up your account.
  • A test sender: a Solana keypair holding devnet USDC and no SOL. Get devnet USDC from Circle’s faucet. Export the base58 secret key as SENDER_SECRET_KEY. Use a key you made for testing only. The sender must already hold the token. A quote for an address that has never held USDC returns 400 invalid_request with “sender has no token account for this asset”.
  • A recipient address on devnet.
  • Node.js 20 or later, with @solana/web3.js, tweetnacl, and bs58 installed.
1

Request a quote

Create sponsored transfer takes the chain, asset, sender, recipient, and amount. It requires an Idempotency-Key. Use one per transfer, so a retry returns the same quote instead of creating a second one.
The recipient gets 1 and the sender is debited 1.004. The fee here is an example: use the one in your quote. Before going on, check that sign.scheme is solana_ed25519.
2

Show the user what they're signing

In a real app, show recipient_amount, fee, total_debit, and the recipient before you ask for a signature, and get the user’s confirmation. Request the quote when they’re ready to confirm: it expires at expires_at, about three minutes later.
3

Sign the message

Decode message_base64 and sign the raw bytes with the sender’s key. Don’t change or hash the message first.
sign.mjs
In production the user’s wallet does this step. Test your wallet integration in sandbox first: it must sign the bytes exactly as given, without adding a prefix.
4

Submit the signature

Submit signatures takes the base58 signature as signature. It also requires an Idempotency-Key.
A 202 means Bitnob verified the signature, added its own, and queued the transfer for broadcast. A 422 signature_invalid means the signature didn’t match. The quote is still valid, so sign again and resubmit.
5

Track it to completed

Poll Get sponsored transfer until the status is terminal.
The status moves from validating to submitted to pending, and then to completed. Once it’s pending, tx_hash is set and you can look the transaction up on a devnet explorer. The sender’s SOL balance is still zero.

Put it together

transfer.mjs
Sponsored transfers aren’t in the list of webhook event types yet, so polling is how you track them. Back off as the transfer ages: every few seconds while pending, and stop at a terminal status.

Next steps