Quickstart: Create and Fund a Solana Associated Token Account (ATA)
Create a Solana ATA for an owner wallet and pay the creation rent from a payer wallet
On Solana, SPL token transfers require the recipient to have an Associated Token
Account (ATA) for that token. Gas Station does not pay for ATA creation unless
you use Solana ATA sponsorship.
Without sponsorship, you must create the ATA and pay its rent before a transfer
can succeed.This quickstart walks you through a server-side script that creates a USDC ATA.
A payer wallet pays the one-time SOL rent, and an owner wallet owns the ATA and
receives USDC.
All transactions in this guide take place on Solana Devnet. No real funds are
required beyond testnet SOL for rent and fees. You can adapt the code for
mainnet by setting CLUSTER to 'mainnet-beta' and using mainnet USDC mint
addresses.
This use of a private key is simplified for demonstration purposes. In
production, store and access your private key securely and never share it.
The PAYER_PRIVATE_KEY should be a JSON array of bytes representing your
private key. You can export this from most Solana wallets.
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, install bs58, save the following code as
convert-key.ts, and run it with tsx to convert it to a JSON array:
Shell
Report incorrect code
Copy
npm install bs58npx tsx convert-key.ts
TypeScript
Report incorrect code
Copy
import bs58 from "bs58";const privateKeyBase58: string = "YOUR_BASE58_PRIVATE_KEY";try { // Decode the Base58 string to a Uint8Array const privateKeyBytes: Uint8Array = bs58.decode(privateKeyBase58); // Convert the Uint8Array to a JSON array string const privateKeyJsonString: string = JSON.stringify( Array.from(privateKeyBytes), ); console.log("JSON Array:", privateKeyJsonString);} catch (error) { console.error( "Error converting key. Check if the Base58 key is valid.", error, );}
Add the following code to index.ts. This code defines the createUSDCata
function and the main function.
How this script works
The createUSDCata function:
Derives the ATA address.
Builds the idempotent ATA instruction.
Sends the transaction.
The main function:
Loads the payer keypair and owner address from .env.
Initializes the Solana connection.
Calls createUSDCata.
Prints the final ATA address.
The payer covers the ATA rent-exempt amount and transaction fees.
index.ts
Report incorrect code
Copy
async function createUSDCata( connection: Connection, payer: Keypair, owner: PublicKey,): Promise<string> { // Select the correct USDC mint for the configured cluster. const mint = USDC_MINT[CLUSTER]; // Derive the owner's Associated Token Account (ATA) for USDC. const ata = getAssociatedTokenAddressSync(mint, owner); // Create an idempotent ATA instruction. // If the ATA already exists, this instruction is a no-op. const ix = createAssociatedTokenAccountIdempotentInstruction( payer.publicKey, ata, owner, mint, ); // Build and send the transaction. The payer signs and pays ATA rent plus fees. const tx = new Transaction().add(ix); await sendAndConfirmTransaction(connection, tx, [payer], { commitment: "confirmed", }); // Print the ATA and a Solana Explorer link for verification. console.log("ATA created:", ata.toBase58()); const explorerCluster = CLUSTER === "mainnet-beta" ? "" : `?cluster=${CLUSTER}`; console.log( `Explorer: https://explorer.solana.com/address/${ata.toBase58()}${explorerCluster}`, ); return ata.toBase58();}async function main(): Promise<void> { const payerRaw = process.env.PAYER_PRIVATE_KEY; const ownerRaw = process.env.OWNER_PUBLIC_KEY; // Validate required environment variables. if (!payerRaw || !ownerRaw) { throw new Error( "Set PAYER_PRIVATE_KEY and OWNER_PUBLIC_KEY in .env (see Step 1.4)", ); } // Parse keys and create an RPC connection. const payer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(payerRaw))); const owner = new PublicKey(ownerRaw); const connection = new Connection(clusterApiUrl(CLUSTER), "confirmed"); // Create the ATA and print the final address. const ataAddress = await createUSDCata(connection, payer, owner); console.log("Ready to receive USDC at:", ataAddress);}main().catch((err) => { console.error(err); process.exit(1);});
ATA created: DC85yuMEnGDTLpubqUC53BgmMeMjVvoqQyqopekUXffzExplorer: https://explorer.solana.com/address/DC85yuMEnGDTLpubqUC53BgmMeMjVvoqQyqopekUXffz?cluster=devnetReady to receive USDC at: DC85yuMEnGDTLpubqUC53BgmMeMjVvoqQyqopekUXffz
Open the Solana Explorer link to verify the ATA onchain. Run the script again to
confirm idempotent behavior: the transaction still succeeds and the ATA address
is unchanged.