Creating and Managing SPL Tokens on Solana: A Step-by-Step Guide with TypeScript
Hi, we’re going to be learning how to create SPL token using Solana’s Token 22 Program with node.js (TypeScript).
But, lemme tell you, as a NOOB to the Solana Ecosystem this wasn’t all smooth ride, battled through hours of errors, mysterious bugs and bugging a senior developer in the ecosystem for days.
Now, armed with my new found wisdom and a keyboard that survived the journey 😁, I’m here to share the secrets of creating your very own SPL Token using the Solana’s Token 22 Program. We’ll look at how to Add Metadata, Set Transfer Fees, Mint, and transfer some of the Token to another Wallet
By the end, not only would you have your token, you’ll also the scars (er, I mean skills lol) to prove it.
Ready to embark on this token-astic journey? Let’s go!
Installing & Importing the Superpowers
List of packages we will be utilising
@solana/web3.js
: These are your trusty tools for battling the blockchain! You'll be wielding the powers of transaction and connection like a true Solana wizard.@solana/spl-token
: This is your magic wand for creating and controlling tokens, including fancy features like transfer fees and metadata pointers.@solana/spl-token-metadata
: Adds the sparkle to your tokens, allowing them to shine with custom metadata.
import {
clusterApiUrl,
sendAndConfirmTransaction,
Connection,
Keypair,
SystemProgram,
Transaction,
LAMPORTS_PER_SOL,
} from ‘@solana/web3.js’;
import {
ExtensionType,
createInitializeMintInstruction,
mintTo,
createAccount,
getMintLen,
TOKEN_2022_PROGRAM_ID,
TYPE_SIZE,
LENGTH_SIZE,
createInitializeMetadataPointerInstruction,
} from '@solana/spl-token';
import {
createInitializeTransferFeeConfigInstruction,
transferCheckedWithFee,
} from '@solana/spl-token';
import { createInitializeInstruction, pack, TokenMetadata } from '@solana/spl-token-metadata';
Setting Up Mint Extensions
// Define the extensions to be used by the mint
const extensions = [ ExtensionType.TransferFeeConfig,ExtensionType.MetadataPointer, ];
// Calculate the length of the mint
const mintLen = getMintLen(extensions);
- Extensions: This specify which additional features are to be used by the token mint, such as
TransferFeeConfig
for setting transfer fees andMetadataPointer
for handling token metadata. - Mint Length Calculation: This ensures there is enough room to fit in all the selected extensions in the token
Starting Your Solana Adventure
async function main(){
const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');
const payer = Keypair.generate();
// Airdrop SOL to the payer
const airdropSignature = await connection.requestAirdrop(payer.publicKey, 2 * LAMPORTS_PER_SOL);
await connection.confirmTransaction({ signature: airdropSignature, ...(await connection.getLatestBlockhash()) });
const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;
console.log("Mint public key: ", mint.toBase58());
- Connection: Establishes a connection to the Solana devnet, where testing and development occur.
- Payer Keypair: This is the big boss, the one who will fund this adventure.
- Airdrop: Let give some SOL to the Payer, to ensure he can fund the transactions.
- Mint Keypair: You’re crafting a new token! Give it a unique identity with a public key.
Configuring Authorities and Token Parameters
const transferFeeConfigAuthority = Keypair.generate();
const withdrawWithheldAuthority = Keypair.generate();
const decimals = 9;
const feeBasisPoints = 50;
const maxFee = BigInt(5_000);
- Authorities: Generates keypairs for transfer fee and withdrawal authorities, they handle important tasks like managing fees
- Token Parameters: Defines the number of decimal places (
decimals
), the fee as a percentage in basis points (feeBasisPoints
), and the maximum fee (maxFee
).
Defining Metadata and Calculating Space
const metadata: TokenMetadata = {
mint: mint,
name: "Adventure-Coin",
symbol: "ADC",
uri: "link-to-your-metadata.json",
additionalMetadata: [["description", "Only Possible On Solana"]],
};
const mintLen = getMintLen(extensions);
const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length;
const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen);
- Metadata: Sets up the token’s metadata with properties like
name
,symbol
,uri
, and additional metadata fields. We’ll dicuss about the fields in the metadata shortly. - Space Calculation: Computes the length required for storing the mint and metadata.
- Rent Exemption: Calculates the minimum balance needed to make the mint account rent-exempt on Solana.
Creating and Sending the Mint Transaction
const mintTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint,
space: mintLen,
lamports: mintLamports,
programId: TOKEN_2022_PROGRAM_ID,
}),
createInitializeTransferFeeConfigInstruction(
mint,
transferFeeConfigAuthority.publicKey,
withdrawWithheldAuthority.publicKey,
feeBasisPoints,
maxFee,
TOKEN_2022_PROGRAM_ID
),
createInitializeMetadataPointerInstruction(mint, payer.publicKey, mint, TOKEN_2022_PROGRAM_ID),
createInitializeMintInstruction(mint, decimals, payer.publicKey, null, TOKEN_2022_PROGRAM_ID),
createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
mint: mint,
metadata: metadata.mint,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
mintAuthority: payer.publicKey,
updateAuthority: payer.publicKey,
}),
);
await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
console.log("Mint created: ", mint.toBase58());
- Mint Transaction: Creates a transaction to set up the mint account and initialises extensions and metadata.
- Transfer Fee Config: Set up the tiny toll for each token journey..
- Metadata Pointer: Tell the blockchain where to find your token’s secret identity.
- Mint Initialisation: Sets up the mint with decimals and authorities.
- Metadata Initialisation: Applies the defined metadata to the mint.
- Transaction Confirmation: Sends the transaction and confirms its completion, logging the mint’s public key.
Minting and Transferring Tokens
const mintAmount = BigInt(1_000_000_000);
const sourceAccount = await createAccount(
connection,
payer,
mint,
payer.publicKey,
undefined,
undefined,
TOKEN_2022_PROGRAM_ID
);
console.log("Source account: ", sourceAccount.toBase58());
// Mint the token to the payer's account
await mintTo(
connection,
payer,
mint,
sourceAccount,
payer.publicKey,
mintAmount,
[],
undefined,
TOKEN_2022_PROGRAM_ID
);
// Receiver of the token
const account = Keypair.generate();
const destinationAccount = await createAccount(
connection,
payer,
mint,
payer.publicKey,
account,
undefined,
TOKEN_2022_PROGRAM_ID
)
console.log('Destination account: ', destinationAccount.toBase58());
const transferAmount = BigInt(1_000_000);
const fee = (transferAmount * BigInt(feeBasisPoints)) / BigInt(10_000);
console.log('Fee: ', fee);
// Transfer the token with the fee
await transferCheckedWithFee(
connection,
payer,
sourceAccount,
mint,
destinationAccount,
payer,
transferAmount,
decimals,
fee,
[],
undefined,
TOKEN_2022_PROGRAM_ID
)
console.log("Token transferred");
}
- Minting Tokens: Mints the specified amount of tokens (
mintAmount
) to the source account. - Source Account: Creates a token account for the payer to hold the minted tokens.
- Destination Account: Creates a token account for the receiver.
- Transfer Tokens: Calculates the transfer fee and transfers the specified amount to the destination account, applying the fee.
Nice, almost done… now let talk about the metadata
Metadata: The Essence of Your Token
- Name (
"Adventure-Coin"
): The unique name of your token, like a brand that makes it memorable. - Symbol (
"ADC"
): A short identifier for your token, similar to a stock ticker symbol. - URI: A link to a JSON file containing more information about your token, such as images or detailed descriptions.
- Additional Metadata: Extra details about your token, like a tagline or description, which can highlight its uniqueness.
The uri should be a link pointing to your metadata.json file, you can upload this file on "Github"
and update the uri in the metadata to the link pointing to metadata.json
Your metadata.json should look like this
{
"name": "Adventure-Coin",
"symbol": "ADC",
"description": "First Token",
"image": "link-to-your-image"
}
These metadata fields help define your token’s identity and make it stand out in the blockchain world!
And we are done…
Well met, if thou hast reached this stage. (in my Shakespeare’s voice 😁)
You’re now Armed with new skills and hope your keyboard survive 😁, you can now tackle more projects and share your wisdom. Keep exploring, creating, and having fun 😉.
Cheers!