To get XDC testnet USDC
Use Circle's CCTP sample app
to transfer USDC cross-chain to your XDC wallet.
To get testnet XDC
Use the XDC faucet from the XDC Apothem
testnet network.
This guide shows you how to transfer USDC on the XDC Apothem testnet. You'll write a simple script that checks your balance and sends a test transfer.
Before you begin, make sure you have:
.env
file.To get XDC testnet USDC
Use Circle's CCTP sample app
to transfer USDC cross-chain to your XDC wallet.
To get testnet XDC
Use the XDC faucet from the XDC Apothem
testnet network.
Initialize a new Node.js project and install dependencies:
npm init -y
npm pkg set type=module
npm install viem dotenv
In the project root, create a .env
file with your private key and recipient
address:
PRIVATE_KEY=<YOUR_PRIVATE_KEY>
RECIPIENT_ADDRESS=0x<RECIPIENT_ADDRESS>
Create an index.js
file. You'll add code step by step in the following
sections.
In index.js
, import required modules and define constants for the XDC Apothem
testnet and the USDC contract:
import "dotenv/config";
import {
createPublicClient,
createWalletClient,
http,
formatUnits,
parseUnits,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { xdcTestnet } from "viem/chains";
const USDC_ADDRESS = "0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4";
const USDC_DECIMALS = 6;
const USDC_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "account", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "", type: "bool" }],
},
];
.env
file.balanceOf
and transfer
functions, since they're all
that's needed for this guide.Next, load your private key and recipient address from .env
:
const PRIVATE_KEY_RAW = process.env.PRIVATE_KEY;
const RECIPIENT = process.env.RECIPIENT_ADDRESS;
if (!PRIVATE_KEY_RAW || !RECIPIENT) {
console.error("Error: Missing PRIVATE_KEY or RECIPIENT_ADDRESS in .env");
process.exit(1);
}
if (!/^0x[a-fA-F0-9]{40}$/.test(RECIPIENT)) {
console.error("Error: Recipient address is invalid");
process.exit(1);
}
const PRIVATE_KEY = PRIVATE_KEY_RAW.startsWith("0x")
? PRIVATE_KEY_RAW
: `0x${PRIVATE_KEY_RAW}`;
.env
file so you don't hardcode sensitive information.0x
so Viem can use it.Create a public client for reading blockchain data and a wallet client for sending transactions:
const account = privateKeyToAccount(PRIVATE_KEY);
const publicClient = createPublicClient({
chain: xdcTestnet,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: xdcTestnet,
transport: http(),
});
privateKeyToAccount
creates an account object from your private key.createPublicClient
is used for reading data from the blockchain (no private
key needed).createWalletClient
uses your account to sign and send transactions.Now, write the main logic to check your balance and send a transfer:
(async () => {
try {
// Check balance
const balance = await publicClient.readContract({
address: USDC_ADDRESS,
abi: USDC_ABI,
functionName: "balanceOf",
args: [account.address],
});
const balanceFormatted = Number(formatUnits(balance, USDC_DECIMALS));
const amount = 10; // send 10 USDC
console.log("Sender:", account.address);
console.log("Recipient:", RECIPIENT);
console.log("Balance:", balanceFormatted);
if (amount > balanceFormatted) {
console.error("Error: Insufficient balance");
process.exit(1);
}
const amountInDecimals = parseUnits(amount.toString(), USDC_DECIMALS);
// Transfer
const hash = await walletClient.writeContract({
address: USDC_ADDRESS,
abi: USDC_ABI,
functionName: "transfer",
args: [RECIPIENT, amountInDecimals],
});
console.log("Transfer successful!");
console.log("Tx hash:", hash);
console.log("Explorer:", `https://testnet.xdcscan.com/tx/${hash}`);
} catch (err) {
console.error("Transfer failed:", err.message || err);
process.exit(1);
}
})();
balanceOf
function.formatUnits
.parseUnits
.transfer
function on the USDC contract to send tokens.After running the script:
node index.js
You'll see output similar to the following:
Sender: 0x1A2b...7890
Recipient: 0x9F8f...1234
Balance: 250.0
Transfer successful!
Tx hash: 0xabc123...def456
Explorer: https://testnet.xdcscan.com/tx/0xabc123...def456
To verify the transfer, copy the transaction hash URL from the Explorer:
line
and open it in your browser. This will take you to the XDC Apothem testnet
explorer, where you can view full transaction details.
Tip: If your script doesn't output a full explorer URL, you can manually paste the transaction hash into the XDC testnet explorer.
In this quickstart, you learned how to check balances and transfer USDC on the XDC Apothem testnet using Viem and Node.js. Here are the key points to remember:
.env
. Never commit secrets.balanceOf
and transfer
for
simplicity.