How to Create and Mint a Mintable SPL Token on Solana (CLI)
A practical guide to creating a mintable SPL token: CLI quick start, common decimals pitfalls, verification steps, plus a runnable TypeScript example and authority controls.

How to Create and Mint a Mintable SPL Token on Solana (CLI)
This guide covers:
- The mental model (Mint Account vs Token Account / ATA)
- Method 1: CLI (spl-token) end-to-end
- Method 2: TypeScript / SDK (and how Anchor usually does it)
- How to mint tokens after creation, transfer/burn, and revoke mint authority (fixed supply)
Introduction: What are mintable SPL tokens?
SPL tokens are Solana's standard for fungible tokens. On Solana, tokens are represented by on-chain accounts controlled by a token program (there are two main token programs: the original Token Program and Token-2022 / Token Extensions).
mint_to instruction later and increase total supply.Fixed supply means: you revoke Mint Authority after minting the final supply (no one can ever mint again).
Tested & Verified
Solana CLI version: 3.0.13
spl-token CLI version: 5.4.0
Network tested: Devnet
Note: Commands may differ slightly by version. If your CLI version differs, run
spl-token --help or spl-token initialize-metadata --help to see available flags for your version.
Quick Start: Create + Mint in 5 Minutes (CLI)
If you just need to get a token created quickly, here's the minimal flow:
-
Set Devnet and fund wallet:
solana config set --url https://api.devnet.solana.com solana airdrop 2 -
Create mint (9 decimals):
spl-token create-token --decimals 9 # Save the mint address printed (e.g., ABC123...) -
Create token account (ATA):
spl-token create-account <MINT_ADDRESS> -
Mint 100 tokens (base units!):
# With 9 decimals: 100 tokens = 100 * 10^9 = 100000000000 base units spl-token mint <MINT_ADDRESS> 100000000000 -
Verify:
spl-token balance <MINT_ADDRESS> spl-token supply <MINT_ADDRESS>
For detailed explanations, decimals handling, Token-2022 options, and programmatic approaches, see the full sections below.
Section 1: Core concepts & prerequisites
The Solana token model (why EVM devs get whiplash)
Solana splits "code" and "state": your token logic lives in the Token Program, while your token data lives in accounts owned by that program.
Mint Account (global token state)
A Mint Account represents the token itself and stores global properties like supply, decimals, and authorities.
Example mental model: USDC is a mint; your wallet does not "hold USDC" directly — it owns a token account that holds a balance for the USDC mint.
Token Account + ATA (your balance lives here)
A Token Account holds a specific owner's balance for a specific mint. An Associated Token Account (ATA) is the standard deterministic token account address most wallets use.
This is the classic "cultural shock": one user + one mint = one token account (ATA). That design helps parallel execution and performance.
Mint Authority (the "god key" for supply)
Decimals (divisibility — and you can't change it later)
Decimals are Immutable
Choose decimals carefully (commonly 6 or 9). After the mint is created, decimals are effectively immutable — if you mess it up, you usually need a new mint.
What you need before starting
- A Solana wallet with SOL for fees and rent-exempt accounts (Devnet SOL via airdrop; Mainnet SOL is real money).
solanaCLI +spl-tokenCLI installed.- A target cluster: Devnet first, always.
Section 2: Method 1 — Create a mintable token via CLI (spl-token)
Below commands are shown for Devnet. Always test Devnet before Mainnet.
1) Point your CLI to Devnet and check your wallet
solana config set --url https://api.devnet.solana.com
solana address
solana balance
Fund on Devnet:
solana airdrop 2
2) Create the Mint (choose decimals)
spl-token create-token --decimals 9
The CLI will print the new mint address. Save it — you'll need it for subsequent steps.
Optional: create under Token-2022 (Token Extensions) Token-2022
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEbYou can target it like:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --decimals 9
Token-2022 is the path if you want native "extensions" (including modern metadata patterns).
3) Create your token account (ATA) to hold the balance
spl-token create-account <MINT_ADDRESS>
This creates an ATA by default.
4) Initialize metadata (name, symbol, URI)
Wallets display tokens better when metadata is present.
Important: Token-2022 supports storing metadata via token extensions / metadata interface patterns, but the mint must be created under the Token-2022 program (see step 2 above). Classic Token Program mints use external metadata (Metaplex, etc.).
For Token-2022 mints:
initialize-metadata command:spl-token initialize-metadata --program-2022 <MINT_ADDRESS> "My Token" "MTK" https://example.com/metadata.json
--program-2022 flag tells the CLI to use the Token-2022 program. You can also use -p TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb instead.Optional flags:
--update-authority <ADDRESS>- Set the update authority (defaults to your keypair)--mint-authority <KEYPAIR>- Specify mint authority if different from fee payer
For Classic Token Program mints:
Check available flags:
To see all options for your version:
spl-token initialize-metadata --help
Option B: Programmatic metadata (TypeScript)
@solana/spl-token helpers. See the TypeScript section below for a complete example.Verification step:
After initializing metadata, verify it exists:
-
Via CLI:
spl-token account-info <MINT_ADDRESS> -
Via explorer:
- Visit
https://solscan.io/token/<MINT_ADDRESS>?cluster=devnet(replace<MINT_ADDRESS>with your mint) - Or use Solana Explorer:
https://explorer.solana.com/address/<MINT_ADDRESS>?cluster=devnet - Check that name, symbol, and URI appear correctly
- Visit
5) Verify mint authority
You can inspect mint info:
spl-token account-info <MINT_ADDRESS>
# or
spl-token supply <MINT_ADDRESS>
6) Mint tokens (the decimals trap)
The Decimals Trap
Minting uses base units. If decimals = 9:
- 1 token = 1,000,000,000 base units
100 * 10^9. This is the #1 reason people mint and see a weird tiny balance.CLI:
spl-token mint <MINT_ADDRESS> <AMOUNT_IN_BASE_UNITS>
Example: To mint 100 tokens with 9 decimals:
spl-token mint <MINT_ADDRESS> 100000000000
Check balance:
spl-token balance <MINT_ADDRESS>
Verify It Worked
After creating and minting via CLI, verify:
- ✅ Cluster is correct (
solana config get) - ✅ Mint address exists (
spl-token account-info <MINT_ADDRESS>) - ✅ Token program ID matches (Token Program or Token-2022)
- ✅ Mint authority is set (check
account-infooutput) - ✅ ATA exists (
spl-token accounts) - ✅ Supply increased (
spl-token supply <MINT_ADDRESS>) - ✅ Balance shows correctly (
spl-token balance <MINT_ADDRESS>)
Section 3: Method 2 — Create a mintable token programmatically (TypeScript / SDK) TypeScript
What actually happens on-chain (two instructions)
Creating a mint account requires:
- System Program: create the account + allocate space + fund rent
- Token Program: initialize mint (decimals + authorities)
TypeScript example (fully runnable on Devnet)
Below is a complete, runnable example: create mint → create ATA → mint to ATA.
Prerequisites:
- Node.js installed
npm install @solana/web3.js @solana/spl-token- Devnet SOL (via airdrop in the script)
Complete script:
import {
Connection,
Keypair,
clusterApiUrl,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import {
createMint,
getOrCreateAssociatedTokenAccount,
mintTo,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
async function main() {
try {
// 1) Connect to Devnet
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
console.log("Connected to Devnet");
// 2) Generate keypair (or load from file for production)
const payer = Keypair.generate();
console.log("Payer address:", payer.publicKey.toBase58());
// 3) Request airdrop for Devnet SOL
console.log("Requesting airdrop...");
const airdropSignature = await connection.requestAirdrop(
payer.publicKey,
2 * LAMPORTS_PER_SOL
);
await connection.confirmTransaction(airdropSignature, "confirmed");
console.log("Airdrop confirmed");
// 4) Set mint authority (same as payer for simplicity)
const mintAuthority = payer;
const decimals = 9;
// 5) Create the mint (mintable because mintAuthority exists)
console.log("Creating mint...");
const mint = await createMint(
connection,
payer,
mintAuthority.publicKey,
null, // freeze authority optional
decimals,
undefined, // keypair (auto-generated)
undefined, // confirmOptions
TOKEN_PROGRAM_ID // Use Token-2022 program ID here if needed
);
console.log("Mint created:", mint.toBase58());
// 6) Create recipient ATA (here: payer's ATA)
console.log("Creating ATA...");
const ata = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
payer.publicKey
);
console.log("ATA address:", ata.address.toBase58());
// 7) Mint tokens to ATA (base units!)
const uiAmount = 100;
const baseUnits = BigInt(uiAmount) * 10n ** BigInt(decimals);
console.log(`Minting ${uiAmount} tokens (${baseUnits} base units)...`);
await mintTo(
connection,
payer,
mint,
ata.address,
mintAuthority,
baseUnits
);
console.log("Minting confirmed");
// 8) Verify
const supply = await connection.getTokenSupply(mint);
console.log("Token supply:", supply.value.uiAmount, supply.value.uiAmountString);
console.log("\n✅ Success!");
console.log("Mint:", mint.toBase58());
console.log("ATA:", ata.address.toBase58());
console.log("Mint Authority:", mintAuthority.publicKey.toBase58());
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
main();
To run:
- Save as
create-token.ts - Run:
npx ts-node create-token.ts(or compile withtscand run withnode)
For production:
- Replace
Keypair.generate()with secure keypair loading (wallet adapter, file-based keypair with proper permissions, etc.) - Use mainnet connection and real SOL
- Add proper error handling and retries
- Consider using a multisig or governance program for mint authority
Verify It Worked
After running the TypeScript script, verify:
- ✅ Cluster is Devnet (check connection URL)
- ✅ Mint address was printed and exists on-chain
- ✅ Token program ID matches (
TOKEN_PROGRAM_IDorTOKEN_2022_PROGRAM_ID) - ✅ Mint authority matches your payer keypair
- ✅ ATA address was created
- ✅ Supply shows the correct amount (100 tokens in the example)
- ✅ Check on explorer:
https://solscan.io/token/<MINT_ADDRESS>?cluster=devnet
Solana Kit style (getCreateAccountInstruction / getInitializeMintInstruction)
getCreateAccountInstruction and getInitializeMintInstruction to assemble the same two-instruction flow. This gives more control over transaction construction.Anchor / Rust mental model
init and sets authority either to:- a user wallet (EOA) or
- a PDA (program-controlled minting, common for emissions)
mint_to instruction and requires the mint authority signer (wallet or PDA via CPI).Section 4: Executing mint operations (post-creation)
1) Ensure the recipient has an ATA
Before minting to someone else, they need a token account for that mint. You can create it for yourself:
spl-token create-account <MINT_ADDRESS>
--fund-recipient in some flows.)2) Mint to a destination
spl-token mint <MINT_ADDRESS> <AMOUNT_IN_BASE_UNITS> <RECIPIENT_TOKEN_ACCOUNT>
(If you omit destination, CLI often mints to your default token account.)
3) Transfer tokens (and optionally fund recipient ATA)
spl-token transfer <MINT_ADDRESS> <AMOUNT_IN_BASE_UNITS> <RECIPIENT_WALLET> --fund-recipient
--fund-recipient creates the recipient ATA if missing (sender pays).4) Burn tokens (reduce supply)
spl-token burn <TOKEN_ACCOUNT> <AMOUNT_IN_BASE_UNITS>
5) Authority management: transfer or revoke mint authority
Transfer mint authority (e.g., to a multisig or governance program) is often safer than keeping it on a hot wallet. Conceptually, Solana's "Set Authority" is how this works.
Revoke mint authority (make fixed supply):
spl-token authorize --authority <CURRENT_AUTHORITY_KEYPAIR> mint <MINT_ADDRESS> --disable
OwnerMismatch.Conclusion: mintable tokens are flexible — but trust is earned
Alternative: No-code option
If you prefer a UI-based approach without writing CLI commands or code, you can use no-code tools. However, understanding the underlying concepts (decimals, mint authority, ATAs) remains important regardless of the tool you choose.
What no-code tools provide:
- Visual interface for token creation
- Metadata configuration forms
- Authority management UI
- Minting interface
What you still need to understand:
- Decimals and base units (for minting amounts)
- Mint authority (who can mint, when to revoke)
- Token program choice (Token Program vs Token-2022)
- Security best practices
No-code tools are non-custodial (your wallet signs transactions), so the on-chain behavior and authority rules are identical to CLI/SDK methods.
- Token Creator— Create SPL or Token-2022 tokens
- Update Metadata— Modify token name, symbol, or URI
- Revoke Mint Authority— Permanently revoke minting capability
FAQs
Troubleshooting: Common Issues
Authority mismatch errors
OwnerMismatch Error
OwnerMismatch or similar authority errors:- Verify you're signing with the correct mint authority keypair
- Check that the mint authority hasn't been revoked already
- Ensure you're using the correct program ID (Token Program vs Token-2022)
Wrong cluster / network
Network Mismatch
Common mistake: testing on Devnet but deploying to Mainnet (or vice versa). Always verify:
solana config get
Missing ATA (Associated Token Account)
ATA Required
Before minting or transferring to a recipient, ensure their ATA exists:
spl-token accounts --owner <RECIPIENT_ADDRESS>
If missing, create it:
spl-token create-account <MINT_ADDRESS> --owner <RECIPIENT_ADDRESS>
Decimals confusion
Base Units Conversion
Remember: minting uses base units. If decimals = 9:
- 1 token = 1,000,000,000 base units
- 100 tokens = 100,000,000,000 base units
10^decimals when minting programmatically.Insufficient SOL for rent
Rent-Exempt SOL Required
Token accounts and mint accounts require rent-exempt SOL. If creation fails:
- Check your SOL balance:
solana balance - Airdrop more SOL on Devnet:
solana airdrop 2 - On Mainnet, ensure you have sufficient SOL for fees and rent



