Skip to main content

How to Mint an NFT on Solana using Typescript

Updated on
Dec 11, 2023

12 min read

Before you start this guide

This guide will give you an in-depth walkthrough of how to mint an NFT on Solana using TypeScript through a series of technical steps. If you’d like to accomplish this task quicker and leave the heavy lifting to us, we recommend the Crossmint NFT Mint API [mainnet] add-on. By using the Crossmint NFT Mint API [mainnet], you can airdrop NFTs to Solana users and email addresses with a single POST request. Use the NFT Mint API to create collections and mint NFTs easily!

Try the Crossmint NFT Mint API [mainnet]

Overview

Minting 10,000 NFTs with a Candy Machine is fun, but there are many instances where we might need to mint a single NFT (e.g., 1 of 1 art, music, etc.). Metaplex, the organization that created the Solana metadata standard, has recently developed some handy JS SDKs that make creating an NFT from your terminal easy.

What You Will Do

In this guide, you will mint an NFT with metadata to Solana's devnet from your terminal using the Solana Web3 library and Metaplex JS SDKs. We will cover three key steps to NFT minting: 

  1. Upload an Image
  2. Upload Metadata
  3. Mint NFT

What You Will Need

  • Nodejs (version 16.15 or higher) installed
  • Typescript experience and ts-node installed

Solana NFT Background Info

Before building our app, let's talk about NFTs and Solana's NFT architecture. So what the heck is an NFT? A non-fungible token is effectively a set of data stored on chain that is unique from any other. In short, there's just one of them. On Solana, we achieve this by using the SPL Token Program and creating a token with a supply of 1. Further, NFTs cannot be subdivided, meaning you cannot have a third of an NFT (note: there are some advanced NFT protocols around fractionalization, but typically an NFT cannot be broken into smaller units). On Solana, we also achieve this using the SPL Token Program by setting decimals to 0 (meaning it cannot be fractionalized).

Though creating an SPL token with a supply of 1 and decimals of 0 is technically an NFT defined by its unique Mint ID, the SPL token program is limited regarding some of the types and amount of data that can be stored. Enter Metaplex. The Metaplex Metadata program sets a standard for on and off-chain metadata. Through a Program Derived Address associated with the Mint account, Metaplex allows users to assign more detailed metadata to a mint (e.g., name, description, mutability, seller fees, etc.). One notable field in the Metaplex metadata is called "URI." This is an address that points to specific off-chain metadata. Due to rent fees and speed constraints, Metaplex expects certain data to be stored off-chain (e.g., image/image URI, NFT traits/characteristics). The following figure from Metaplex visualizes the relationships between these fields:

Source: https://docs.metaplex.com/programs/token-metadata/overview

In this guide, we will first upload our image to Arweave and fetch its URI, then we will create the off-chain metadata and upload it to Arweave, and finally, we will mint our token and define on-chain metadata that points to our off-chain metadata.

Set Up Your Project

Create a new project directory in your terminal with:

mkdir mint-nft
cd mint-nft

Create a file for your app, app.ts:

echo > app.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 with .json importing enabled:

tsc -init --resolveJsonModule true

Finally, create a folder for your uploads:

mkdir uploads

Save an image (.png or .jpg) to this folder as image.png. We will be using this pixelated beauty:

Install Solana Web3 Dependency

We will need to add the Solana Web3 and SPL Token libraries for this exercise. Additionally, we will use Metaplex's JS SDK and MPL Token Metadata libraries. In your terminal type:

yarn add @solana/web3.js @metaplex-foundation/js
#or
npm install @solana/web3.js @metaplex-foundation/js

Create a Wallet and Airdrop SOL

You'll need to create a Solana File System Wallet (keypair written to a guideSecret.json file) and airdrop some SOL to it. You can do this using Solana CLI or use this script we have created for you. If you already have a paper wallet but just need some Devnet SOL, you can request some below:

🪂Request Devnet SOL

Make sure you save your wallet to your project directory as guideSecret.json.

After set up, your environment should look something like this:

Set Up Your App

Import Necessary Dependencies

Open app.ts, and paste the following imports on line 1:

import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { Metaplex, keypairIdentity, bundlrStorage, toMetaplexFile, toBigNumber } from "@metaplex-foundation/js";
import * as fs from 'fs';
import secret from './guideSecret.json';

In addition to the wallet we created in the previous step, we are also importing a few essential methods and classes from the Solana Web3 and Metaplex JS libraries. Also, since we will be uploading files from our system, we need to import fs, the file system library.

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:

Inside app.ts under your import statements, declare your RPC and establish your Connection to Solana:

const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/';
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);

Declare Variables

You will need to declare a few variables to run your script: 

  • Your source wallet (a keypair derived from your secret key). 
  • A Metaplex instance. 
  • A CONFIG file (we will use this to store information about the NFT we're going to mint).

Add the following declarations below SOLANA_CONNECTION to establish the wallet we will be using:

const WALLET = Keypair.fromSecretKey(new Uint8Array(secret));

Establish a new Metaplex instance by calling our SOLANA_CONNECTION in Metaplex.make(). Our instance will use the Keypair we just created and bundlrStorage (an option for uploading files to Arweave using Solana):

const METAPLEX = Metaplex.make(SOLANA_CONNECTION)
.use(keypairIdentity(WALLET))
.use(bundlrStorage({
address: 'https://devnet.bundlr.network',
providerUrl: QUICKNODE_RPC,
timeout: 60000,
}));

By including our network connection, wallet, and storage route, the API will make it easy for us to submit transactions to the Solana network.

Define NFT Characteristics

We will create a CONFIG object containing some metadata that we want to include in our NFT. Create a new const, CONFIG, and include the following attributes:

const CONFIG = {
uploadPath: 'uploads/',
imgFileName: 'image.png',
imgType: 'image/png',
imgName: 'QuickNode Pixel',
description: 'Pixel infrastructure for everyone!',
attributes: [
{trait_type: 'Speed', value: 'Quick'},
{trait_type: 'Type', value: 'Pixelated'},
{trait_type: 'Background', value: 'QuickNode Blue'}
],
sellerFeeBasisPoints: 500,//500 bp = 5%
symbol: 'QNPIX',
creators: [
{address: WALLET.publicKey, share: 100}
]
};

Feel free to use your own values here. Just make sure if you add your own creators to use PublicKeys for the address, and double check that the total share equals 100.

Create and Call Main Function

Create an async function, main, that we will use to execute all of the steps of our code:

async function main() {
console.log(`Minting ${CONFIG.imgName} to an NFT in Wallet ${WALLET.publicKey.toBase58()}.`);
}

main();

This won't do much, but you should be able to run your code to make sure there are no errors at this point:

ts-node app

You should see a log in your console that you're about to mint an NFT--so let's do it already! If you're getting an error or have questions, shoot us a line on Discord, and we will be happy to help.

Upload Your Image

Before doing anything, we will need to upload the image we will use for our NFT to a decentralized storage platform. This is because we must pass the URI of the NFT image into the NFT's metadata. If you already have an image hosted with a URI, feel free to define that in your CONFIG file and skip to step 2. Otherwise, let's create a new async function, uploadImage before our main function. The function should accept a filePath and a fileName and return a promise of a string (the URI pointing to our uploaded image).

async function uploadImage(filePath: string,fileName: string): Promise<string>  {
console.log(`Step 1 - Uploading Image`);

}

To upload our image, we must first use the File System program to create a Buffer. We can do this using the fs.readFileSync method. Inside uploadImage, add:

    const imgBuffer = fs.readFileSync(filePath+fileName);

To use the Metaplex uploader, we will need to convert the returned Buffer to a MetaplexFile using toMetaplexFile. On the following line, add:

    const imgMetaplexFile = toMetaplexFile(imgBuffer,fileName);

Use the Metaplex storage().upload() method to upload your file. Because we have set bundlr as our storage provider in our Metaplex instance, the upload function will use Bundlr to upload the file to Arweave. Log and return your results:

    const imgUri = await METAPLEX.storage().upload(imgMetaplexFile);
console.log(` Image URI:`,imgUri);
return imgUri;

Finally, add this step inside of your main() function by calling uploadImage and passing uploadPath and imgFileName from your CONFIG file:

    //Step 1 - Upload Image
const imgUri = await uploadImage(CONFIG.uploadPath, CONFIG.imgFileName);

You should be able to test your function by running your script in the terminal:

ts-node app

After a few seconds, you should see an Arweave link for your image in your terminal. Nice job!

Now let's add that URI to our metadata and upload our metadata.

Upload Metadata

Metadata is more or less what makes your NFT special. It includes the image, any defining traits, assigns it to a collection, etc. Metaplex makes uploading metadata easy--just a single call of nfts().uploadMetadata(). Let's start by creating a new function, uploadMetadata that accepts 5 parameters: imgUri, imgType, nftName, description, and attributes:

async function uploadMetadata(imgUri: string, imgType: string, nftName: string, description: string, attributes: {trait_type: string, value: string}[]) {
console.log(`Step 2 - Uploading Metadata`);

}

Next, call nfts().uploadMetadata(). We will need to pass our parameters as follows:

    const { uri } = await METAPLEX
.nfts()
.uploadMetadata({
name: nftName,
description: description,
image: imgUri,
attributes: attributes,
properties: {
files: [
{
type: imgType,
uri: imgUri,
},
]
}
});
console.log(' Metadata URI:',uri);
return uri;

There's not a lot to this--but you may want to add extra elements to your metadata. A complete list of Metaplex's token standards is available here.

Let's go ahead and call our function in main(). After uploadImage, call uploadMetadata using our CONFIG to populate the parameters:

    //Step 2 - Upload Metadata
const metadataUri = await uploadMetadata(imgUri, CONFIG.imgType, CONFIG.imgName, CONFIG.description, CONFIG.attributes);

If you want to test your function, you'll need to log metadataUri and then run ts-node app. Whenever you're ready, let's mint this to a token on Solana!

Mint NFT

Like our previous functions, the Metaplex API simplifies this process and allows us to mint our NFT with a single method, nfts().create(). Unlike the metadata in the previous step, in this final step, we must pass in some metadata that will be stored on the Solana chain directly.

After your uploadMetadata() function and before main(), create a new async function, mintNft():

async function mintNft(metadataUri: string, name: string, sellerFee: number, symbol: string, creators: {address: PublicKey, share: number}[]) {
console.log(`Step 3 - Minting NFT`);
}

Finally, inside of our function, call nfts().create() passing our metadataUri and other function parameters and log the results:

    const { nft } = await METAPLEX
.nfts()
.create({
uri: metadataUri,
name: name,
sellerFeeBasisPoints: sellerFee,
symbol: symbol,
creators: creators,
isMutable: false,
});
console.log(` Success!🎉`);
console.log(` Minted NFT: https://explorer.solana.com/address/${nft.address}?cluster=devnet`);

This function will mint a token on-chain that links to our metadata using the Metaplex Metadata Program. You can optionally pass a maxSupply parameter to allow editions of the NFT to be minted later (by default this value is maxSupply: toBigNumber(0) meaning future printing is disabled).

Now all you need to do is call this function inside of main(), passing parameters from CONFIG:

    //Step 3 - Mint NFT
mintNft(metadataUri, CONFIG.imgName, CONFIG.sellerFeeBasisPoints, CONFIG.symbol, CONFIG.creators);

Run Your Code💨

You're all set! If you would like to double-check your code against ours, our entire workbook is available on GitHub here.

When you're ready, in terminal type:

ts-node app

YES! Do you see something like this?

Go ahead and follow the link in your console to Solana Explorer. You should be able to see your NFT on the page:

If you scroll down, you should be able to see the on-chain metadata with a link to our uploaded metadata:

And the attributes from our CONFIG file:

You should also be able to see your NFT in your wallet (make sure you have selected Devnet in your wallet's developer settings):

Great job.

Next Steps and Wrap Up

You now have a handy and reusable script to mint NFTs right from your terminal! Pretty handy, right? Want to keep building? Here are a couple of ideas to keep building off what you just learned:

  1. Use an SPL token transfer to send this NFT to another wallet. (Guide: How to Transfer SPL Tokens on Solana)
  2. Use the Solana dApp Scaffold to create an NFT minting widget. (Guide: How to Connect Users to Your dApp with the Solana Wallet Adapter and Scaffold) Note: You'll have to use a Wallet Adapter Identity instead of keypairIdentity to create your instance of Metaplex in a front end with Solana Wallet Adapter.

If you give either a shot or have any questions for us, let us know! Find us on Discord and Twitter!

We <3 Feedback!

If you have any feedback or questions on this guide, let us know. We'd love to hear from you!

Share this guide