Typescript
SPL Token with Web3.js

SPL Token with Web3.js

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 the Associated 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.

Contents
View Source
Blueshift © 2025Commit: dd6c76d