Creating a Token on Solana - 23/06/2023
Creating a Token on Solana
The Solana blockchain has emerged as a powerful platform for decentralised applications and digital asset creation. This article will serve as a comprehensive guide to help you navigate the process of creating your own token on the Solana network.
In this article, we will walk you through the essential steps and concepts involved in token creation on Solana. We’ll cover everything from setting up a development environment to deploying and managing your token.
Tokens on Solana
Tokens on Solana are somewhat different from the ones on other chains. They are made and managed by the Solana Program Library (aka SPL). The source code for SPL is publicly available on GitHub. There are widely two types of Tokens - NFTs and Regular Tokens (memecoins). Both of them come under the SPL Token Program.
As everything on the Solana Blockchain is an account, let’s first understand the working of SPL Tokens.
A High-Level overview of the management of Tokens on Solana.
Let’s go through each type of account:
1. Wallet Account:
- Just a regular crypto wallet.
- Owned by the System Program.
- Works on the User side.
2. Token Account:
- Associated with the user or a wallet.
- The PDA (Program Derived Address) links the mint account and the wallet.
- Keeps track of the details of the tokens wrt. user’s wallet. eg- No. of tokens.
3. Mint Account:
- Stores the Token Metadata.
- Mint Authority: The account that has authority to mint the token.
- Freeze Authority: The account that can stop the mint of the token.
- Supply: How many tokens can exist.
- Decimals: No. of decimal places, the precision. eg- 1.04 has Decimals = 2.
- Is Initialized: If the account is initialized or not.
Minting a Token:
We will be using Metaplex SDK to mint our token and upload our token Metadata to Areave.
Pre Requisites:
To successfully mint your own token on Solana, you’ll need:
1. Setting Up Environment:
Create a new project directory in your terminal with the name of your token:
mkdir <name>
cd <name>
Let’s name our token Avatar. You can go with whatever name you like. Open the folder in VS Code by the command:
code .
Now in the VS Code’s integrated terminal, or your local terminal, run
npm init --yes
to initialise an NPM Project in the directory.
Also create a tsconfig.json
by:
tsc --init
This initialises a TypeScript project into the directory.
Now, let’s install the SDKs that will help us work with the Metaplex and Solana:
npm install @solana/web3.js @metaplex-foundation/mpl-token-metadata @metaplex-foundation/js @solana/spl-token
If everything has been good so far, your directory will look something like this:
2. Creating a Wallet Account:
As discussed earlier, we need an account to take control of our token. We are assuming that you do not have any wallet setup on the CLI. So, let’s create one using Solana’s web3.js SDK and airdrop some funds for the network fees.
Now, in the same directory, create a new file named wallet.ts
.
Let’s first import the SDKs into our file.
We are importing the Solana’s web3.js sdk to interact with the Solana Network and FileSystem (fs) to save the wallet onto our local system.
import { Keypair, LAMPORTS_PER_SOL, Connection } from "@solana/web3.js";
import * as fs from "fs";
Now, let’s make a connection to the Solana Devnet. In the same wallet.ts
file, add this below the imports:
const endpoint = "https://api.devnet.solana.com";
const solanaConnection = new Connection(endpoint);
Let’s generate a new Solana wallet:
const keypair = Keypair.generate();
console.log(
`Generated a Wallet with PublicKey: `,
keypair.publicKey.toString()
);
const secret_array = keypair.secretKey
.toString()
.split(",")
.map((value) => Number(value));
const secret = JSON.stringify(secret_array); //Convert to JSON string
fs.writeFile("wallet.json", secret, "utf8", function (err) {
if (err) throw err;
console.log("Wrote secret key to wallet.json.");
});
This code will create a new keypair using the Keypair
object from the web3.js SDK and display the PublicKey of the generated wallet. It will also create a wallet.json
file containing the Private Key of the generated Wallet.
Now, to pay for the Network Fees, let’s get some Devnet SOL airdropped to our wallet:
(async () => {
const airrdropSignature = solanaConnection.requestAirdrop(
keypair.publicKey,
LAMPORTS_PER_SOL
);
try {
const txId = await airdropSignature;
console.log(`Airdropped 1 SOL. Transaction Id: ${txId}`);
console.log(`https://explorer.solana.com/tx/${txId}?cluster=devnet`);
} catch (err) {
console.log(err);
}
})();
Now, run the script which we have created so far:
ts-node wallet.ts
If you have been following the tutorial so far, you will get output like this into your terminal:
You will also see a file named wallet.json
has been created into the directory.
3. Minting our token:
Before creating the minting script, first go to tsconfig.json
and uncomment or add this line:
(You can use Ctrl/Cmd + F to find the line)
"resolveJsonModule": true,
Now, we are good to go! Create a file named mint.ts
and add the imports:
import {
Transaction,
SystemProgram,
Keypair,
Connection,
PublicKey,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
MINT_SIZE,
TOKEN_PROGRAM_ID,
createInitializeMintInstruction,
getMinimumBalanceForRentExemptMint,
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
createMintToInstruction,
} from "@solana/spl-token";
import {
DataV2,
createCreateMetadataAccountV3Instruction,
} from "@metaplex-foundation/mpl-token-metadata";
import {
bundlrStorage,
keypairIdentity,
Metaplex,
UploadMetadataInput,
} from "@metaplex-foundation/js";
import secret from "./wallet.json";
Just below the imports, make a connection to the Solana devnet:
const endpoint = "https://api.devnet.solana.com/";
const solanaConnection = new Connection(endpoint);
const userWallet = Keypair.fromSecretKey(new Uint8Array(secret));
const metaplex = Metaplex.make(solanaConnection)
.use(keypairIdentity(userWallet))
.use(
bundlrStorage({
address: "https://devnet.bundlr.network",
providerUrl: endpoint,
timeout: 60000,
})
);
Now, create the config for out token. Below the connection, add
const MINT_CONFIG = {
numDecimals: 2,
numberTokens: 100,
};
You are free to use any number of decimals and number of tokens. I am going with 2 decimals and 100 tokens.
Now, add the token metadata part. This will contain a logo, name, description for your token:
const MY_TOKEN_METADATA: UploadMetadataInput = {
name: "Avatar",
symbol: "AVT",
description: "Token for Avatar",
image: "https://i.ibb.co/PtRMT08/avatar-orange.jpg", //add public URL to image you'd like to use
};
We now need to create the on-chain metadata:
const ON_CHAIN_METADATA = {
name: MY_TOKEN_METADATA.name,
symbol: MY_TOKEN_METADATA.symbol,
uri: "TO_UPDATE_LATER",
sellerFeeBasisPoints: 0,
creators: null,
collection: null,
uses: null,
} as DataV2;
Till now, we have created the metadata. Now, it’s the time to get On Chain. We use Bundlr to upload the metadata to Arweave.
const uploadMetadata = async (
tokenMetadata: UploadMetadataInput
): Promise<string> => {
//Upload to Arweave
const { uri } = await metaplex.nfts().uploadMetadata(tokenMetadata);
console.log(`Arweave URL: `, uri);
return uri;
};
The next step is to create a mint transaction. Create a function named createNewMintTransaction
. We also need these 3 values further:
- getMinimumBalanceForRentExemptMint: Number of required Lamports we need to pay for our Token Mint account.
- findMetadataPDA: Get the Metaplex program derived address (“PDA”) for our token mint, using the Metaplex method.
- createAssociatedTokenAccount: This is needed to get the destination owner wallet address.
const createNewMintTransaction = async (connection: Connection, payer: Keypair, mintKeypair: Keypair, destinationWallet: PublicKey, mintAuthority: PublicKey, freezeAuthority: PublicKey) => {
//Get the minimum lamport balance to create a new account and avoid rent payments
const requiredBalance = await getMinimumBalanceForRentExemptMint(connection);
//metadata account associated with mint
const metadataPDA = await metaplex.nfts().pdas().metadata({ mint: mintKeypair.publicKey });
//get associated token account of your wallet
const tokenATA = await getAssociatedTokenAddress(mintKeypair.publicKey, destinationWallet);
Now, let’s create our Transaction. Solana allows you to append multiple transactions into a single one. Yes, Only Possible On Solana.
Create a new variable named, createNewTokenTransaction
and add these 5 instructions to it:
const createNewTokenTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: MINT_SIZE,
lamports: requiredBalance,
programId: TOKEN_PROGRAM_ID,
}),
createInitializeMintInstruction(
mintKeypair.publicKey, //Mint Address
MINT_CONFIG.numDecimals, //Number of Decimals of New mint
mintAuthority, //Mint Authority
freezeAuthority, //Freeze Authority
TOKEN_PROGRAM_ID),
createAssociatedTokenAccountInstruction(
payer.publicKey, //Payer
tokenATA, //Associated token account
payer.publicKey, //token owner
mintKeypair.publicKey, //Mint
),
createMintToInstruction(
mintKeypair.publicKey, //Mint
tokenATA, //Destination Token Account
mintAuthority, //Authority
MINT_CONFIG.numberTokens * Math.pow(10, MINT_CONFIG.numDecimals),//number of tokens
),
createCreateMetadataAccountV3Instruction({
metadata: metadataPDA,
mint: mintKeypair.publicKey,
mintAuthority: mintAuthority,
payer: payer.publicKey,
updateAuthority: mintAuthority,
}, {
createMetadataAccountArgsV3: {
data: ON_CHAIN_METADATA,
isMutable: true,
collectionDetails: null
}
})
);
return createNewTokenTransaction;
}
- createAccount: Creating new mint account.
- createInitializeMintInstruction: Initialize the new mint account.
- createAssociatedTokenAccountInstruction: Create a new token account for the new mint in your destination wallet.
- createMintToInstruction: Instruction that will define what tokens to mint, where to mint them to, and how many to mint.
- createCreateMetadataAccountV2Instruction: Associate our token meta data with this mint.
A lot of instructions! That may have been overwhelming. Just go through the comments and you will understand the stuff we have been trying to do.
The only thing left now is to execute the functions we have been creating so far. For that, we need a main
function. Let’s create that and get our own token.
Now, below this function, add that main()
function as well:
const main = async () => {
console.log(`---STEP 1: Uploading MetaData---`);
const userWallet = Keypair.fromSecretKey(new Uint8Array(secret));
let metadataUri = await uploadMetadata(MY_TOKEN_METADATA);
ON_CHAIN_METADATA.uri = metadataUri;
console.log(`---STEP 2: Creating Mint Transaction---`);
let mintKeypair = Keypair.generate();
console.log(`New Mint Address: `, mintKeypair.publicKey.toString());
const newMintTransaction: Transaction = await createNewMintTransaction(
solanaConnection,
userWallet,
mintKeypair,
userWallet.publicKey,
userWallet.publicKey,
userWallet.publicKey
);
console.log(`---STEP 3: Executing Mint Transaction---`);
let { lastValidBlockHeight, blockhash } =
await solanaConnection.getLatestBlockhash("finalized");
newMintTransaction.recentBlockhash = blockhash;
newMintTransaction.lastValidBlockHeight = lastValidBlockHeight;
newMintTransaction.feePayer = userWallet.publicKey;
const transactionId = await sendAndConfirmTransaction(
solanaConnection,
newMintTransaction,
[userWallet, mintKeypair]
);
console.log(`Transaction ID: `, transactionId);
console.log(
`Succesfully minted ${MINT_CONFIG.numberTokens} ${
ON_CHAIN_METADATA.symbol
} to ${userWallet.publicKey.toString()}.`
);
console.log(
`View Transaction: https://explorer.solana.com/tx/${transactionId}?cluster=devnet`
);
console.log(
`View Token Mint: https://explorer.solana.com/address/${mintKeypair.publicKey.toString()}?cluster=devnet`
);
};
main();
The final step is here! Running our code.
TypeScript uses ts-node
to compile and execute the code. Go into your terminal, and invoke ts-node
:
ts-node mint.ts
You will get an output like this, with your unique URL on Arweave.
Hit up the last link and you can see your token on the explorer. Here’s mine:
https://explorer.solana.com/address/3gRYAwYJyCE7HP6u5jpXV4Ufpf178nT3w24Kb5952h4c?cluster=devnet
Here’s the final version of both the codes:
Congratulations on minting your own token on Solana.