The Token Program
On Solana everything token related is handled by the SPL Token Program and the Token2022 Program: Solana's native token framework that defines how all tokens are created, managed, and transferred.
It's a single, unified program that handles all token operations across the network, ensuring consistency and interoperability.
The decision of having a single, unified interface for all tokens on Solana creates an easy implementation that can be replicated across all dApps (decentralized applications) and integrations (like wallets, ...)
Let's start by installing the required package to work with the SPL Token program using Web3.js:
npm i @solana/spl-token
Mint and Token Accounts
Under the hood, creating a Mint
and Token
account is quite "complicated". There are different instruction that requires different inputs and accounts; the account needs to be made rent exempt before we can actually initialize it, ...
Mint Account
Without any abstraction, creating a Mint
account would look like this:
import {
Keypair,
sendAndConfirmTransaction,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import {
createInitializeMint2Instruction,
MINT_SIZE,
getMinimumBalanceForRentExemptMint,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
const mint = Keypair.generate();
const mintRent = await getMinimumBalanceForRentExemptMint(connection);
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: mint.publicKey,
space: MINT_SIZE,
lamports: mintRent,
programId: TOKEN_PROGRAM_ID
});
const initializeMintInstruction = createInitializeMint2Instruction(
mint.publicKey, // mint pubkey
6, // decimals
feePayer.publicKey, // mint authority
null, // freeze authority
TOKEN_PROGRAM_ID
);
const transaction = new Transaction().add(
createAccountInstruction,
initializeMintInstruction,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair, mint]);
console.log(`Mint created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
Thankfully, the @solana/spl-token
package has some abstraction. So we can create a Mint
account with a single createMint()
function like so:
const mint = await createMint(
connection, // connection
keypair, // payer
keypair.publicKey, // mint authority
null, // freeze authority
6 // decimals
);
Token Account
Same goes with the Token
account. If we would to create it without any abstractions it would look like this:
import {
Keypair,
sendAndConfirmTransaction,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import {
createInitializeAccount3Instruction,
ACCOUNT_SIZE,
getMinimumBalanceForRentExemptAccount,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
const token = Keypair.generate();
const tokenRent = await getMinimumBalanceForRentExemptAccount(connection);
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: token.publicKey,
space: ACCOUNT_SIZE,
lamports: tokenRent,
programId: TOKEN_PROGRAM_ID
});
const initializeTokenInstruction = createInitializeAccount3Instruction(
token.publicKey, // token pubkey
mint.publicKey, // mint pubkey
feePayer.publicKey, // owner pubkey
TOKEN_PROGRAM_ID
);
const transaction = new Transaction().add(
createAccountInstruction,
initializeTokenInstruction,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair, token]);
console.log(`Token created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
But same as the Mint
account, the @solana/spl-token
package has some abstraction to create the Token
account. We can use the createAccount()
function like so:
const mint = await createMint(
connection, // connection
keypair, // payer
mint.publicKey, // mint pubkey
keypair.publicKey, // owner pubkey
);
Associated Token Account
Same goes for the Associated Token
account, but the abstraction is not related to the creation of the accounts like for the Mint
and Token
account, it's mainly related to the address derivation.
So here's how we create an Associated Token
account without any abstractions:
import {
sendAndConfirmTransaction,
Transaction,
} from "@solana/web3.js";
import {
TOKEN_PROGRAM_ID
createAssociatedTokenAccountIdempotentInstruction,
getAssociatedTokenAddress,
} from "@solana/spl-token";
const associatedTokenAccount = await getAssociatedTokenAddress(
mint.publicKey, // mint pubkey
keypair.publicKey, // owner pubkey
false, // allow owner off-curve
TOKEN_PROGRAM_ID
);
// Create ATA creation instructions for all accounts
const createAtaInstruction = createAssociatedTokenAccountIdempotentInstruction(
keypair.publicKey, // payer
associatedTokenAccount, // associated token account address
keypair.publicKey, // owner
mint.publicKey, // mint
TOKEN_PROGRAM_ID
);
const transaction = new Transaction().add(
createAtaInstruction,
);
const signature = await sendAndConfirmTransaction(connection, transaction, [keypair]);
console.log(`Associated Token created! Check out your TX here: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
And here's how it looks with abstractions:
const ata = await getOrCreateAssociatedTokenAccount(
connection, // connection
keypair, // payer
mint, // mint pubkey
keypair.publicKey // owner pubkey
);
As you can see, the function is called
getOrCreateAssociatedTokenAccount()
. This is because it might happen that theAssociated Token
account has already been created previously and we don't want our transaction to fail because of it. So what it does is create or just return the address of the ATA.