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.
USDC on Stellar is a native asset issued by Circle. Transferring USDC between
Stellar accounts uses a standard payment operation, submitted through
Horizon, Stellar’s HTTP
API for submitting transactions. To transfer USDC crosschain between Stellar and
other blockchains, see
Transfer USDC to and from Stellar.
The @stellar/stellar-sdk script
you build in this guide will:
- Create and fund a recipient Stellar Testnet wallet
- Establish a USDC trustline on the recipient account
- Transfer USDC from your existing sender wallet to the recipient
Before a Stellar account can receive USDC, it must establish a trustline for the
asset. See
Set up a USDC trustline on Stellar
for more detail. This quickstart handles trustline setup automatically as part
of the script.
Prerequisites
Before you begin, ensure that you have:
- Installed Node.js v22+
- Set up a terminal and code editor for running commands and editing files
- Created a Stellar Testnet wallet with the secret key (
S...) available
Contract addresses
You need the following Stellar Testnet USDC issuer address:
Step 1: Set up the project
1.1. Create the project and install dependencies
Create a new directory and install the required dependencies:
# Set up your directory and initialize a Node.js project
mkdir stellar-usdc-transfer
cd stellar-usdc-transfer
npm init -y
# Set up module type and start command
npm pkg set type=module
npm pkg set scripts.start="npx tsx --env-file=.env main.ts"
# Install runtime dependencies
npm install @stellar/stellar-sdk
# Install dev dependencies
npm install --save-dev typescript @types/node
This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:
Then, update the tsconfig.json file:
cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
Create a .env file in your project directory and add your sender’s secret key:
STELLAR_SECRET_KEY=YOUR_SENDER_SECRET_KEY
Open .env in your editor rather than writing values with shell commands, and
add .env to your .gitignore. This prevents credentials from leaking into
your shell history or version control.
This example uses one or more private keys for local testing. In production,
use a secure key management solution and never expose or share private keys.
Step 2: Create the transfer script
Add main.ts at the project root. The script:
- Loads your existing sender wallet from the environment variable
- Creates and funds a new recipient wallet using Friendbot
- Establishes a USDC trustline on the recipient account
- Submits a USDC payment from the sender to the recipient
import {
Asset,
BASE_FEE,
Horizon,
Keypair,
Networks,
Operation,
TransactionBuilder,
} from "@stellar/stellar-sdk";
const HORIZON_URL = "https://horizon-testnet.stellar.org";
const FRIENDBOT_URL = "https://friendbot.stellar.org";
const USDC_ISSUER = "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5";
const USDC_CODE = "USDC";
const TRANSFER_AMOUNT = "10"; // 10 USDC
const TX_EXPLORER_BASE = "https://stellar.expert/explorer/testnet/tx";
const server = new Horizon.Server(HORIZON_URL);
const usdc = new Asset(USDC_CODE, USDC_ISSUER);
async function fundWithFriendbot(publicKey: string): Promise<void> {
const response = await fetch(`${FRIENDBOT_URL}?addr=${publicKey}`);
if (!response.ok) {
throw new Error(
`Friendbot funding failed: ${response.status} ${response.statusText}`,
);
}
}
async function establishTrustline(keypair: Keypair): Promise<void> {
const account = await server.loadAccount(keypair.publicKey());
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(Operation.changeTrust({ asset: usdc }))
.setTimeout(30)
.build();
tx.sign(keypair);
const result = await server.submitTransaction(tx);
if (!result.successful) {
throw new Error(`Trustline transaction failed: ${JSON.stringify(result)}`);
}
console.log(`Trustline established: ${TX_EXPLORER_BASE}/${result.hash}`);
}
async function transferUsdc(
sender: Keypair,
recipientPublicKey: string,
): Promise<void> {
const account = await server.loadAccount(sender.publicKey());
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.TESTNET,
})
.addOperation(
Operation.payment({
destination: recipientPublicKey,
asset: usdc,
amount: TRANSFER_AMOUNT,
}),
)
.setTimeout(30)
.build();
tx.sign(sender);
const result = await server.submitTransaction(tx);
if (!result.successful) {
throw new Error(`Payment transaction failed: ${JSON.stringify(result)}`);
}
console.log(`Transfer successful: ${TX_EXPLORER_BASE}/${result.hash}`);
}
async function main() {
// Load the sender wallet from the environment variable
const sender = Keypair.fromSecret(process.env.STELLAR_SECRET_KEY as string);
console.log("Sender address:", sender.publicKey());
// Create and fund a new recipient wallet via Friendbot
console.log("\nCreating recipient wallet...");
const recipient = Keypair.random();
console.log("Recipient address:", recipient.publicKey());
console.log("Funding recipient with testnet XLM...");
await fundWithFriendbot(recipient.publicKey());
console.log("Recipient funded with XLM.");
// Establish a USDC trustline on the recipient account
console.log("\nEstablishing USDC trustline on recipient account...");
await establishTrustline(recipient);
// Transfer USDC from sender to recipient
console.log(`\nTransferring ${TRANSFER_AMOUNT} USDC to recipient...`);
await transferUsdc(sender, recipient.publicKey());
// Confirm recipient balance
const recipientAccount = await server.loadAccount(recipient.publicKey());
const usdcBalance = recipientAccount.balances.find(
(b) =>
b.asset_type === "credit_alphanum4" &&
b.asset_code === USDC_CODE &&
b.asset_issuer === USDC_ISSUER,
);
console.log(`\nRecipient USDC balance: ${usdcBalance?.balance ?? "0"} USDC`);
}
main().catch(console.error);
Step 3: Run the script
From the project directory, run:
Your output should look similar to the following (addresses and hashes will
differ):
Sender address: GAXYZ...
Creating recipient wallet...
Recipient address: GDEFG...
Funding recipient with testnet XLM...
Recipient funded with XLM.
Establishing USDC trustline on recipient account...
Trustline established: https://stellar.expert/explorer/testnet/tx/abc123...
Transferring 10 USDC to recipient...
Transfer successful: https://stellar.expert/explorer/testnet/tx/def456...
Recipient USDC balance: 10.0000000 USDC
Common errors you might encounter:
Friendbot funding failed: 400 — The Friendbot rate limit was exceeded.
Wait a few seconds and try again.
op_no_trust — The recipient account does not have a USDC trustline. The
script establishes one automatically, but if you’re adapting this example for
your own recipient address, ensure the trustline exists first. See
Set up a USDC trustline on Stellar.
op_underfunded — The sender does not have enough USDC. Ensure your
sender wallet is funded with testnet USDC from the
Circle Faucet before running the script.
For more detail on Stellar-specific USDC behavior in CCTP, including address
encoding and decimal precision, see CCTP on Stellar.