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
- Experience sending transactions with Solana Web3.js 2.0 (Guide: How to Send Transactions with Solana Web3.js 2.0)
- Node.js (version 19 or higher) installed
- TypeScript and ts-node installed
- Solana CLI installed
Dependencies Used in this Guide
Dependency | Version |
---|---|
@solana/web3.js | ^2.0.0 |
@solana-program/system | ^0.5.0 |
@solana-program/token | ^0.5.0 |
solana cli | 1.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:
- Mint Account: Stores information about the token, such as total supply and decimals.
- Token Account: Holds a balance of tokens for a specific owner.
- 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:
- We use
getCreateAccountInstruction
andgetInitializeMintInstruction
to create instructions for creating and initializing our mint account. - We use the
pipe
function to chain multiple operations together when creating and sending our transaction. - 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
andgetThawAccountInstruction
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.