Solana Token Standards
Solana has two token programs: the original SPL Token and the newer Token-2022 with advanced extensions.
SPL Token Program
The original token standard (still used by 99% of tokens):
Program ID: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Account Structure
Mint Account (defines the token):
- Mint Authority: Can mint new tokens
- Supply: Total tokens in circulation
- Decimals: Number of decimal places (9 for SOL-like)
- Freeze Authority: Can freeze token accounts
Token Account (holds tokens for an owner):
- Mint: Which token this account holds
- Owner: Who owns these tokens
- Amount: Token balance
- Delegate: Optional account that can spend on behalf of owner
- State: Normal, Frozen, or Uninitialized
Associated Token Account (ATA)
A deterministic token account address:
import { getAssociatedTokenAddress } from '@solana/spl-token';
const ata = await getAssociatedTokenAddress(
mint, // Token mint address
owner, // Wallet address
);
// Derivation: PDA with seeds [owner, TOKEN_PROGRAM_ID, mint]
ATAs simplify token transfers (one account per mint per wallet).
Token-2022 (Token Extensions)
The new token program with powerful extensions:
Program ID: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Why Token-2022?
- Backwards compatible with SPL Token
- Extensions add features without changing core program
- Future-proof (can add new extensions)
Available Extensions
1. Transfer Hooks
Execute custom logic on every transfer:
// Example: Charge a fee on every transfer
pub fn transfer_hook(
ctx: Context<TransferHook>,
amount: u64,
) -> Result<()> {
let fee = amount / 100; // 1% fee
// Deduct fee, send to treasury
Ok(())
}
Use cases:
- Transaction fees
- Blacklist/whitelist enforcement
- Royalties on token transfers
2. Confidential Transfers
Privacy via zero-knowledge proofs:
Normal transfer: "Alice sent 100 USDC to Bob" (public)
Confidential transfer: "Alice sent ??? USDC to Bob" (amount hidden)
Balances are encrypted, but the network verifies the math is correct.
3. Transfer Fees
Built-in transfer fee without hooks:
Transfer 1000 tokens with 1% fee:
Recipient receives: 990 tokens
Fee account receives: 10 tokens
4. Interest-Bearing Tokens
Tokens that accrue interest:
Rate: 5% APY
You hold: 1000 tokens
After 1 year: Balance shows 1050 tokens (automatically)
Implemented via an exchange rate that updates over time.
5. Non-Transferable Tokens
Soulbound tokens (can't be transferred):
Use cases:
- Credentials (diplomas, certificates)
- Achievements (non-tradeable NFTs)
- Identity tokens
6. Permanent Delegate
A delegate that can't be revoked:
Use case: Staking program that needs permanent control
7. Metadata Pointer
Store metadata on-chain (no external JSON):
{
"name": "My Token",
"symbol": "MTK",
"uri": "https://example.com/metadata.json"
}
Metaplex Token Metadata is still more common, but metadata pointer is simpler.
Creating a Token-2022 Mint
import { createMint } from '@solana/spl-token';
import { TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
const mint = await createMint(
connection,
payer,
mintAuthority,
freezeAuthority,
decimals,
undefined, // keypair (optional)
undefined, // confirm options
TOKEN_2022_PROGRAM_ID // Use Token-2022 instead of SPL Token
);
Adding Extensions
import {
createInitializeTransferFeeConfigInstruction,
createInitializeMintInstruction,
} from '@solana/spl-token';
// 1. Calculate space needed
const extensions = [ExtensionType.TransferFeeConfig];
const mintLen = getMintLen(extensions);
// 2. Create account
const createAccountIx = SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint.publicKey,
space: mintLen,
lamports: await connection.getMinimumBalanceForRentExemption(mintLen),
programId: TOKEN_2022_PROGRAM_ID,
});
// 3. Initialize extension
const initTransferFeeIx = createInitializeTransferFeeConfigInstruction(
mint.publicKey,
feeAuthority.publicKey,
withdrawAuthority.publicKey,
feeBasisPoints, // 100 = 1%
maxFee,
TOKEN_2022_PROGRAM_ID
);
// 4. Initialize mint
const initMintIx = createInitializeMintInstruction(
mint.publicKey,
decimals,
mintAuthority.publicKey,
freezeAuthority.publicKey,
TOKEN_2022_PROGRAM_ID
);
// Send transaction with all 3 instructions
Token Metadata (Metaplex)
Metaplex Token Metadata is the standard for NFT metadata:
Metadata Account (PDA):
- Name: "Solana Monkey #1234"
- Symbol: "SMB"
- URI: "https://arweave.net/..."
- Creators: [{ address, verified, share }]
- Seller Fee Basis Points: 500 (5% royalty)
- Primary Sale Happened: true/false
The URI points to JSON:
{
"name": "Solana Monkey #1234",
"description": "A cool monkey",
"image": "https://arweave.net/image.png",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "trait_type": "Hat", "value": "Crown" }
]
}
Choosing the Right Standard
| Feature | SPL Token | Token-2022 |
|---|---|---|
| Mature ecosystem | ✅ | ❌ (newer) |
| DEX support | ✅ All DEXs | ⚠️ Limited |
| Transfer fees | ❌ | ✅ |
| Transfer hooks | ❌ | ✅ |
| Confidential transfers | ❌ | ✅ |
| Interest-bearing | ❌ | ✅ |
Use SPL Token for:
- General purpose tokens
- Maximum compatibility
- Production DeFi
Use Token-2022 for:
- Compliance requirements (transfer hooks for KYC)
- Privacy (confidential transfers)
- Novel token mechanics (interest-bearing, non-transferable)
Token Account Rent
Token accounts require rent (~0.002 SOL):
Create 100 token accounts = 0.2 SOL locked
You can close accounts to reclaim rent:
import { closeAccount } from '@solana/spl-token';
await closeAccount(
connection,
payer,
tokenAccount,
destination, // Where to send reclaimed SOL
owner
);
Best Practices
- Use ATAs for user-facing wallets (simplifies UX)
- Close unused accounts to reclaim rent
- Validate token mint before accepting transfers (prevent spam)
- Check program ID (SPL Token vs Token-2022 vs fake programs)
- Handle decimals correctly (9 decimals = 1.0 = 1_000_000_000 base units)
Next Challenge
You'll implement token math functions for handling decimals, price calculations, and LP token formulas.