Skip to main content

How to Launch an NFT Collection with Metaplex Core Candy Machine

Created on
Updated on
Dec 17, 2024

17 min read

Overview

Metaplex recently launched a Candy Machine tool for their new Metaplex Core standard. Candy Machine is a tool that allows creators to sell/distribute bulk NFTs fairly and transparently, and Metaplex Core is a low-cost/flexible standard for minting NFTs on Solana.

This guide will teach you how to create a Core Candy Machine and mint NFTs using the Umi JavaScript library.

Let's jump in!

What You Will Do

Write a script to:

  • Upload NFT images and metadata to IPFS
  • Create a Core NFT Collection
  • Deploy a Core Candy Machine
  • Mint NFTs using the Candy Machine
  • Delete the Candy Machine and recover rent

What You Will Need

This guide assumes basic knowledge of the Metaplex Core standard and Candy Machine. If you are new to these concepts, check out the following guides before getting started:

You will need the following tools and dependencies to complete this guide:

Dependencies Used in this Guide

DependencyVersion
@metaplex-foundation/umi^0.9.1
@metaplex-foundation/umi-bundle-defaults^0.9.1
@metaplex-foundation/mpl-core-candy-machine^0.2.0
solana cli1.18.1

Let's get started!

What is Core Candy Machine?

Core Candy Machine ("CCM") is a new tool that brings the power of Metaplex's Candy Machine and Candy Guards to the Metaplex Core standard. It enables creators to distribute NFTs to their audience fairly and transparently using Candy Guards to specify the rules for minting. Candy Machines are temporary accounts that are deployed to the Solana blockchain that enable users to mint NFTs.

Set Up Your Project

Create a new project directory. In your terminal, enter:

mkdir core-cm-example
cd core-cm-example

Initiate a new project with npm:

npm init -y

Create a new Typescript file in your project directory:

echo > app.ts

Download Necessary Programs

We will be running our tests today on a local validator. Because the Candy Machine and Candy Guard programs are not included in your local validator, we will need to download them from the network to initiate them when we start our local validator.

solana program dump -um CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d core.so
solana program dump -um CMACYFENjoBMHzapRXyo1JZkVS6EtaDDzkjMrmQLvr4J candy_machine_core.so
solana program dump -um CMAGAKJ67e9hRZgfC5SFTbZH8MgEmtqazKXjmkaJjWTJ candy_guard.so

These commands will write the program executable data to files in your project directory. We will use these later when we are ready to run our script.

Install Dependencies

Next, install the necessary dependencies for your project:

npm install @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-core-candy-machine

Add the following imports to your app.ts file:

import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
import {
mplCandyMachine as mplCoreCandyMachine, create,
addConfigLines,
fetchCandyMachine,
deleteCandyMachine,
mintV1,
} from "@metaplex-foundation/mpl-core-candy-machine";
import {
Umi,
PublicKey,
generateSigner,
transactionBuilder,
keypairIdentity,
some,
sol,
dateTime,
TransactionBuilderSendAndConfirmOptions
} from '@metaplex-foundation/umi';
import { createCollectionV1 } from '@metaplex-foundation/mpl-core';
import { setComputeUnitLimit } from '@metaplex-foundation/mpl-toolbox';

We will use these imports to create and deploy our Core Candy Machine--we will discuss these functions in more detail as we go.

Define Key Variables

Before we start building, let's define our umi instance using our local Solana cluster and the keypairs we will use to sign transactions.

const umi = createUmi('http://127.0.0.1:8899').use(mplCoreCandyMachine());

const keypair = generateSigner(umi);
const collectionMint = generateSigner(umi);
const treasury = generateSigner(umi);
const candyMachine = generateSigner(umi);

umi.use(keypairIdentity(keypair));

const options: TransactionBuilderSendAndConfirmOptions = {
send: { skipPreflight: true },
confirm: { commitment: 'processed' }
};

async function main() {

console.log(`Testing Candy Machine Core...`);
console.log(`Important account information:`)
console.table({
keypair: keypair.publicKey.toString(),
collectionMint: collectionMint.publicKey.toString(),
treasury: treasury.publicKey.toString(),
candyMachine: candyMachine.publicKey.toString(),
});

// 1. Airdrop 100 SOL to the keypair

// 2. Create a collection

// 3. Create a Candy Machine

// 4. Add items to the Candy Machine

// 5. Verify the Candy Machine configuration

// 6. Mint NFTs

// 7. Verify the Candy Machine configuration

// 8. Delete the Candy Machine

}
main().catch(console.error);

Here's a summary of what we added:

  • We utilized createUmi to create an instance of the Umi library and pass in the URL of our local Solana cluster. We also used the mplCoreCandyMachine function to add the Candy Machine program to our Umi instance.
  • We used generateSigner to create keypairs for our keypair (our payer/primary signer), collectionMint, treasury, and candyMachine.
  • We set the keypair as the primary signer for our Umi instance.
  • We defined an options object that we will use to send and confirm transactions. For our test environment, we are skipping the preflight check and setting the commitment level to processed--this will ensure that our transactions are processed quickly. You will likely want to change this for a production environment.
  • In addition to our constants, we have defined a main function to run our script. We will fill in the steps of the main function as we go.

Create Helper Function

We will create one helper function that we can use to fetch our collection and verify that the on-chain data matches our expectations. Under main, add the following:

interface ExpectedCandyMachineState {
itemsLoaded: number;
itemsRedeemed: number;
authority: PublicKey;
collection: PublicKey;
}

async function checkCandyMachine(
umi: Umi,
candyMachine: PublicKey,
expectedCandyMachineState: ExpectedCandyMachineState,
step?: number
) {
try {
const loadedCandyMachine = await fetchCandyMachine(umi, candyMachine, options.confirm);
const { itemsLoaded, itemsRedeemed, authority, collection } = expectedCandyMachineState;
if (Number(loadedCandyMachine.itemsRedeemed) !== itemsRedeemed) {
throw new Error('Incorrect number of items available in the Candy Machine.');
}
if (loadedCandyMachine.itemsLoaded !== itemsLoaded) {
throw new Error('Incorrect number of items loaded in the Candy Machine.');
}
if (loadedCandyMachine.authority.toString() !== authority.toString()) {
throw new Error('Incorrect authority in the Candy Machine.');
}
if (loadedCandyMachine.collectionMint.toString() !== collection.toString()) {
throw new Error('Incorrect collection in the Candy Machine.');
}
step && console.log(`${step}. ✅ - Candy Machine has the correct configuration.`);
} catch (error) {
if (error instanceof Error) {
step && console.log(`${step}. ❌ - Candy Machine incorrect configuration: ${error.message}`);
} else {
step && console.log(`${step}. ❌ - Error fetching the Candy Machine.`);
}
}
}

Here, we are creating a TypeScript interface that defines the expected state of our Candy Machine. Our helper function, checkCandyMachine, will accept an umi instance, the candyMachine public key, and the expected state of the Candy Machine. The function uses the fetchCandyMachine function to get the current state of the Candy Machine and compares it to the expected state. If the state does not match, the function will throw an error.

Step 1 - Airdrop SOL

Since we will be working on a local host, we will need to airdrop SOL every time we run our script to our keypair. Add the following code to your main function:

    // 1. Airdrop 100 SOL to the keypair
try {
await umi.rpc.airdrop(keypair.publicKey, sol(100), options.confirm);
console.log(`1. ✅ - Airdropped 100 SOL to the ${keypair.publicKey.toString()}`)
} catch (error) {
console.log('1. ❌ - Error airdropping SOL to the wallet.');
}

We use the umi.rpc.airdrop function to send 100 SOL to our keypair.

Step 2 - Create a Collection

Next, we will create a collection for our NFTs to be minted into. Add the following code to your main function:

    // 2. Create a collection
try {
await createCollectionV1(umi, {
collection: collectionMint,
name: 'My Collection',
uri: 'https://example.com/my-collection.json',
}).sendAndConfirm(umi, options);
console.log(`2. ✅ - Created collection: ${collectionMint.publicKey.toString()}`)
} catch (error) {
console.log('2. ❌ - Error creating collection.');
}

We are using the createCollectionV1 function to create a new collection. We are passing in the collectionMint keypair, the name of the collection, and the URI where the collection metadata will be stored. Make sure to update the URI with your own metadata, and ensure that it complies with the Metaplex Non-Fungible Token Metadata standard.

Step 3 - Create a Candy Machine

Now that we have a collection, we can use the create function from the mpl-core-candy-machine package to create a new Candy Machine. Add the following code to your main function:

    // 3. Create a Candy Machine
try {
const createIx = await create(umi, {
candyMachine,
collection: collectionMint.publicKey,
collectionUpdateAuthority: umi.identity,
itemsAvailable: 3,
authority: umi.identity.publicKey,
isMutable: false,
configLineSettings: some({
prefixName: 'Quick NFT #',
nameLength: 11,
prefixUri: 'https://example.com/metadata/',
uriLength: 29,
isSequential: false,
}),
guards: {
botTax: some({ lamports: sol(0.001), lastInstruction: true }),
solPayment: some({ lamports: sol(1.5), destination: treasury.publicKey }),
startDate: some({ date: dateTime('2023-04-04T16:00:00Z') }),
// All other guards are disabled...
}
})
await createIx.sendAndConfirm(umi, options);
console.log(`3. ✅ - Created Candy Machine: ${candyMachine.publicKey.toString()}`)
} catch (error) {
console.log('3. ❌ - Error creating Candy Machine.');
}

The create function takes our umi instance and an object with the following properties:

  • candyMachine: The public key of the Candy Machine account
  • collection: The public key of the collection we created in step 2
  • collectionUpdateAuthority: The public key of the collection update authority
  • itemsAvailable: The number of NFTs that will be available in the Candy Machine
  • authority: The public key of the authority that will control the Candy Machine
  • isMutable: A boolean that determines if the NFTs from the Candy Machine are mutable or not
  • configLineSettings: An object that defines the configuration of the NFTs that will be minted. This can help reduce the size of the NFT metadata by using prefixes and lengths for the name and URI. For example, if we set prefixName to 'Quick NFT #' and nameLength to 11, the name of the NFT will be 'Quick NFT #1', 'Quick NFT #2', etc., rather than having to store that same prefix in the Candy Machine for each NFT.
  • guards: An object that defines the rules for minting NFTs from the Candy Machine. In this example, we set a bot tax of 0.001 SOL, a payment of 1.5 SOL to the treasury, and a start date of April 4, 2023. You can add additional guards as needed.

For more information on config settings and candy guards, check out our guide on Candy Machine v. 3 or the Metaplex Core Candy Machine documentation.

Step 4 - Add Items to the Candy Machine

Now that our candy machine is initiated, we will need to add items to it. Since we used the configLineSettings object to define the configuration of the NFTs that will be minted, we can use the addConfigLines function to add items to the Candy Machine with our abbreviated names and URIs (e.g., 1 instead of Quick NFT #1 and 1.json instead of https://example.com/metadata/1.json). Add the following code to your main function:

    // 4. Add items to the Candy Machine
try {
await addConfigLines(umi, {
candyMachine: candyMachine.publicKey,
index: 0,
configLines: [
{ name: '1', uri: '1.json' },
{ name: '2', uri: '2.json' },
{ name: '3', uri: '3.json' },
],
}).sendAndConfirm(umi, options);
console.log(`4. ✅ - Added items to the Candy Machine: ${candyMachine.publicKey.toString()}`)
} catch (error) {
console.log('4. ❌ - Error adding items to the Candy Machine.');
}

The addConfigLines function takes our umi instance and an object with the following properties:

  • candyMachine: The public key of the Candy Machine account
  • index: The index of the first item to add (if you are running this script multiple times, you may need to adjust this index to avoid overwriting existing items)
  • configLines: An array of objects defining each item's name and URI to add to the Candy Machine.

Step 5 - Verify the Candy Machine Configuration

Before we mint our NFTs, let's use our helper function, checkCandyMachine, to verify that the Candy Machine configuration matches our expectations. Add the following code to your main function:

    // 5. Verify the Candy Machine configuration
await checkCandyMachine(umi, candyMachine.publicKey, {
itemsLoaded: 3,
authority: umi.identity.publicKey,
collection: collectionMint.publicKey,
itemsRedeemed: 0,
}, 5);

We expect at this point that our Candy Machine should have three items loaded and none redeemed. If the configuration does not match, the checkCandyMachine function will throw an error.

Step 6 - Mint NFTs

Let's mint all of our NFTs from the Candy Machine. We can write a simple loop that calls the mintV1 function for each item. Add the following code to your main function:

    // 6. Mint NFTs
try {
const numMints = 3;
let minted = 0;
for (let i = 0; i < numMints; i++) {
await transactionBuilder()
.add(setComputeUnitLimit(umi, { units: 800_000 }))
.add(
mintV1(umi, {
candyMachine: candyMachine.publicKey,
asset: generateSigner(umi),
collection: collectionMint.publicKey,
mintArgs: {
solPayment: some({ destination: treasury.publicKey }),
},
})
)
.sendAndConfirm(umi, options);
minted++;
}
console.log(`6. ✅ - Minted ${minted} NFTs.`);
} catch (error) {
console.log('6. ❌ - Error minting NFTs.');
}

The mintV1 function requires that we generate an address for the new NFT (we are using the generateSigner function for this), the collection public key, and the mint arguments. In this example, we only need to pass in the solPayment guard to send 1.5 SOL to the treasury for each minted NFT (as not all guards require arguments). Check the Metaplex Core Candy Machine Documentation for more information on each argument you plan to use.

Step 7 - Verify the Candy Machine Configuration

Now that we have minted our NFTs, we should expect that our Candy Machine configuration has changed. Let's use our checkCandyMachine function to verify that the configuration matches our expectations. Add the following code to your main function:

    // 7. Verify the Candy Machine configuration
await checkCandyMachine(umi, candyMachine.publicKey, {
itemsLoaded: 3,
authority: umi.identity.publicKey,
collection: collectionMint.publicKey,
itemsRedeemed: 3,
}, 7);

We expect our Candy Machine to still have three items loaded, but now all of them should be redeemed. If the configuration does not match, the checkCandyMachine function will throw an error.

Step 8 - Delete the Candy Machine

Finally, since our mint is complete, we will delete the Candy Machine and recover the rent. Add the following code to your main function:

    // 8. Delete the Candy Machine
try {
await deleteCandyMachine(umi, {
candyMachine: candyMachine.publicKey,
}).sendAndConfirm(umi, options);
console.log(`8. ✅ - Deleted the Candy Machine: ${candyMachine.publicKey.toString()}`);
} catch (error) {
console.log('8. ❌ - Error deleting the Candy Machine.');
}

The deleteCandyMachine function takes our umi instance and an object with the public key of the Candy Machine account. This function will delete the Candy Machine and recover the rent that was used to create it. After this step, the Candy Machine account will no longer exist on the chain, and the Candy Machine will be unusable. If we were to run the checkCandyMachine function after deleting the Candy Machine, it would throw an error.

Great work. Your script is now complete! Let's start our local validator and run the script.

Run Your Script

To run your script, start your local validator with the programs we downloaded earlier. Make sure you run this from the same directory that the files are located in (or provide the full path to the files):

solana-test-validator -r \
--bpf-program CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d core.so \
--bpf-program CMACYFENjoBMHzapRXyo1JZkVS6EtaDDzkjMrmQLvr4J candy_machine_core.so \
--bpf-program CMAGAKJ67e9hRZgfC5SFTbZH8MgEmtqazKXjmkaJjWTJ candy_guard.so

Your validator should start with the programs loaded:

Solana Test Validator

You should be able to see the programs on-chain using Solana Explorer's custom cluster view, here.

Then, run your Candy Machine script. In your terminal, run:

ts-node app.ts

Your script should run successfully and output something like this:

Script Output

Great job!

Sweet Success

You have successfully created and minted NFTs using the Metaplex Core Candy Machine. You now have the tools to build and deploy a Candy Machine that takes advantage of the unique flexibility and low cost of the Metaplex Core standard.

If you have a question or idea you want to share, drop us a line on Discord or Twitter!

We ❤️ Feedback!

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

Resources

Share this guide