Associated Token Accounts (ATAs)
An Associated Token Account (ATA) is the canonical SPL token account address for a wallet + mint pair. Wallets, programs, and indexers rely on this deterministic PDA instead of ad-hoc token account addresses.
Search across all documentation pages
An Associated Token Account (ATA) is the canonical SPL token account address for a wallet + mint pair. Wallets, programs, and indexers rely on this deterministic PDA instead of ad-hoc token account addresses.
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS } from "@solana-program/token";
import { address } from "@solana/kit";
const [ata] = await findAssociatedTokenPda({
mint: address("MINT_PUBKEY"),
owner: address("WALLET_PUBKEY"),
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});spl-token create-account <MINT>
spl-token address --token <MINT>When to reach for this:
import {
createSolanaRpc,
createTransactionMessage,
pipe,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
signTransactionMessageWithSigners,
sendAndConfirmTransactionFactory,
address,
} from "@solana/kit";
import {
getCreateAssociatedTokenIdempotentInstruction,
findAssociatedTokenPda,
TOKEN_PROGRAM_ADDRESS,
} from "@solana-program/token";
import { ASSOCIATED_TOKEN_PROGRAM_ADDRESS } from "@solana-program/associated-token";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
const mint = address("MINT_PUBKEY");
const owner = address("WALLET_PUBKEY");
const [ata] = await findAssociatedTokenPda({
mint,
owner,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});
const createAtaIx = getCreateAssociatedTokenIdempotentInstruction({
ata,
mint,
owner,
payer: owner, // fee payer signer in real app
tokenProgram: TOKEN_PROGRAM_ADDRESS,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
});
// Build, sign, and send with your wallet signer wired to fee payer + payer fields
console.log("ATA:", ata);init_if_needed requires the cargo feature, or the program will not build:
# Cargo.toml
anchor-lang = { version = "0.32.1", features = ["init-if-needed"] }
anchor-spl = "0.32.1"use anchor_lang::prelude::*;
use anchor_spl::associated_token::AssociatedToken;
use anchor_spl::token::{Mint, Token, TokenAccount};
#[derive(Accounts)]
pub struct InitUserAta<'info> {
pub mint: Account<'info, Mint>,
#[account(
init_if_needed,
payer = payer,
associated_token::mint = mint,
associated_token::authority = user,
associated_token::token_program = token_program,
)]
// No `space` here: Anchor knows the size for associated_token:: inits.
pub user_ata: Account<'info, TokenAccount>,
pub user: SystemAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}What this demonstrates:
create_associated_token_account_idempotent is safe to call when ATA may already existassociated_token constraints wire the same derivation on-chain, including token_program so the derivation matches the mint's actual programanchor_spl::token types are classic-Token-only; for a mint that may be Token-2022, use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface} with InterfaceAccount and Interface<'info, TokenInterface>ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL) creates token accounts at a PDATokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb as token program| Input | Role |
|---|---|
owner | Wallet or PDA that owns the token account |
mint | SPL mint pubkey |
token_program | Classic SPL or Token-2022 program ID |
use anchor_spl::associated_token::{
get_associated_token_address, get_associated_token_address_with_program_id,
};
// Classic Token only - this helper hardcodes Tokenkeg... in the seeds.
let ata = get_associated_token_address(&owner.key(), &mint.key());
// For a mint that may be Token-2022, the token program is part of the seeds:
let ata_any = get_associated_token_address_with_program_id(
&owner.key(),
&mint.key(),
token_program.key,
);
// Compare to the passed account in manual validation pathsfindAssociatedTokenPda.--fund-recipient in CLI or createAssociatedTokenIdempotent in clients.getMinimumBalanceForRentExemption(165).| Alternative | Use When | Don't Use When |
|---|---|---|
| Non-ATA token account | Legacy integrations require fixed address | Standard wallet UX |
getOrCreateAssociatedTokenAccount patterns | One-shot client helper | On-chain validation still needs derivation |
| Token-2022 with extensions | Same ATA rules | Client assumes classic Token program only |
The canonical associated token account PDA for a given wallet owner and mint. Wallets display one deposit address per SPL asset.
No. The derivation is deterministic - only one ATA address per (owner, mint, token program) triple.
Yes. Payer funds rent-exempt lamports (~2M for 165-byte account) plus transaction fee.
Yes. Owner pubkey can be a PDA; the owning program signs transfers with PDA seeds.
Derive with findAssociatedTokenPda / spl-token address --token - creation is separate step.
Yes. Idempotent instruction succeeds whether or not the ATA already exists.
Yes, but pass Token-2022 program ID in derivation and instructions.
Works if account mint matches, but UX breaks - users expect ATA addresses in explorers and wallets.
Yes when balance is zero. Lamports return to a destination account.
getTokenAccountsByOwner returns ATAs and any other token accounts for that owner.
Common onboarding subsidy - budget treasury for create ATA per user per mint.
Use @solana-program/token and @solana-program/associated-token packages alongside kit RPC and transaction builders.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026