Skip to main content

Creating a Fungible Token with Solana Web3.js 2.0

Updated on
Nov 13, 2024

11 min read

Overview

Solana's Web3.js 2.0 library introduces a new way to interact with the Solana blockchain, emphasizing composability and modularity. In this guide, we'll explore how to create a fungible token using this updated library.

Let's dive in!

What You Will Do

Write a script to create a fungible token on the Solana blockchain using the Solana Web3.js 2.0 library:

  • Set up a connection to a Solana cluster
  • Generate necessary key pairs
  • Create a mint account
  • Initialize the mint
  • Create an Associated Token Account (ATA)
  • Mint tokens to the ATA

What You Will Need

Dependencies Used in this Guide

DependencyVersion
@solana/web3.js^2.0.0
@solana-program/system^0.5.0
@solana-program/token^0.5.0
solana cli1.18.8

Let's get started!

What is a Fungible Token on Solana?

A fungible token on Solana is a type of digital asset where each unit is interchangeable with another unit of the same token. These tokens are created and managed using Solana's Token Program, which provides functionality for minting, transferring, and burning tokens. Fungible tokens can be used to store and exchange value (e.g., USDC or other assets), serve as a utility (e.g., a whitelist token for your upcoming NFT mint), represent a stake in a protocol (e.g., a governance token), or really anything else you can think of.

Key components of a fungible token on Solana include:

  1. Mint Account: Stores information about the token, such as total supply and decimals.
  2. Token Account: Holds a balance of tokens for a specific owner.
  3. Associated Token Account (ATA): A deterministic Token Account derived from an owner's address and a mint address.

Now, let's explore how to create these components using Solana Web3.js 2.0.

Create a New Project

First, let's set up our project:

mkdir solana-fungible-token && cd solana-fungible-token

Initialize your project as a Node.js project:

npm init -y

Install the dependencies:

npm install @solana/web3.js@2 @solana-program/system @solana-program/token && npm install --save-dev @types/node typescript ts-node

Note: You may need to install the @solana-program/system and @solana-program/token packages using the --legacy-peer-deps flag.

Add a tsconfig.json file to your project:

tsc --init --resolveJsonModule true

Create a new file called create-token.ts in your project directory:

echo > create-token.ts

Great! Now, we're ready to start coding.

Import Dependencies

In your create-token.ts file, let's start by importing the necessary dependencies:

import { getCreateAccountInstruction } from "@solana-program/system";
import {
findAssociatedTokenPda,
getCreateAssociatedTokenIdempotentInstructionAsync,
getInitializeMintInstruction,
getMintSize,
getMintToInstruction,
TOKEN_PROGRAM_ADDRESS
} from "@solana-program/token";
import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports,
sendAndConfirmTransactionFactory,
pipe,
createTransactionMessage,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
getSignatureFromTransaction,
setTransactionMessageFeePayerSigner,
appendTransactionMessageInstructions,
CompilableTransactionMessage,
TransactionMessageWithBlockhashLifetime,
Commitment,
} from "@solana/web3.js";

const LAMPORTS_PER_SOL = BigInt(1_000_000_000);
const DECIMALS = 9;
const DROP_AMOUNT = 100;

Here, we're importing various functions from the Solana Web3.js 2.0 library and the Solana Program libraries.

Create the Main Function

Next, let's create our main function that will house the logic of our script:

async function main() {
// 1 - Establish connection to Solana cluster

// 2 - Generate key pairs

// 3 - Airdrop SOL to payer account

// 4 - Create mint account and initialize mint

// 5 - Create Associated Token Account and mint tokens
}

main();

Establish Connection to Solana Cluster

Inside the main function, let's establish a connection to our local Solana cluster:

    // 1 - Establish connection to Solana cluster
const httpEndpoint = 'http://127.0.0.1:8899';
const wsEndpoint = 'ws://127.0.0.1:8900';
const rpc = createSolanaRpc(httpEndpoint);
const rpcSubscriptions = createSolanaRpcSubscriptions(wsEndpoint);
console.log(`✅ - Established connection to ${httpEndpoint}`);

We're using the createSolanaRpc and createSolanaRpcSubscriptions functions to create our RPC connections. We're using localhost for this guide, but if you're ready to connect to a remote Solana cluster, you can use your QuickNode HTTP Provider and WSS Provider endpoints from your QuickNode Dashboard.

Generate Key Pairs

Let's generate the necessary key pairs for our token creation process. Add the following code to the main function in Step 2:

    // 2 - Generate key pairs
const mintAuthority = await generateKeyPairSigner();
const payer = await generateKeyPairSigner();
const owner = await generateKeyPairSigner();
const mint = await generateKeyPairSigner();

const [ata] = await findAssociatedTokenPda({
mint: mint.address,
owner: owner.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});

console.log(`✅ - Generated key pairs`);
console.log(` Mint Authority: ${mintAuthority.address}`);
console.log(` Payer: ${payer.address}`);
console.log(` Owner: ${owner.address}`);
console.log(` Mint: ${mint.address}`);
console.log(` Associated Token Account: ${ata}`);

Here, we generate key pairs for the mint authority, payer, owner, and mint account itself. We also derive the Associated Token Account (ATA) address using the findAssociatedTokenPda function. For the example, we log the addresses to the console as a reference.

Airdrop SOL to Payer Account

Before we can create our token, we need to fund our payer account with SOL. Let's do this using the airdropFactory function. Add the following code to the main function in Step 3:

    // 3 - Airdrop SOL to payer account
const airdrop = airdropFactory({ rpc, rpcSubscriptions });
const airdropTx = await airdrop({
commitment: 'processed',
lamports: lamports(LAMPORTS_PER_SOL),
recipientAddress: payer.address
});
console.log(`✅ - Airdropped 1 SOL to payer: ${airdropTx}`);

Here, we're using the airdropFactory function to create an airdrop function, which we then use to airdrop 1 SOL to our payer account.

Create Mint Account and Initialize Mint

Now that our payer account has SOL, let's create and initialize our mint account. Add the following code to the main function in Step 4:

    // 4 - Create mint account and initialize mint
const mintSpace = BigInt(getMintSize());
const mintRent = await rpc.getMinimumBalanceForRentExemption(mintSpace).send();

const instructions = [
// Create the Mint Account
getCreateAccountInstruction({
payer,
newAccount: mint,
lamports: mintRent,
space: mintSpace,
programAddress: TOKEN_PROGRAM_ADDRESS,
}),
// Initialize the Mint
getInitializeMintInstruction({
mint: mint.address,
decimals: DECIMALS,
mintAuthority: mintAuthority.address
}),
];

const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
const signAndSendTransaction = createSignAndSendTransaction(sendAndConfirmTransaction);

const createMintTxid = await pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(instructions, tx),
(tx) => signAndSendTransaction(tx)
);
console.log(`✅ - Mint account created and initialized: ${createMintTxid}`);

This section demonstrates several key features of Web3.js 2.0:

  1. We use getCreateAccountInstruction and getInitializeMintInstruction to create instructions for creating and initializing our mint account.
  2. We use the pipe function to chain multiple operations together when creating and sending our transaction.
  3. We use createSignAndSendTransaction, a custom helper function we'll define later, to allow us to sign and send our transaction.

Create Associated Token Account and Mint Tokens

Finally, let's create another transaction that creates an Associated Token Account (ATA) for our owner and mint some tokens to it. Add the following code to the main function in Step 5:

    // 5 - Create Associated Token Account and mint tokens
const mintInstructions = [
// Create the Destination Associated Token Account
await getCreateAssociatedTokenIdempotentInstructionAsync({
mint: mint.address,
payer,
owner: owner.address,
}),
// Mint To the Destination Associated Token Account
getMintToInstruction({
mint: mint.address,
token: ata,
amount: BigInt(DROP_AMOUNT * 10 ** DECIMALS),
mintAuthority, // Signs by including the signer rather than the public key
})
];

const mintTxid = await pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions(mintInstructions, tx),
(tx) => signAndSendTransaction(tx)
);
console.log(`✅ - Tokens minted to ATA: ${mintTxid}`);

Here, we're creating an ATA for our owner and minting tokens to it. We're using the getCreateAssociatedTokenIdempotentInstructionAsync function to create the ATA, and getMintToInstruction to mint tokens to it. We are using a similar pipe function to create and send our transaction as we did in Step 4.

Helper Function

Let's add a helper function at the end of our file to handle signing and sending transactions. Add the following code to the end of your create-token.ts file:

const createSignAndSendTransaction = (sendAndConfirmTransaction: ReturnType<typeof sendAndConfirmTransactionFactory>) => {
return async (
transactionMessage: CompilableTransactionMessage & TransactionMessageWithBlockhashLifetime,
commitment: Commitment = 'processed',
skipPreflight: boolean = true
) => {
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
try {
await sendAndConfirmTransaction(signedTransaction, { commitment, skipPreflight });
return getSignatureFromTransaction(signedTransaction);
} catch (e) {
console.error('Transaction failed:', e);
throw e;
}
};
};

This helper function encapsulates the process of signing and sending a transaction, making our main code cleaner and more readable.

Set Up Local Environment

For this guide, we'll use a local Solana validator. Open a new terminal window and start the validator:

solana-test-validator -r

When you are ready to take your token to production, you'll need to deploy it to a Solana cluster. Whether deploying to mainnet or devnet, you can get a free QuickNode endpoint by signing up for a QuickNode account.

Run the Script

To run our script, use the following command:

ts-node create-token.ts

You should see output indicating successful connection, key pair generation, airdrop, mint creation, and token minting:

✅ - Established connection to http://127.0.0.1:8899
✅ - Generated key pairs
Mint Authority: 7JBobKzJt8BgGwbPwGGGD2HyLMf6nGW1FXvHvd2xUKZ8
Payer: 3eVn4tAFGGP5tW3Rc1Lqq7hEDUFJz8uxFpNa3SJsDtba
Owner: 8nM1gKXDygKUWDCy8PJvAJvQUd11m5coQWUo7v5Zitm1
Mint: 9vgOWh6vhd9s33S1JTzS3Hk1mPEp2kPMPNK1sY6lEYvZ
Associated Token Account: 6Z9Q5KyFbUZ2hYU2HfxUVPD6XpQUy1rHgLGLwR6sPGNj
✅ - Airdropped 1 SOL to payer: 2gkQL7h7hHRWNfNwf2WyuPRmbhVd8oP7Ehfs54Pspqxx7KkYGF3wxjeoiVwLUHMgr9UYKY4PiMymmsGsQqKVWYoC
✅ - Mint account created and initialized: 5JcnmFrYhPcZnmfUPecuBUJw9RVAoHwUQ2xGtcJ8XkHNwZHMWLR13eJcuqfkdbBnfRm1ajFTaF3E7WzQFyCpgCc3
✅ - Tokens minted to ATA: 3nLePg18vjuVDM5m3z1jFSTPBYNWUHx4eaUprWXKqCu6ZNCYSBUgezp3xKnPeKQAjBRJmtVqc4ij3KvZUakqPa8y

Great job! You've successfully created a fungible token using Solana Web3.js 2.0. You should be able to see your transactions and mints in the Local Solana Explorer.

Wrap Up

This guide explored the new Solana Web3.js 2.0 library by creating a script to perform some basic operations using the SPL Token Program and the new @solana-program/token library. Hopefully, after this guide, you are starting to see some of the power of the new library and how it can help you build more complex applications. As you continue to explore Web3.js 2.0, you'll discover even more powerful features and improvements. Keep up the good work!

If you want to keep practicing, try expanding the script to do some new things:

  • Transfer SPL Tokens to another account using getTransferCheckedInstruction
  • Burn SPL Tokens using getBurnCheckedInstruction
  • Freeze and Thaw SPL Tokens using getFreezeAccountInstruction and getThawAccountInstruction

We ❤️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Resources

Share this guide