QuickNode Raises $60M Series B!
Read the Letter from our CEO.

How to Create a Solana NFT Collection using Candy Machine V3 and TypeScript

November 28, 2022

Overview

Are you ready to launch your NFT collection on Solana? If so, you've come to the right place! We'll explore Metaplex's new JS SDK which brings the power of Candy Machine to your JavaScript and TypeScript applications to allow for quick and easy minting. Metaplex has added several new features, notably Candy Guards (a tool for restricting or limiting access to NFT mints) which we'll implement in this guide.

What You Will Do

In this guide, you will create a new V3 Candy Machine through your own TypeScript application. Specifically, you will:

  1. Create a Candy Machine
  2. Add items to the Candy Machine
  3. Implement a Candy Guard for your Candy Machine
  4. Mint an NFT!

What You Will Need

Set Up Your Project

Create a new project directory in your terminal with the following:

set up your project

Copy
mkdir cm-v3-demo
cd cm-v3-demo

Create a file for your app, app.ts:

set up your project

Copy
echo > app.ts

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

set up your project

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

Create a tsconfig.json with .json importing enabled:

set up your project

Copy
tsc -init --resolveJsonModule true

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. In your terminal, type:

set up your project

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

Add Your Wallet and Airdrop SOL

Open the cm-v3-demo directory in your code editor of choice (we're using VSCode). To follow this guide, you will need the same Solana File System Wallet (keypair written to a guideSecret.json file) that is the authority of your NFT. If you do not already have an NFT or an NFT's authority wallet, create one by following our Guide: How to Mint an NFT on Solana using Typescript or just grab the source code directly (from this Github repository).

Make sure you save your wallet to your project directory as guideSecret.json (you can copy this directly from your mint-nft project directory).

You will also need to ensure your wallet has some devnet SOL. You can get some at the QuickNode Multi-Chain Faucet by pasting your wallet address and selecting Solana Devnet:



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:

set up your app

Copy
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
import { Metaplex, keypairIdentity, bundlrStorage, toMetaplexFile, toBigNumber, CreateCandyMachineInput, DefaultCandyGuardSettings, CandyMachineItem, toDateTime, sol, TransactionBuilder, CreateCandyMachineBuilderContext } from "@metaplex-foundation/js";
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.

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:

set up your app

Copy
const QUICKNODE_RPC = 'https://example.solana-devnet.quiknode.pro/0123456/'; // 👈 Replace with your QuickNode Solana Devnet HTTP Endpoint
const SESSION_HASH = 'QNDEMO'+Math.ceil(Math.random() * 1e9); // Random unique identifier for your session
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC, { commitment: 'finalized' , httpHeaders: {"x-session-hash": SESSION_HASH}});

Note that we have also added an httpHeader. This is just a random string that will create stickiness to one node. This will help improve the likelihood that our Metaplex calls to the Solana network will hit the same node on each request and reduce the risk of latency gaps between nodes.

Declare Variables

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

  • Your source wallet (a keypair derived from your secret key).
  • A source NFT metadata URL (note: in this guide, we will focus on the mechanics of the Candy Machine, so we will reuse an already uploaded NFT metadata and image. You're welcome to incorporate image and metadata uploading into this exercise--for more information on uploading to Arweave using the Metaplex JS SDK, check out our guide on How to Mint an NFT on Solana using Typescript).
  • The mint address for the NFT you would like to use as your Collection NFT (this is the cover image you might see for the collection in a wallet or exchange. More information at Metaplex.com).
  • A placeholder for your Candy Machine ID (we will update this later).
  • A Metaplex instance.
Add the following declarations below SOLANA_CONNECTION to establish the wallet we will be using and placeholders for the NFT metadata and the Candy Machine ID:

set up your app

Copy
const WALLET = Keypair.fromSecretKey(new Uint8Array(secret));
const NFT_METADATA = 'https://mfp2m2qzszjbowdjl2vofmto5aq6rtlfilkcqdtx2nskls2gnnsa.arweave.net/YV-mahmWUhdYaV6q4rJu6CHozWVC1CgOd9NkpctGa2Q'; 
const COLLECTION_NFT_MINT = ''; 
const CANDY_MACHINE_ID = '';

Establish a new Metaplex instance by calling our SOLANA_CONNECTION in Metaplex.make(). Our instance will use the Keypair we just created:

set up your app

Copy
const METAPLEX = Metaplex.make(SOLANA_CONNECTION)
    .use(keypairIdentity(WALLET));

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

Note: you will need to include an additional .use() call for a storage route if you intend to upload your own images and metadata (not necessary to follow this demo):

set up your app

Copy
    .use(bundlrStorage({
        address: 'https://devnet.bundlr.network',
        providerUrl: QUICKNODE_RPC,
        timeout: 60000,
    }))

Create Collection NFT

Candy Machine utilizes a feature, Metaplex Certified Collections, in order to verify collections on chain. Verification makes it easy for 3rd parties (e.g., exchanges) to group and track sets of NFTs and prevent fakes. To create our Candy Machine, we must first create a Collection NFT. This will serve as a the "cover image" that you might see on an exchange or wallet (e.g., Magic Eden or Phantom).

All we need to do is create a new NFT using, nfts().create(). Create a new function, createCollectionNft:

create collection nft

Copy
async function createCollectionNft() {
    const { nft: collectionNft } = await METAPLEX.nfts().create({
        name: "QuickNode Demo NFT Collection",
        uri: NFT_METADATA,
        sellerFeeBasisPoints: 0,
        isCollection: true,
        updateAuthority: WALLET,
      });

      console.log(`✅ - Minted Collection NFT: ${collectionNft.address.toString()}`);
      console.log(`     https://explorer.solana.com/address/${collectionNft.address.toString()}?cluster=devnet`);
}

Make sure to set isCollection as true, as this will note that this NFT is being used to define a collection.

Mint your NFT. At the end of your app, call createCollectionNft():

create collection nft

Copy
createCollectionNft();

And then, in your terminal, run:

create collection nft

Copy
ts-node app

Copy the MINT ID and update your COLLECTION_NFT_MINT variable (on line 11 for us):

create collection nft

Copy
const COLLECTION_NFT_MINT = 'GWWhaWp7kJ3WKLkQTKyy5DEEGGF4TnCfHXXKxsBSbcwg'; 

Before moving on, delete or comment out your call to createCollectionNft() as we will no longer need it.

Initiate Candy Machine

The Metaplex SDK allows you to initiate a Candy Machine with a single line of code 🤯:

initiate candy machine

Copy
const { candyMachine } = await METAPLEX.candyMachines().create(candyMachineSettings);

As you can see, however, we must first set some settings for our Candy Machine. Create a new function, generateCandyMachine, where we define our Candy Machine settings and then call candyMachines().create():

initiate candy machine

Copy
async function generateCandyMachine() {
    const candyMachineSettings: CreateCandyMachineInput<DefaultCandyGuardSettings> =
        {
            itemsAvailable: toBigNumber(3), // Collection Size: 3
            sellerFeeBasisPoints: 1000, // 10% Royalties on Collection
            symbol: "DEMO",
            maxEditionSupply: toBigNumber(0), // 0 reproductions of each NFT allowed
            isMutable: true,
            creators: [
                { address: WALLET.publicKey, share: 100 },
            ],
            collection: {
                address: new PublicKey(COLLECTION_NFT_MINT), // Can replace with your own NFT or upload a new one
                updateAuthority: WALLET,
            },
        };
    const { candyMachine } = await METAPLEX.candyMachines().create(candyMachineSettings);
    console.log(`✅ - Created Candy Machine: ${candyMachine.address.toString()}`);
    console.log(`     https://explorer.solana.com/address/${candyMachine.address.toString()}?cluster=devnet`);
}

Candy Machine settings define the size of the collection and key data points that are the same for every NFT (e.g., royalties, creators, symbol, mutability). We have defined some values for this example--feel free to modify them slightly to meet your own needs. A detailed overview of Candy Machine settings is available at Metaplex.com. Note that you can include Candy Guards at this step--we will add them next in a separate step to let you walk through the process of how to update a Candy Machine.

Go ahead and initiate your Candy Machine. At the end of your app, call generateCandyMachine():

initiate candy machine

Copy
generateCandyMachine();

And then, in your terminal, run:

initiate candy machine

Copy
ts-node app

You should see a Candy Machine ID and link to it on Solana Explorer:



Copy the Candy Machine ID and update your CANDY_MACHINE_ID variable (on line 11 for us):

initiate candy machine

Copy
const CANDY_MACHINE_ID = 'D2ARG7rXosZfnZwVBx3v7piG3H2tcYvWyw5YJCPuEpBU';

If you're getting an error or have questions, shoot us a line on Discord, and we will be happy to help.

Before moving on, delete or comment out your call to generateCandyMachine() as we will no longer need it.

Add Candy Guards

Candy Machine V3 includes a nifty new feature called "Candy Guards" (or just Guards for short). Guards are modular pieces of code that can restrict access to the mint of a Candy Machine and even add new features to it! (Source: Metaplex.com). As of November 2022, Metaplex has 16 different Guards available:

  • Address Gate: Restricts the mint to a single address.
  • Allow List: Uses a wallet address list to determine who is allowed to mint.
  • Bot Tax: Configurable tax to charge invalid transactions.
  • End Date: Determines a date to end the mint.
  • Gatekeeper: Restricts minting via a Gatekeeper Network, e.g., Captcha integration.
  • Mint Limit: Specifies a limit on the number of mints per wallet.
  • Nft Burn: Restricts the mint to holders of a specified collection, requiring a burn of the NFT.
  • Nft Gate: Restricts the mint to holders of a specified collection.
  • Nft Payment: Set the price of the mint as an NFT of a specified collection.
  • Redeemed Amount: Determines the end of the mint based on the total amount minted.
  • Sol Payment: Set the price of the mint in SOL.
  • Start Date: Determines the start date of the mint.
  • Third-Party Signer: Requires an additional signer on the transaction.
  • Token Burn: Restricts the mint to holders of a specified token, requiring a burn of the tokens.
  • Token Gate: Restricts the mint to holders of a specified token.
  • Token Payment: Set the price of the mint in token amount. (Source: Metaplex.com)
Metaplex allows you to mix, match, and stack multiple Guards to customize your NFT's mint. You can even create various "Guard Groups" to create custom mint experiences for different users.

For our mint, we will set a Start Date, a Sol Payment, and a Limit of 2 mints per user. Create a new function, updateCandyMachine():

add candy guards

Copy
async function updateCandyMachine() {
    const candyMachine = await METAPLEX
        .candyMachines()
        .findByAddress({ address: new PublicKey(CANDY_MACHINE_ID) });

    const { response } = await METAPLEX.candyMachines().update({
        candyMachine,
        guards: {
            startDate: { date: toDateTime("2022-10-17T16:00:00Z") },
            mintLimit: {
                id: 1,
                limit: 2,
            },
            solPayment: {
                amount: sol(0.1),
                destination: METAPLEX.identity().publicKey,
            },
        }
    })
    
    console.log(`✅ - Updated Candy Machine: ${CANDY_MACHINE_ID}`);
    console.log(`     https://explorer.solana.com/tx/${response.signature}?cluster=devnet`);
}
Our function does three things:

  1. Fetch our Candy Machine: look up our Candy Machine by ID and return a CandyMachine object by calling .candyMachines().findByAddress().
  2. Update our Candy Machine by calling candyMachines().update(). We first pass our candyMachine from step 1 and then define our guards. Each guard has a specific Object Key with unique GuardSettings. Settings for each Candy Guard can be found at Metaplex.com.
    • Start Date: pass a start date using Metaplex's toDateTime method.
    • Mint Limit: pass a unique id for this guard (necessary to allow different limits to be tracked using Guard Groups) and a limit amount.
    • Sol Payment: pass an amount of SOL to collect using Metaplex's sol method and the destination Public Key (your mint's treasury).
  3. Log a link to the transaction on Solana Explorer.
Add your Candy Guards by calling updateCandyMachine(). At the end of your app, add the following:

add candy guards

Copy
updateCandyMachine();

And then, in your terminal, run:

add candy guards

Copy
ts-node app

You should see something like this in your terminal:



Nice job. Before moving on, delete or comment out your call to updateCandyMachine() as we will no longer need it.

Let's add some items to our Candy Machine!

Add Items to Your Candy Machine

Adding items to your Candy Machine is really similar to updating it. Instead of .update(), however, we need to invoke .insertItems(). Create a new function, addItems() and add the following code:

add items to your candy machine

Copy
async function addItems() {
    const candyMachine = await METAPLEX
        .candyMachines()
        .findByAddress({ address: new PublicKey(CANDY_MACHINE_ID) }); 
    const items = [];
    for (let i = 0; i < 3; i++ ) { // Add 3 NFTs (the size of our collection)
        items.push({
            name: `QuickNode Demo NFT # ${i+1}`,
            uri: NFT_METADATA
        })
    }
    const { response } = await METAPLEX.candyMachines().insertItems({
        candyMachine,
        items: items,
      },{commitment:'finalized'});

    console.log(`✅ - Items added to Candy Machine: ${CANDY_MACHINE_ID}`);
    console.log(`     https://explorer.solana.com/tx/${response.signature}?cluster=devnet`);
}

Our function does three things:

  1. Fetches our Candy Machine: look up our Candy Machine by ID and return a CandyMachine object by calling .candyMachines().findByAddress().
  2. Creates an Array of Items to Add: the insertItems() method requires us to pass an items array that includes the name and uri of each item to be added to the Candy Machine. Since we set the size of our Candy Machine as 3 in our "Initiate Candy Machine" step, we will add 3 items using a simple for loop. If you are adding unique NFTs, add your custom logic here. Because Solana transaction sizes are limited, we can only pass a limited number of items per call. "The number of items we can insert per transaction will depend on the Name Length and URI Length attributes defined in the Config Line Settings. The shorter our names and URIs are, the more we can fit into a transaction." (Metaplex.com).
  3. Inserts Items into Candy Machine by Calling candyMachines().insertItems(): We first pass our candyMachine from step 1, and then pass our items defined in Step 2.
  4. Logs a link to the transaction on Solana Explorer.
Go ahead and add your items by calling addItems(). At the end of your app, add the following:

add items to your candy machine

Copy
addItems();

And then, in your terminal, run:

add items to your candy machine

Copy
ts-node app

You should see something like this in your terminal:



Before moving on, delete or comment out your call to addItems() as we will no longer need it.

You should now have a loaded Candy Machine with guards in place. Let's test it out!

Mint an NFT

Much like our previous steps, we will need to fetch our Candy Machine and call a new method, candyMachines().mint(). Create a new function, mintNft():

mint an nft

Copy
async function mintNft() {
    const candyMachine = await METAPLEX
        .candyMachines()
        .findByAddress({ address: new PublicKey(CANDY_MACHINE_ID) }); 
    let { nft, response } = await METAPLEX.candyMachines().mint({
        candyMachine,
        collectionUpdateAuthority: WALLET.publicKey,
        },{commitment:'finalized'})

    console.log(`✅ - Minted NFT: ${nft.address.toString()}`);
    console.log(`     https://explorer.solana.com/address/${nft.address.toString()}?cluster=devnet`);
    console.log(`     https://explorer.solana.com/tx/${response.signature}?cluster=devnet`);
}

This code should be starting to look pretty familiar--here's what it does:

  1. Fetches our Candy Machine: look up our Candy Machine by ID and return a CandyMachine object by calling .candyMachines().findByAddress().
  2. Mints an NFT by calling candyMachines().mint(): We first pass our candyMachine from step 1, and then pass our wallet's public key as the collectionUpdateAuthority (required because "this information does not live in the candyMachine model and it is required by the underlying mint instructions" Source: Metaplex.com).
  3. Logs a link to the mint and transaction on Solana Explorer.
Finally, mint an NFT by calling mintNft(). At the end of your app, add the following:

mint an nft

Copy
mintNft();

And then, in your terminal, run:

mint an nft

Copy
ts-node app

You should see something like this in your terminal:



Nice job! Let's test out one of our Candy Guards. Rerun your script. You should see a similar result and mint a 2nd NFT to your wallet. Then try it a third time.

The third try should give you some problems. Why? Well, if you dig into your error code, you should see something like this:

mint an nft

Copy
  title: CandyGuardProgram > The maximum number of allowed mints was reached,
  problem: The program [CandyGuardProgram] at address [Guard1JwRhJkVH6XZhzoYxeBVQe872VH6QggF4BWmS9g] raised an error of code [6029] that translates to The maximum number of allowed mints was reached.

If you recall, we created a mintLimit Candy Guard that limits any wallet to only be able to mint 2 NFTs, so the guard is preventing us from minting a third! Pretty cool, huh?

Next Steps and Wrap Up

You now have a couple of useful scripts for interacting with Candy Machine using TypeScript! We are excited to see what you build with this. Hop on Discord or Twitter and share your projects!

Looking for an extra challenge? Try creating a mint site using the scripts we just learned and the Solana dApp Scaffold!

We <3 Feedback!

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

Related articles 41

Solana NFT Metadata Deep Dive
Published: Dec 16, 2022
Updated: Dec 16, 2022

Even in the 2022 bear market, Solana NFTs are showing no signs of slowing down. If you are building with Solana NFTs, understanding your NFTs' metadata will make it easier for you to deploy...

Continue reading
How to Transfer SPL Tokens on Solana
Published: Sep 23, 2022
Updated: Sep 23, 2022

Sending Solana Program Library (SPL) Tokens is a critical mechanism for Solana development. Whether you are airdropping whitelist tokens to your community, bulk sending NFTs to another wallet,...

Continue reading
Como crear un NFT en SOLANA
Published: Dec 27, 2021
Updated: Sep 23, 2022

¡Hola querido lector! Bienvenidos a una nueva guía de Solana.Solana es una blockchain que promete mucho a la hora de intentar resolver los problemas de escalabilidad que...

Continue reading
How to Mint an NFT on Solana
Published: Aug 27, 2021
Updated: Sep 23, 2022

Updated at: April 10, 2022Welcome to another QuickNode guide on Solana - the up-and-coming blockchain that seeks to solve the scalability issues of Ethereum. We will be walking through...

Continue reading
How to Send Bulk Transactions on Solana
Published: Aug 31, 2022
Updated: Oct 3, 2022

Are you running a batch process that has many transactions? Perhaps an airdrop to your community's NFT holders or a token distribution to early users of your dApp. Solana transaction...

Continue reading
How to Burn SPL Tokens on Solana
Published: Jan 13, 2023
Updated: Jan 13, 2023

🔥🔥🔥Building a deflationary token protocol? Want to destroy a rugged NFT? Just want to have some fun with your community? The Solana SPL Token Program's Burn feature is what...

Continue reading
How to Get Transaction Logs on Solana
Published: Jun 24, 2022
Updated: Oct 27, 2022

Ever need to pull all the transactions associated with a Wallet? Want to see all of the mint transactions associated with a Candy Machine? Or maybe see transaction history of an NFT? Solana's...

Continue reading
How to Use Priority Fees on Solana
Published: Jan 13, 2023
Updated: Jan 17, 2023

Are you looking to get your transactions confirmed as quickly as possible on Solana? This guide will show you how to use priority fees to bid for priority in the leader's queue and confirm...

Continue reading
Solana Fundamentals Reference Guide
Published: Oct 27, 2022
Updated: Oct 27, 2022

The Solana blockchain is a powerful tool, delivering thousands of transactions per second with almost no-cost transaction fees. If you are new to Web3 or have developed on EVM-based...

Continue reading