General
Create a Website for minting Assets from your Core Candy Machine
If you are looking to launch a Core NFT Collection on Solana, you would typically use a Candy Machine where your users can come and buy your Assets. To provide a user-friendly experience, it is recommended to have a website for it. This guide will focus on how to build your own mint function. It will also show you how to fetch data from the Candy Machine to, for example, display the remaining amount that can be minted.
This guide focuses on the core Candy Machine functionality and interactions, rather than providing a complete website implementation. It will not cover aspects like adding buttons to a website or integrating with a wallet adapter. Instead, it provides essential information on working with the Core Candy Machine.
For a full website implementation, including UI elements and wallet integration, you may want to start with a template like the metaplex-nextjs-tailwind-template
. This template includes many setup steps for components like the Wallet Adapter.
If you're looking for guidance on general web development practices or how to use specific frameworks, tools like Visual Studio Code offer extensive documentation and community resources.
Prerequisites
- An already created Candy Machine. Find more info on how to create one here.
- Basic familiarity with web development and your chosen framework. We recommend Next JS for easiest compatibility to umi.
Required Packages
Regardless of your chosen template or implementation, you'll need to install the following packages for interacting with the Core Candy Machine:
npm i @metaplex-foundation/umi @metaplex-foundation/umi-bundle-defaults @metaplex-foundation/mpl-core-candy-machine
Fetch On-chain Data
After setting up your environment, we can start focusing on the Candy Machine. Mint UIs often want to show data such as:
- Number of already minted Assets
- Number of Assets in the Candy Machine
- Time until the mint starts
- Price of Assets
- etc.
It can also make sense to fetch additional data that is not shown to the user but used in background calculations. For example, when using the Redeemed Amount Guard, you would want to fetch the already redeemed amount to see if the user is allowed to mint more.
Fetch Candy Machine Data
In the Candy Machine Account, data such as the number of Available and Redeemed assets is stored. It also stores the mintAuthority
, which is usually the address of your Candy Guard.
To fetch the Candy Machine, the fetchCandyMachine
function can be used as shown below:
import {
mplCandyMachine,
fetchCandyMachine,
} from "@metaplex-foundation/mpl-core-candy-machine";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
// The next two lines are only required if you did not set up umi before
const umi = createUmi("https://api.devnet.solana.com")
.use(mplCandyMachine());
const candyMachineId = "Ct5CWicvmjETYXarcUVJenfz3CCh2hcrCM3CMiB8x3k9";
const candyMachine = await fetchCandyMachine(umi, publicKey(candyMachineId));
console.log(candyMachine)
This would return the Candy Machine Data like this:
JSON Result
From a UI perspective the most important field in here are itemsRedeemed
, itemsAvailable
and mintAuthority
. In some cases it might also be interesting to show some of the items
on your website as teaser pictures.
Show remaining Asset Amount
To display a section like 13 / 16 Assets minted
one would use something like:
const mintedString = `${candyMachine.itemsRedeemed} / ${candyMachine.itemsAvailable} Assets minted`
If you want to get the remaining mintable Assets like 3 available
you would instead run a calculation like:
const availableString = `${candyMachine.itemsAvailable - candyMachine.itemsRedeemed} available`;
Fetch Candy Guard Data
The Candy Guard contains the conditions that have to be met to allow minting. This can for example be a Sol or Token Payment happening, limiting the amount of Assets one Wallet is allowed to mint and way more. You can find more information about Candy Guards on the Candy Guard Page.
Similar to the Candy Machine Data it is not a necessity to fetch the guard account. Doing so can allow more flexibility like just updating the SOL price in the Candy Guard and automatically updating the numbers on the website, too.
If you want to build a more flexible UI that can be used for multiple Candy Machines fetching the Candy Guard then allows you to both build your mint function and check eligibility dynamically.
The following snippet assumes that the candyMachine
account was fetched before. Alternatively to candyMachine.mintAuthority
the publicKey of the Candy Guard could be hardcoded.
import { safeFetchCandyGuard } from "@metaplex-foundation/mpl-core-candy-machine";
const candyGuard = await safeFetchCandyGuard(umi, candyMachine.mintAuthority);
JSON Result
Fetch additional Candy Machine related Accounts
The choice of Guards you implement may necessitate fetching additional accounts. For instance, if you plan to verify a wallet's minting eligibility and are utilizing the mintLimit
Guard, you would need to retrieve the mintCounter
account. This account maintains a record of how many NFTs a particular wallet has already minted under that specific guard.
MintLimit
Accounts
When the MintLimit
guard is active, it's advisable to retrieve the MintCounter
account for the user's wallet. This allows you to check whether the user has reached their minting limit or if they're still eligible to mint additional items.
The following code snippet demonstrates how to fetch the MintCounter
. Note that this example assumes you've already obtained the Candy Machine and Candy Guard data:
import { safeFetchMintCounterFromSeeds } from "@metaplex-foundation/mpl-core-candy-machine";
const mintCounter = await safeFetchMintCounterFromSeeds(umi, {
id: 1, // The mintLimit id you set in your guard config
user: umi.identity.publicKey,
candyMachine: candyMachine.publicKey,
candyGuard: candyMachine.mintAuthority,
});
NftMintLimit
Accounts
Similar to the MintLimit
guard it can make sense to fetch the NftMintCounter
account of the NftMintLimit
guard to verify eligibility.
The following code snippet demonstrates how to fetch the NftMintCounter
account. Note that this example assumes you've already obtained the Candy Machine and Candy Guard data:
import {
findNftMintCounterPda,
fetchNftMintCounter
} from "@metaplex-foundation/mpl-core-candy-machine";
const pda = findNftMintCounterPda(umi, {
id: 1, // The nftMintLimit id you set in your guard config
mint: asset.publicKey, // The address of the nft your user owns
candyGuard: candyMachine.mintAuthority,
candyMachine: candyMachine.publicKey,
});
const nftMintCounter = fetchNftMintCounter(umi, pda)
AssetMintLimit
Accounts
Similar to the NftMintCounter
guard it can make sense to fetch the AssetMintCounter
account of the AssetMintLimit
guard to verify eligibility.
The following code snippet demonstrates how to fetch the AssetMintCounter
account. Note that this example assumes you've already obtained the Candy Machine data:
import {
findAssetMintCounterPda,
fetchAssetMintCounter
} from "@metaplex-foundation/mpl-core-candy-machine";
const pda = findAssetMintCounterPda(umi, {
id: 1, // The assetMintLimit id you set in your guard config
asset: asset.publicKey, // The address of the core nft your user owns
candyGuard: candyMachine.mintAuthority,
candyMachine: candyMachine.publicKey,
});
const assetMintCounter = fetchAssetMintCounter(umi, pda);
Allocation
Accounts
For the Allocation
guard it can make sense to fetch the AllocationTracker
account to verify that additional NFTs can be minted from a given group.
The following code snippet demonstrates how to fetch the AllocationTracker
account. Note that this example assumes you've already obtained the Candy Machine data:
import {
safeFetchAllocationTrackerFromSeeds,
} from "@metaplex-foundation/mpl-core-candy-machine";
const allocationTracker = await safeFetchAllocationTrackerFromSeeds(umi, {
id: 1, // The allocation id you set in your guard config
candyMachine: candyMachine.publicKey,
candyGuard: candyMachine.mintAuthority,
});
Allowlist
Accounts
When implementing the Allowlist guard, it's crucial to execute a route
instruction beforehand. This instruction generates a unique account for each wallet and Candy Machine combination, effectively marking the wallet as authorized to mint.
From a UI perspective, it's beneficial to query this account. This allows you to determine whether the route
instruction needs to be executed or if the user can proceed directly to the mint instruction.
The following code snippet demonstrates how to fetch this account. It assumes that you've already retrieved the Candy Machine data. However, if you prefer, you can hardcode the candyGuard
and candyMachine
public keys instead.
import {
safeFetchAllowListProofFromSeeds,
getMerkleRoot,
} from "@metaplex-foundation/mpl-core-candy-machine";
const allowlist = [
"Tes1zkZkXhgTaMFqVgbgvMsVkRJpq4Y6g54SbDBeKVV",
"GjwcWFQYzemBtpUoN5fMAP2FZviTtMRWCmrppGuTthJS",
"AT8nPwujHAD14cLojTcB1qdBzA1VXnT6LVGuUd6Y73Cy"
];
const allowListProof = await safeFetchAllowListProofFromSeeds(umi, {
candyGuard: candyMachine.mintAuthority,
candyMachine: candyMachine.publicKey,
merkleRoot: getMerkleRoot(allowlist),
user: umi.identity.publicKey,
});
Fetch Wallet Data
To validate the legibility you may also want to fetch information about the connected wallet. Depending on the Guards you are using you may want to know how much SOL is in the wallet and which Tokens and NFTs the wallet owns.
To fetch the SOL balance the built in getAccount
umi function can be used to fetch the wallet account:
const account = await umi.rpc.getAccount(umi.identity.publicKey);
const solBalance = account.lamports;
If you are using one of the guards that require tokens or NFTs you may want to fetch those, too. We recommend to use DAS API for this. DAS is an index of Tokens maintained by your RPC Provider. Using this allows to fetch all the required information with one call. In the UI you can then use the returned object to verify if the connected wallet owns the required tokens or NFTs.
import { publicKey } from '@metaplex-foundation/umi';
import { dasApi } from '@metaplex-foundation/digital-asset-standard-api';
// When defining the umi instance somewhere before you can already
// add `.use(dasApi());` so there is no need to define umi again.
const umi = createUmi('<ENDPOINT>').use(dasApi());
const assets = await umi.rpc.getAssetsByOwner({
umi.identity.publicKey
});
Verify legibility
After fetching all the required data you can then verify if the connected wallet is allowed to mint or not.
It's important to note that when groups are attached to a Candy Machine, the default
guards apply universally across all created groups. Also, when groups are enabled the ability to mint from the default
group becomes disabled and you must use the created groups for minting.
Therefore, if there are no groups defined you need to check if all the mint conditions of the default
group are met. If there are groups defined the combination of both the default
guards and the current minting group guards both need to be validated.
Given a Candy Machine with the startDate
, SolPayment
and mintLimit
guards attached that is not leveraging groups the following validations should be done before allowing the user to call the mint function. It is assumed that the candyGuard
was fetched before and one Core NFT Asset should be minted.
- Validate the
startDate
is in the past. Note that we are not using the users device time here but instead fetching the current internal Solana Blocktime since this is the time the Candy Machine will use for the validation on mint:
import { unwrapOption } from '@metaplex-foundation/umi';
let allowed = true;
// fetch the current slot and read the blocktime
const slot = await umi.rpc.getSlot();
let solanaTime = await umi.rpc.getBlockTime(slot);
// Check if a `default` startDate guard is attached
const startDate = unwrapOption(candyGuard.guards.startDate);
if (startDate) {
// validate the startTime is in the future
if (solanaTime < startDate) {
console.info(`StartDate not reached!`);
allowed = false;
}
}
- Check if the Wallet has enough SOL to pay for the mint. Note that we are not including transaction fees here and assume that the
SolBalance
was fetched as described above.
import { unwrapOption } from '@metaplex-foundation/umi';
const solPayment = unwrapOption(candyGuard.guards.solPayment);
if (solPayment){
if (solPayment.lamports.basisPoints > solBalance){
console.info(`Not enough SOL!`);
allowed = false;
}
}
- Make sure the
mintLimit
was not reached yet:
import { unwrapOption } from '@metaplex-foundation/umi';
import {
safeFetchMintCounterFromSeeds,
} from "@metaplex-foundation/mpl-core-candy-machine";
const mintLimit = unwrapOption(candyGuard.guards.mintLimit);
if (mintLimit){
const mintCounter = await safeFetchMintCounterFromSeeds(umi, {
id: mintLimit.id,
user: umi.identity.publicKey,
candyMachine: candyMachine.publicKey,
candyGuard: candyMachine.mintAuthority,
});
// mintCounter PDA exists (not the first mint)
if (mintCounter && mintLimit.limit >= mintCounter.count
) {
allowed = false;
}
}
When a wallet is not eligible to mint it is helpful to disable the mint button and show the user the reason for not being eligible to mint. E.g. a Not enough SOL!
message.
Guard Routes
Certain Guards require specific instructions to be executed before minting can occur. These instructions create accounts that store data or provide proof of a wallet's eligibility to mint. The execution frequency of these instructions varies depending on the Guard type.
Target Audience of this section
In case you are not using the Allocation
, FreezeSolPayment
, FreezeTokenPayment
or Allowlist
guard it is safe to skip this section.
Configuration
Some Guards need their routes executed only once for the entire Candy Machine. For these, it's not necessary to include a function in the UI but can be run upfront once through a script:
Other Guards require their routes to be executed for each individual wallet. In these cases, the route instruction should be run prior to the mint transaction:
For an example of how to implement Guard routes, consider the case of the Allowlist guard. This assumes that the allowListProof
has been fetched as described earlier, and that allowlist
represents an array of eligible wallet addresses. The following code demonstrates how to handle this scenario in your implementation.
import {
getMerkleRoot,
getMerkleProof,
route
} from "@metaplex-foundation/mpl-core-candy-machine";
import {
publicKey,
} from "@metaplex-foundation/umi";
// assuming you fetched the AllowListProof as described above
if (allowListProof === null) {
route(umi, {
guard: "allowList",
candyMachine: candyMachine.publicKey,
candyGuard: candyMachine.mintAuthority,
group: "default", // Add your guard label here
routeArgs: {
path: "proof",
merkleRoot: getMerkleRoot(allowlist),
merkleProof: getMerkleProof(allowlist, publicKey(umi.identity)),
},
})
}
Create a Mint Function
It is recommended to implement legibility checks for all the guards that are attached. Keep in mind that if there are any groups attached the default
guards will apply to all additional groups, while simultaneously disabling the default
group.
After those checks are done and, if required, the route instructions were run the mint transaction can be built. Depending on the guards, mintArgs
may have to be passed in. These are arguments that help build the mint transaction by passing in the correct accounts and data. For example the mintLimit
guard requires the mintCounter
account. The Umi SDK abstracts these details away but still requires some information to build the transaction correctly.
Assuming again a Candy Machine with startDate
, SolPayment
and mintLimit
Guards attached let's see how to build the mintArgs
.
import { some, unwrapOption } from '@metaplex-foundation/umi';
import {
DefaultGuardSetMintArgs
} from "@metaplex-foundation/mpl-core-candy-machine";
let mintArgs: Partial<DefaultGuardSetMintArgs> = {};
// add solPayment mintArgs
const solPayment = unwrapOption(candyGuard.guards.solPayment)
if (solPayment) {
mintArgs.solPayment = some({
destination: solPayment.destination,
});
}
// add mintLimit mintArgs
const mintLimit = unwrapOption(candyGuard.guards.mintLimit)
if (mintLimit) {
mintArgs.mintLimit = some({ id: mintLimit.id });
}
Not all Guards require additional mintArgs
to be passed in. This is the reason startDate
is not in the above code snippet. To understand if the guards you are using require mintArgs
to be passed in it is recommended to check the Developer Hub Guard pages. If there are "Mint Settings" described you need to pass in mintArgs
for this guard.
Now that the mintArgs
are built let's see how to call the mint function itself. The following snippet assumes that the candyMachine
and candyGuard
were fetched as described above. Technically the publicKeys of candyMachine
, collection
, candyGuard
and all the mintArgs
can also be passed in manually in case you do not want to fetch them.
// Generate the NFT address
const nftMint = generateSigner(umi);
await mintV1(umi, {
candyMachine: candyMachine.publicKey,
collection: candyMachine.collectionMint,
asset: nftMint,
candyGuard: candyGuard.publicKey,
mintArgs,
}).sendAndConfirm(umi)
console.log(`NFT ${nftMint.publicKey} minted!`)
Advanced Minting Techniques
While the basic minting function we've discussed works well for most cases, there are some advanced techniques you can use to enhance your minting process. Let's explore a couple of these:
Minting Multiple NFTs in One Transaction
For efficiency, you might want to allow users to mint multiple NFTs in a single transaction. Here's how you can achieve this:
Depending on the specific setup it can be helpful to allow minting multiple NFTs in one Transaction by combining the Transaction Builders.
let builder = transactionBuilder()
.add(mintV1(...))
.add(mintV1(...))
If you add to many mintV1
instructions into a transaction you will receive a Transaction too large
error. The function builder.fitsInOneTransaction(umi)
allows to check for this before sending the transaction so that the transaction can be split before being sent. In case splitting is needed using signAllTransactions
is recommended so that only one popup has to be approved in the Wallet Adapter.
Guard Groups
Guard groups are a powerful feature of Core Candy Machine that allow you to define multiple sets of guards with different configurations. They can be particularly useful in scenarios such as:
- Tiered minting: Different groups for VIP, early access, and public sale.
- Multiple payment options: Groups for SOL payment, SPL token payment, etc.
- Time-based minting: Groups with different start and end dates.
- Allowlist-based minting: Groups for allowlisted users and public sale.
To implement guard groups in your UI, you have two main approaches:
Multiple buttons approach: Create a separate button for each group, allowing users to choose their preferred minting option.
Automatic group selection: Implement a function that determines the best group for a user based on their eligibility and current conditions.
Regardless of which scenario or approach you choose, here's how to adjust the mintV1
instruction to work with your specific group. The key modification is to include a group
parameter that specifies the desired label.
// Generate the NFT address
const nftMint = generateSigner(umi);
await mintV1(umi, {
candyMachine: candyMachine.publicKey,
collection: candyMachine.collectionMint,
asset: nftMint,
candyGuard: candyGuard.publicKey,
mintArgs,
group: "group1",
}).sendAndConfirm(umi)
console.log(`NFT ${nftMint.publicKey} minted!`)
Next Steps
Now that you've mastered the essentials of interacting with the Candy Machine in your frontend, you might want to consider the following steps to further enhance and distribute your project:
Hosting: Make your frontend accessible to users by deploying it to a hosting platform. Popular options among developers include:
- Vercel
- Cloudflare Pages
- Netlify
- GitHub Pages
Testing: Thoroughly test your UI on various devices and browsers to ensure a smooth user experience.
Optimization: Fine-tune your frontend for performance, especially if you're expecting high traffic during minting events.
Monitoring: Set up monitoring tools to keep track of your Candy Machine UIs status and quickly address any issues that may arise.
By focusing on these areas, you'll be well-prepared to launch and maintain a successful NFT minting project using Core Candy Machine.