Features

Candy Guards

Now that we know how Candy Machines work and how to load them, it’s time we talk about the last piece of the puzzle: Guards.

What is a guard?

A guard is a modular piece of code that can restrict access to the mint of a Candy Machine and even add new features to it!

There is a large set of guards to choose from and each of them can be activated and configured at will.

We’ll touch on all available guards later in this documentation but let’s go through a few examples here to illustrate that.

  • When the Start Date guard is enabled, minting will be forbidden before the preconfigured date. There is also an End Date guard to forbid minting after a given date.
  • When the Sol Payment guard is enabled, the minting wallet will have to pay a configured amount to a configured destination wallet. Similar guards exist for paying with tokens or NFTs of a specific collection.
  • The Token Gate and NFT Gate guards restrict minting to certain token holders and NFT holders respectively.
  • The Allow List guard only allows minting if the wallet is part of a predefined list of wallets. Kind of like a guest list for minting.

As you can see, each guard takes care of one responsibility and one responsibility only which makes them composable. In other words, you can pick and choose the guards your need to create your perfect Candy Machine.

The Candy Guard account

If you remember the content of our Candy Machine account, you’ll see no signs of guards in there. This is because guards live in another account called the Candy Guard account which is created by the Candy Guard program.

Each Candy Machine account should typically be associated with its own Candy Guard account which will add a layer of protection to it.

This works by creating a Candy Guard account and making it the Mint Authority of the Candy Machine account. By doing so, it is no longer possible to mint directly from the main Candy Machine program — known as the Candy Machine Core program. Instead, we must mint via the Candy Guard program which, if all guards are resolved successfully, will defer to the Candy Machine Core program to finish the minting process.

Note that, since Candy Machine and Candy Guard accounts work hand and hand together, our SDKs treat them as one entity. When you create a Candy Machine with our SDKs, an associated Candy Guard account will also be created by default. The same goes when updating Candy Machines as they allow you to update guards at the same time. We will see some concrete examples on this page.

Why another program?

The reason guards don’t live in the main Candy Machine program is to separate the access control logic from the main Candy Machine responsibility which is to mint an NFT.

This enables guards to not only be modular but extendable. Anyone can create and deploy their own Candy Guard program to create custom guards whilst relying on the Candy Machine Core program for all the rest.

Note that our SDKs also offer ways to register your own Candy Guard programs and their custom guards so you can leverage their friendly API and easily share your guards with others.

All available guards

Alright, now that we understand what guards are, let’s see what default guards are available to us.

In the following list, we’ll provide a short description of each guard with a link pointing to their dedicated page for more advanced reading.

  • 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.
  • Freeze Sol Payment: Set the price of the mint in SOL with a freeze period.
  • Freeze Token Payment: Set the price of the mint in token amount with a freeze period.
  • 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.

Creating a Candy Machine with guards

So far, the Candy Machine we created did not have any guards enabled. Now that we know all the guards available to us, let’s see how we can set up new Candy Machines with some guards enabled.

The concrete implementation will depend on which SDK you are using (see below) but the main idea is that you enable guards by providing their required settings. Any guard that has not been set up will be disabled.

Create a Candy Machine with guards

To enable guards using the Umi library, simply provides the guards attribute to the create function and pass in the settings of every guard you want to enable. Any guard set to none() or not provided will be disabled.

import { some, sol, dateTime } from '@metaplex-foundation/umi'

await create(umi, {
  // ...
  guards: {
    botTax: some({ lamports: sol(0.01), lastInstruction: true }),
    solPayment: some({ lamports: sol(1.5), destination: treasury }),
    startDate: some({ date: dateTime('2023-04-04T16:00:00Z') }),
    // All other guards are disabled...
  },
}).sendAndConfirm(umi)

API References: create, DefaultGuardSetArgs

Updating guards

Did you set something wrong in your guards? Did you change your mind about the mint price? Do you need to delay the start of the mint of a little? No worries, guards can easily be updated following the same settings used when creating them.

You can enable new guards by providing their settings or disable current ones by giving them empty settings.

Update guards

You may update the guards of a Candy Machine the same way you created them. That is, by providing their settings inside the guards object of the updateCandyGuard function. Any guard set to none() or not provided will be disabled.

Note that the entire guards object will be updated meaning it will override all existing guards!

Therefore, make sure to provide the settings for all guards you want to enable, even if their settings are not changing. You may want to fetch the candy guard account first to fallback to its current guards.

import { some, none, sol } from '@metaplex-foundation/umi'

const candyGuard = fetchCandyGuard(umi, candyMachine.mintAuthority)
await updateCandyGuard(umi, {
  candyGuard: candyGuard.publicKey,
  guards: {
    ...candyGuard.guards,
    botTax: none(),
    solPayment: some({ lamports: sol(3), destination: treasury }),
  },
})

API References: updateCandyGuard, CandyGuard, DefaultGuardSetArgs

Viewing the guards of a Candy Machine

Once you have set up your guards on a Candy Machine, all the provided settings can be retrieved and viewed by anyone on the Candy Guard account.

Fetch guards

You may access the candy guard associated with a candy machine by using the fetchCandyGuard function on the mintAuthority attribute of the candy machine account.

import {
  fetchCandyMachine,
  fetchCandyGuard,
} from '@metaplex-foundation/mpl-candy-machine'

const candyMachine = await fetchCandyMachine(umi, candyMachineAddress)
const candyGuard = await fetchCandyGuard(umi, candyMachine.mintAuthority)

candyGuard.guards // All guard settings.
candyGuard.guards.botTax // Bot Tax settings.
candyGuard.guards.solPayment // Sol Payment settings.
// ...

Note that, when using the create function, an associated candy guard account is automatically created for each candy machine such that their address is deterministic. Therefore, in this case, we could fetch both accounts using only one RPC call like so.

import { assertAccountExists } from '@metaplex-foundation/umi'
import {
  findCandyGuardPda,
  deserializeCandyMachine,
  deserializeCandyGuard,
} from '@metaplex-foundation/mpl-candy-machine'

const candyGuardAddress = findCandyGuardPda(umi, { base: candyMachineAddress })
const [rawCandyMachine, rawCandyGuard] = await umi.rpc.getAccounts([
  candyMachineAddress,
  candyGuardAddress,
])
assertAccountExists(rawCandyMachine)
assertAccountExists(rawCandyGuard)

const candyMachine = deserializeCandyMachine(umi, rawCandyMachine)
const candyGuard = deserializeCandyGuard(umi, rawCandyGuard)

API References: fetchCandyGuard, findCandyGuardPda, CandyGuard, DefaultGuardSetArgs

Wrapping and unwrapping Candy Guard accounts manually

So far we’ve managed both Candy Machine and Candy Guard accounts together because that makes the most sense for most projects.

However, it is important to note that Candy Machines and Candy Guards can be created and associated in different steps, even using our SDKs.

You will first need to create the two accounts separately and associate/dissociate them manually.

Associate and dissociate guards from a Candy Machine

The create function of the Umi library already takes care of creating and associating a brand new Candy Guard account for every Candy Machine account created.

However, if you wanted to create them separately and manually associate/dissociate them, this is how you’d do it.

import { some, percentAmount, sol, dateTime } from '@metaplex-foundation/umi'

// Create a Candy Machine without a Candy Guard.
const candyMachine = generateSigner(umi)
await createCandyMachineV2({
  candyMachine,
  tokenStandard: TokenStandard.NonFungible,
  collectionMint: collectionMint.publicKey,
  collectionUpdateAuthority: umi.identity,
  itemsAvailable: 100,
  sellerFeeBasisPoints: percentAmount(1.23),
  creators: [
    { address: umi.identity.publicKey, verified: false, percentageShare: 100 },
  ],
  configLineSettings: some({
    prefixName: 'My NFT #',
    nameLength: 3,
    prefixUri: 'https://example.com/',
    uriLength: 20,
    isSequential: false,
  }),
}).sendAndConfirm(umi)

// Create a Candy Guard.
const base = generateSigner(umi)
const candyGuard = findCandyGuardPda(umi, { base: base.publicKey })
await createCandyGuard({
  base,
  guards: {
    botTax: { lamports: sol(0.01), lastInstruction: false },
    solPayment: { lamports: sol(1.5), destination: treasury },
    startDate: { date: dateTime('2022-10-17T16:00:00Z') },
  },
}).sendAndConfirm(umi)

// Associate the Candy Guard with the Candy Machine.
await wrap({
  candyMachine: candyMachine.publicKey,
  candyGuard,
}).sendAndConfirm(umi)

// Dissociate them.
await unwrap({
  candyMachine: candyMachine.publicKey,
  candyGuard,
}).sendAndConfirm(umi)

API References: createCandyMachineV2, createCandyGuard, wrap, unwrap

Conclusion

Guards are important components of Candy Machines. They make it easy to configure the minting process whilst allowing anyone to create their own guards for application-specific needs. On the next page, we’ll see how we can create even more minting scenarios by using guard groups!

Previous
Inserting Items