Skip to main content

How to Create a Fungible SPL Token on Solana with Metaplex

Created on
Updated on
Nov 26, 2024

11 min read

Overview

Are you creating a Whitelist token for your upcoming NFT mint? Or want to launch a fungible token for your next great dApp? Solana and Metaplex make it easy to do so right from your terminal! 

Prefer a video walkthrough? Follow along with Sahil and learn how to create a fungible token on Solana.
Subscribe to our YouTube channel for more videos!

On June 20, 2022, Solana deprecated the Solana Token List, the repository which housed metadata with all fungible SPL tokens. The list has been replaced by Metaplex's Fungible Token Standard. If you're familiar with the old standard or just getting started with your first SPL token, this guide is for you. 

What You Will Do

In this guide, you will create a wallet (and airdrop some SOL), create fungible token metadata using the Metaplex standard, upload token metadata to IPFS, and mint a new fungible SPL token on Solana devnet using Metaplex umi client.

What You Will Need

Set Up Your Environment

Create a new project directory in your terminal with:

mkdir mint-fungible-spl
cd mint-fungible-spl

Create two files, wallet.ts and mint.ts. We will use wallet.ts to create a new dev wallet and airdrop some Solana for testing. We'll use mint.ts to mint a new SPL token and upload our token metadata. 

echo > {wallet,mint}.ts

Initialize your project with the "yes" flag to use default values for your new package: 

yarn init --yes
#or
npm init --yes

Create a tsconfig.json file: 

tsc --init

Open tsconfig.json and uncomment (or add) this to your file: 

"resolveJsonModule": true

This will allow us to import .json files into our repository, which will be important later when we want to generate a Keypair from a PrivateKey.

Also double check that esModuleInterop is set to true to allow for us to use imports. 

Install Solana Web3 dependencies:

yarn add @solana/web3.js @metaplex-foundation/umi @metaplex-foundation/mpl-token-metadata @metaplex-foundation/umi-bundle-defaults bs58
#or
npm i @solana/web3.js @metaplex-foundation/umi @metaplex-foundation/mpl-token-metadata @metaplex-foundation/umi-bundle-defaults bs58

Your environment should look something like this: 

Ready Solana Environment

Alright! We're all ready to go. 

Set Up Your QuickNode Endpoint

To build on Solana, you'll need an API endpoint to connect with the network. You're welcome to use public nodes or deploy and manage your own infrastructure; however, if you'd like 8x faster response times, you can leave the heavy lifting to us. See why over 50% of projects on Solana choose QuickNode and sign up for a free account here.

We're going to use a Solana Devnet node. Copy the HTTP Provider link:

New Solana Endpoint

Create a Wallet and Airdrop SOL

In order to mint a fungible SPL token, we'll first want to create a Devnet wallet and airdrop SOL into it. If you already have a paper wallet, save it to your project directory as guideSecret.json. If it needs some devnet SOL, you can request some with the form below or from QuickNode multi-chain faucet:

🪂Request Devnet SOL

If you don't have a paper wallet, we'll programmatically generate a new one. Open wallet.ts and paste the following code in. We'll break it down in the next section.

wallet.ts
import { Keypair, LAMPORTS_PER_SOL, Connection } from "@solana/web3.js";
import * as fs from 'fs';
import bs58 from 'bs58';

//STEP 1 - Connect to Solana Network
const endpoint = 'https://example.solana-devnet.quiknode.pro/000000/'; //Replace with your QuickNode RPC Endpoint
const solanaConnection = new Connection(endpoint);

//STEP 2 - Generate a New Solana Wallet
const keypair = Keypair.generate();
console.log(`Generated new KeyPair. Wallet PublicKey: `, keypair.publicKey.toString());

//STEP 3 - Convert Private key to Base58
const privateKey = bs58.encode(keypair.secretKey);
console.log(`Wallet PrivateKey:`, privateKey);

//STEP 4 - Write Wallet Secret Key to a .JSON
const secret_array = keypair.secretKey
.toString() //convert secret key to string
.split(',') //delimit string by commas and convert to an array of strings
.map(value=>Number(value)); //convert string values to numbers inside the array

const secret = JSON.stringify(secret_array); //Covert to JSON string

fs.writeFile('guideSecret.json', secret, 'utf8', function(err) {
if (err) throw err;
console.log('Wrote secret key to guideSecret.json.');
});

//STEP 5 - Airdrop 1 SOL to new wallet
(async()=>{
const airdropSignature = solanaConnection.requestAirdrop(
keypair.publicKey,
LAMPORTS_PER_SOL,
);
try{
const txId = await airdropSignature;
console.log(`Airdrop Transaction Id: ${txId}`);
console.log(`https://explorer.solana.com/tx/${txId}?cluster=devnet`)
}
catch(err){
console.log(err);
}
})()

This script will perform 5 tasks: 

  1. Connect to the Solana Network (Make sure you replace the example URL on Line 6 with your actual QuickNode Endpoint URL that you saved in the previous step).
  2. Generate a new Wallet Keypair.
  3. Convert the Private key to Base58 and print it to the console.
  4. Write the Secret Key to a .json file that we'll use in the next step. Lines 16-22 are necessary to format the key as an array of numbers. Lines 24-27 use fs to export the array to a .json file.
  5. Airdrop 1 SOL to the new Wallet. 

Go ahead and run this script to create a new wallet and aidrop it 1 SOL: 

ts-node wallet.ts

You should see a new file, guideSecret.json in your project folder and a terminal log like this:

Generated new KeyPair. Wallet PublicKey:  G7ugoBpckgiL13KZMzWgQ751G27moDR9yVckQERrNnvj
Wallet PrivateKey: 3A4b34ob9hqUaTjR52eBLjQCLova1jqAG1zT59ypXSJvjh1cQzEExpBBbQLWT7gfbcS4KuYddBDiAaYrFCPE55Tu
Wrote secret key to guideSecret.json.
Airdrop Transaction Id: 58uYUd8PeimjWxf12dZRdqmURoMg1Q15SaaWwEET8U4VXU2pystyUsL9s2sq3cp2JTsUugPY7SUwW82S71SUo6Sj
https://explorer.solana.com/tx/58uYUd8PeimjWxf12dZRdqmURoMg1Q15SaaWwEET8U4VXU2pystyUsL9s2sq3cp2JTsUugPY7SUwW82S71SUo6Sj?cluster=devnet

Now, let's add the private key to the wallet of our choice (Phantom in this case):

Do not share Private Keys

We are printing the private keys just so that we can add the new wallet in Phantom. Never share your private keys with anyone; always store them securely and in environment files (.env) when using them in code, and make sure to add the environment files to .gitignore before publishing them on GitHub.

Adding Wallet in Phantom

Let's upload the Token icon and metadata.

Uploading Token Icon and Metadata

We will need a token icon (image) and metadata (JSON file) that are publicly accessible so that our token metadata can be properly displayed on block explorers, wallets, and exchanges.

To stay true to the ethos of decentralization, we will use IPFS to upload our token icon and metadata.

Although we reccomend using QuickNode's IPFS tool to pin and serve data, we'll demonstrate both methods below (e.g. 1. Using QuickNode's managed IPFS service; 2. Running a local IPFS node).


tip

To use IPFS on QuickNode, a Starter plan or higher is required. View our pricing plans here.

Navigate to the QuickNode Dashboard and click the IPFS tab on the left-sidebar.

Then, click on the Files tab and either click the New button and select Upload a file or simply drag the file you want to pin. We'll first upload the image below and then the metadata JSON file.

Token icon

Once the image is uploaded, click on the ellipses menu next to the file then click on Copy IPFS URL. We'll need to add this in the metadata file.

Now let's define our metadata. Solana has recently adopted Metaplex's Fungible Token Standard, which requires a name, symbol, description, and image (all are string values). Using the standard with your token mint will enable major platforms like Phantom Wallet or Solana Explorer to easily recognize your token and make it viewable by their users. Create a new metadata JSON file (token.json in this case).

token.json
{
"name": "Best Token Ever",
"symbol": "BTE",
"description": "This is the best token ever!",
"image": "IPFS_URL_OF_IMAGE"
}

Update IPFS_URL_OF_IMAGE with the IPFS URL of the image we got earlier, save the file and upload it. Once pinned, you'll see the file's name, along with other data such as the files CID, whether they're pinned, and the date they were pinned. Copy the IPFS URL of the metadata file too, We will need this URL later while minting our token.

Screenshot of dashboard with Token icon and metadata upload to IPFS

That was easy! Plus, we don't have to worry about running and managing our own IPFS node. You can click the ellipses next to the file and click Copy IPFS URL to get the URL where your file is hosted. Additionally, you can re-download or view your files details under this ellipses menu. Take a moment to try it.

Now that we have our files pinned on IPFS via QuickNode. Let's mint our token!

Build a Mint Tool

Import Dependencies

Open up mint.ts and import the following dependencies on line 1

import { percentAmount, generateSigner, signerIdentity, createSignerFromKeypair } from '@metaplex-foundation/umi'
import { TokenStandard, createAndMint, mplTokenMetadata } from '@metaplex-foundation/mpl-token-metadata'
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import secret from './guideSecret.json';

We'll cover these as we get to them in the guide, but we do want to note that final import, secret, which is importing the .json we created in early steps.

Establish Solana Connection

Create a Connection to the Solana network by replacing example URL with your QuickNode Endpoint URL in the code below and pasting it just below your imports:

const umi = createUmi('https://example.solana-devnet.quiknode.pro/000000/'); //Replace with your QuickNode RPC Endpoint

Initialize the Signer wallet

We are initializing the wallet from the secret and making it the signer for transactions.

const userWallet = umi.eddsa.createKeypairFromSecretKey(new Uint8Array(secret));
const userWalletSigner = createSignerFromKeypair(umi, userWallet);

*Note: setting numDecimals to 0 results in a token that cannot be subdivided. This might be relevant for something like a membership or whitelist mint token.

Creating the Metadata variable

Create a metadata variable like below and fill in your token details. Replace IPFS_URL_OF_METADATA with the actual IPFS URL of your metadata (token.json) file.

const metadata = {
name: "Best Token Ever",
symbol: "BTE",
uri: "IPFS_URL_OF_METADATA",
};

Creating the Mint PDA

We'll need to create a Mint Program Derived Address for our token. Learn about what are PDAs in Solana and what is Mint PDA for Tokens on Solana.

Below, we are creating a new Mint PDA and asking the umi client to use our wallet initialized earlier from secret as a signer and use the MPL Token Metadata to mint token metadata.

const mint = generateSigner(umi);
umi.use(signerIdentity(userWalletSigner));
umi.use(mplTokenMetadata())

Function to deploy Mint PDA and mint Tokens

In the below function, we send a transaction to deploy the Mint PDA and mint 1 million of our tokens. You can change the amount of tokens and console message according to whatever suits best for you.

createAndMint(umi, {
mint,
authority: umi.identity,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
sellerFeeBasisPoints: percentAmount(0),
decimals: 8,
amount: 1000000_00000000,
tokenOwner: userWallet.publicKey,
tokenStandard: TokenStandard.Fungible,
}).sendAndConfirm(umi)
.then(() => {
console.log("Successfully minted 1 million tokens (", mint.publicKey, ")");
})
.catch((err) => {
console.error("Error minting tokens:", err);
});
Why we add decimals?

Decimals are added to tokens to make them divisible to the smallest parts for simpler transactions and better liquidity. For example, in the above example, we are saying that our token's decimal would be 8, which means that our one token will be divisible to the 8th decimal value. Thus, while minting 1 million tokens, we have to add eight zeros ahead of the 1 million number (1000000_00000000).

Pull it All Together

Alright, the complete mint.ts should look like this:

mint.ts
import { percentAmount, generateSigner, signerIdentity, createSignerFromKeypair } from '@metaplex-foundation/umi'
import { TokenStandard, createAndMint, mplTokenMetadata } from '@metaplex-foundation/mpl-token-metadata'
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import secret from './guideSecret.json';

const umi = createUmi('https://example.solana-devnet.quiknode.pro/000000/'); //Replace with your QuickNode RPC Endpoint

const userWallet = umi.eddsa.createKeypairFromSecretKey(new Uint8Array(secret));
const userWalletSigner = createSignerFromKeypair(umi, userWallet);

const metadata = {
name: "Best Token Ever",
symbol: "BTE",
uri: "IPFS_URL_OF_METADATA",
};

const mint = generateSigner(umi);
umi.use(signerIdentity(userWalletSigner));
umi.use(mplTokenMetadata())

createAndMint(umi, {
mint,
authority: umi.identity,
name: metadata.name,
symbol: metadata.symbol,
uri: metadata.uri,
sellerFeeBasisPoints: percentAmount(0),
decimals: 8,
amount: 1000000_00000000,
tokenOwner: userWallet.publicKey,
tokenStandard: TokenStandard.Fungible,
}).sendAndConfirm(umi)
.then(() => {
console.log("Successfully minted 1 million tokens (", mint.publicKey, ")");
})
.catch((err) => {
console.error("Error minting tokens:", err);
});

Run Your Code

In your Terminal, type: 

ts-node mint.ts

Upon successful execution you should see an output like this:

Final results terminal output

You can view your token on Solana Devnet Explorer as well as in your Phantom wallet:


Token account on Solana devnet explorerToken being displayed in Phantom wallet

Note: If you've minted a fungible token in the past, you've probably submitted your token to the Solana Token Program Registry. That registry is now deprecated and no longer a necessary step. You've already uploaded the metadata on chain, so you're good to go!

Party Time! 🎉

Congrats! You just minted your own token on Solana using the new Metaplex fungible token standard and umi. Have some fun with this--we're excited to see what tokens you're creating! To learn more, check out some of our other Solana tutorials here

We ❤️ Feedback!

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

Share this guide