> ## Documentation Index
> Fetch the complete documentation index at: https://developers.circle.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# Quickstart: Transfer USDC on Solana

> Transfer USDC between wallets on Solana Devnet using a Node.js script

This guide demonstrates how to transfer USDC between two wallets on Solana
Devnet using a Node.js script. You use the
[Solana Kit](https://github.com/anza-xyz/kit) library to interact with the
Solana blockchain and perform SPL token transfers.

<Note>
  All transactions in this guide take place on the Solana Devnet testnet. No real
  funds are transferred. You can adapt this code for mainnet transactions by
  changing the RPC endpoint and using mainnet contract addresses.
</Note>

## Prerequisites

Before you start building the sample app to perform a USDC transfer, ensure you
have:

* **Node.js** and **npm** installed on your development machine. You can
  [download and install Node.js directly](https://nodejs.org), or use a version
  manager like [nvm](https://github.com/nvm-sh/nvm). The npm binary comes with
  Node.js.
* Created two Solana wallets: a sender wallet (with the private key available)
  and a receiver wallet (you only need the public address).
* Funded your sender wallet with the following testnet tokens:
  * Solana Devnet SOL (native token) from a
    [public faucet](https://faucet.solana.com/).
  * Solana Devnet USDC from the [Circle Faucet](https://faucet.circle.com).

## Part 1: Project setup

Set up your project environment and install the required dependencies.

### 1.1. Create a new project

Create a new directory and initialize a new Node.js project with default
settings:

```shell Shell theme={null}
mkdir solana-usdc-transfer
cd solana-usdc-transfer
npm init -y
npm pkg set type=module
```

### 1.2. Install dependencies

Install the required dependencies for Solana interactions:

```shell Shell theme={null}
npm install @solana/kit @solana-program/token ws dotenv
```

### 1.3. Configure environment variables

Create a `.env` file in your project directory and add your wallet private key
and the receiver address:

```shell Shell theme={null}
echo "SOLANA_PRIVATE_KEY=[your-solana-private-key-array]" > .env
echo "RECEIVER_ADDRESS=your-receiver-wallet-address" >> .env
```

The `SOLANA_PRIVATE_KEY` should be a JSON array of bytes representing your
private key. You can export this from most Solana wallets.

<Accordion title="Converting Base58 private key to JSON array">
  Some wallets export Solana private keys as Base58 encoded strings. If you have a
  Base58 encoded private key, you can convert it to a JSON array with the
  following Node.js script:

  ```javascript JavaScript theme={null}
  const fs = require("fs");
  const bs58 = require("bs58");
  const privateKeyBase58 = "YOUR_BASE58_PRIVATE_KEY";

  try {
    // Decode the Base58 string to a Uint8Array
    const privateKeyBytes = bs58.decode(privateKeyBase58);

    // The result is a Uint8Array. Convert it to a JSON array string.
    const privateKeyJsonString = JSON.stringify(Array.from(privateKeyBytes));

    console.log("JSON Array:", privateKeyJsonString);
  } catch (error) {
    console.error("Error converting key. Check if the Base58 key is valid.");
  }
  ```
</Accordion>

<Warning>
  This is strictly for testing purposes. **Never share your private keys** or
  commit them to version control.
</Warning>

## Part 2: Configure the script

Define the configuration constants for interacting with Solana Devnet.

### 2.1. Define configuration constants

The script predefines the RPC endpoints, USDC mint address, and transfer
parameters:

```javascript JavaScript theme={null}
import {
  address,
  createKeyPairSignerFromBytes,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  getAddressEncoder,
  getProgramDerivedAddress,
  getSignatureFromTransaction,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  signTransactionMessageWithSigners,
} from "@solana/kit";
import {
  TOKEN_PROGRAM_ADDRESS,
  getTransferInstruction,
  findAssociatedTokenPda,
} from "@solana-program/token";
import dotenv from "dotenv";

dotenv.config();

// Solana Devnet configuration
const SOLANA_RPC = "https://api.devnet.solana.com";
const SOLANA_WS = "wss://api.devnet.solana.com";
const rpc = createSolanaRpc(SOLANA_RPC);
const rpcSubscriptions = createSolanaRpcSubscriptions(SOLANA_WS);

// Parse Solana keypair from environment variable
const solanaPrivateKey = JSON.parse(process.env.SOLANA_PRIVATE_KEY);
const senderKeypair = await createKeyPairSignerFromBytes(
  Uint8Array.from(solanaPrivateKey),
);

// USDC Mint Address (Devnet)
const USDC_MINT = address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");

// Transfer parameters
const RECEIVER_ADDRESS = address(process.env.RECEIVER_ADDRESS);
const AMOUNT = 1_000_000n; // 1 USDC (6 decimals)
```

<Note>
  USDC uses 6 decimal places. To transfer 1 USDC, you specify `1_000_000` (1 \*
  10<sup>6</sup>) as the amount.
</Note>

## Part 3: Implement the transfer logic

The following sections outline the core transfer logic for sending USDC on
Solana.

### 3.1. Get token accounts

Derive the Associated Token Account (ATA) addresses for both the sender and
receiver. These accounts are program-derived addresses that hold SPL tokens for
a specific wallet:

```javascript JavaScript theme={null}
async function getTokenAccounts() {
  // Derive sender's USDC token account
  const [senderTokenAccount] = await findAssociatedTokenPda({
    mint: USDC_MINT,
    owner: senderKeypair.address,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
  });

  // Derive receiver's USDC token account
  const [receiverTokenAccount] = await findAssociatedTokenPda({
    mint: USDC_MINT,
    owner: RECEIVER_ADDRESS,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
  });

  return { senderTokenAccount, receiverTokenAccount };
}
```

### 3.2. Transfer USDC

Build and send the transfer transaction using the SPL Token program's transfer
instruction:

```javascript JavaScript theme={null}
async function transferUSDC() {
  console.log("Transferring USDC on Solana Devnet...");
  console.log(`From: ${senderKeypair.address}`);
  console.log(`To: ${RECEIVER_ADDRESS}`);
  console.log(`Amount: ${Number(AMOUNT) / 1_000_000} USDC`);

  const { senderTokenAccount, receiverTokenAccount } = await getTokenAccounts();

  // Create the transfer instruction
  const transferInstruction = getTransferInstruction({
    source: senderTokenAccount,
    destination: receiverTokenAccount,
    authority: senderKeypair,
    amount: AMOUNT,
  });

  // Get the latest blockhash for transaction lifetime
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

  // Build the transaction message
  const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(senderKeypair, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    (tx) => appendTransactionMessageInstruction(transferInstruction, tx),
  );

  // Sign and send the transaction
  const signedTransaction =
    await signTransactionMessageWithSigners(transactionMessage);

  const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
    rpc,
    rpcSubscriptions,
  });

  await sendAndConfirmTransaction(signedTransaction, {
    commitment: "confirmed",
  });

  const signature = getSignatureFromTransaction(signedTransaction);
  console.log(`\nTransaction successful!`);
  console.log(`Signature: ${signature}`);
  console.log(
    `View on explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`,
  );

  return signature;
}
```

## Part 4: Complete script

Create a `transfer.js` file in your project directory and populate it with the
complete code below.

```javascript transfer.js expandable theme={null}
import {
  address,
  createKeyPairSignerFromBytes,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  getSignatureFromTransaction,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  signTransactionMessageWithSigners,
} from "@solana/kit";
import {
  TOKEN_PROGRAM_ADDRESS,
  getTransferInstruction,
  findAssociatedTokenPda,
} from "@solana-program/token";
import dotenv from "dotenv";

dotenv.config();

// Solana Devnet configuration
const SOLANA_RPC = "https://api.devnet.solana.com";
const SOLANA_WS = "wss://api.devnet.solana.com";
const rpc = createSolanaRpc(SOLANA_RPC);
const rpcSubscriptions = createSolanaRpcSubscriptions(SOLANA_WS);

// Parse Solana keypair from environment variable
const solanaPrivateKey = JSON.parse(process.env.SOLANA_PRIVATE_KEY);
const senderKeypair = await createKeyPairSignerFromBytes(
  Uint8Array.from(solanaPrivateKey),
);

// USDC Mint Address (Devnet)
const USDC_MINT = address("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");

// Transfer parameters
const RECEIVER_ADDRESS = address(process.env.RECEIVER_ADDRESS);
const AMOUNT = 1_000_000n; // 1 USDC (6 decimals)

async function getTokenAccounts() {
  // Derive sender's USDC token account
  const [senderTokenAccount] = await findAssociatedTokenPda({
    mint: USDC_MINT,
    owner: senderKeypair.address,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
  });

  // Derive receiver's USDC token account
  const [receiverTokenAccount] = await findAssociatedTokenPda({
    mint: USDC_MINT,
    owner: RECEIVER_ADDRESS,
    tokenProgram: TOKEN_PROGRAM_ADDRESS,
  });

  return { senderTokenAccount, receiverTokenAccount };
}

async function transferUSDC() {
  console.log("Transferring USDC on Solana Devnet...");
  console.log(`From: ${senderKeypair.address}`);
  console.log(`To: ${RECEIVER_ADDRESS}`);
  console.log(`Amount: ${Number(AMOUNT) / 1_000_000} USDC`);

  const { senderTokenAccount, receiverTokenAccount } = await getTokenAccounts();

  // Create the transfer instruction
  const transferInstruction = getTransferInstruction({
    source: senderTokenAccount,
    destination: receiverTokenAccount,
    authority: senderKeypair,
    amount: AMOUNT,
  });

  // Get the latest blockhash for transaction lifetime
  const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

  // Build the transaction message
  const transactionMessage = pipe(
    createTransactionMessage({ version: 0 }),
    (tx) => setTransactionMessageFeePayerSigner(senderKeypair, tx),
    (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    (tx) => appendTransactionMessageInstruction(transferInstruction, tx),
  );

  // Sign and send the transaction
  const signedTransaction =
    await signTransactionMessageWithSigners(transactionMessage);

  const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
    rpc,
    rpcSubscriptions,
  });

  await sendAndConfirmTransaction(signedTransaction, {
    commitment: "confirmed",
  });

  const signature = getSignatureFromTransaction(signedTransaction);
  console.log(`\nTransaction successful!`);
  console.log(`Signature: ${signature}`);
  console.log(
    `View on explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`,
  );

  return signature;
}

transferUSDC().catch(console.error);
```

## Part 5: Test the script

Run the following command to execute the script:

```shell Shell theme={null}
node transfer.js
```

Once the script runs and the transfer is finalized, a confirmation message is
logged in the console:

```text theme={null}
Transferring USDC on Solana Devnet...
From: YourSenderAddress
To: YourReceiverAddress
Amount: 1 USDC

Transaction successful!
Signature: 5abc123...
View on explorer: https://explorer.solana.com/tx/5abc123...?cluster=devnet
```

You can verify the transaction by visiting the Solana Explorer link in the
output, or by checking the USDC balance in the receiver's wallet.

<Note>
  If the receiver's wallet has never held USDC before, you may need to create
  their Associated Token Account first. The transfer will fail if the destination
  token account does not exist. You can use the
  `getOrCreateAssociatedTokenAccount` pattern to handle this automatically.
</Note>
