Create A Community-Made NFT Collection using Signature-Based Minting

Create A Community-Made NFT Collection using Signature-Based Minting

⚠️ Warning: This guide currently uses v4 of the Connect SDK. For v5 (latest) code snippets, please check out our documentation while this guide is being updated. ⚠️

In this guide, we will create an app that takes a coupon code to mint an NFT. Only users that have a coupon code can mint!

Let's say you're launching an NFT collection or you want to reward some of your customers or loyal followers and you want to make sure only they can mint the NFTs.

There are a bunch of ways you can tackle this, but thirdweb has developed the perfect solution: signature-based minting!

You can create your own unique codes and distribute them to whomever you want. With our signature-based minting feature, you will allow only users holding that unique code to mint an NFT.

Check out our example repository with the project built out on Github.

Flow

The user goes to the app and picks an image to mint an NFT or you can pre-set an image.

In this guide, we'll let the user pick their own image.

The user then needs to enter a valid coupon code. Once the code is entered, an internal API call will be made to our backend, where the code will be validated.

If the code is valid, a signed payload is sent to the front-end, allowing the user to mint an NFT.

Setup

Make sure you have an NFT Collection contract, before you start building this app.

If you don't have one, go ahead to the dashboard and deploy one.

To build this app, we need to walk through the following steps:

  1. Spin up a starter template from the thirdweb example repo.
  2. Build our UI
  3. Build an internal API request
  4. Build a back-end to validate the code and create
  5. Build our minting function

Never use your Private Keys on the front end and never expose your Private Keys to anyone.

Learn more about securely storing your private keys

Let's go!

Building the App

Please note we won't cover every line of code. We'll take you through the logic and important parts. If you want to recreate the style of this app or certain details, please check out the repo!

Building the UI

In this guide, we're using this starter template. What we want to create is:

npx thirdweb create --next --ts

This template comes with thirdweb and wallet connection pre-configured!

To simplify things, we'll use the unique code as the name of our NFT.

So the input field will be used to both validate the unique code and name the NFT.

const [nftName, setNftName] = useState<string>("");
<input
  type="text"
  placeholder="Name of your NFT"
  className={styles.textInput}
  maxLength={26}
  onChange={(e) => setNftName(e.target.value)}
/>

To upload the image we want a couple of things.

This app allows the user to drag and drop an image and upload one by clicking in the area.

Next to that, we want to show the image, just to make sure the user has the right image.

const [file, setFile] = useState();
// We need the useRef to create the drag and drop feature
const fileInputRef = useRef(null);
// Function to store file in state when user uploads it
const uploadFile = () => {
  if (fileInputRef?.current) {
    fileInputRef.current.click();
    fileInputRef.current.onchange = () => {
      if (fileInputRef?.current?.files?.length) {
        const file = fileInputRef.current.files[0];
        setFile(file);
      }
    };
  }
};
<div className={styles.collectionContainer}>
  {file ? (
    <img
      src={URL.createObjectURL(file)}
      style={{ cursor: "pointer", maxHeight: 250, borderRadius: 8 }}
      onClick={() => setFile(undefined)}
    />
  ) : (
    <div
      className={styles.imageInput}
      onClick={uploadFile}
      onDragOver={(e) => e.preventDefault()}
      onDrop={(e) => {
        e.preventDefault();
        setFile(e.dataTransfer.files[0]);
      }}
    >
      Drag and drop an image here to upload it!
    </div>
  )}
</div>

<input
  type="file"
  accept="image/png, image/gif, image/jpeg"
  id="profile-picture-input"
  ref={fileInputRef}
  style={{ display: "none" }}
/>

Finall, we'll create a button to mint an NFT. We'll write the mintWithSignature function below!

<a className="{styles.mainButton}" onClick={mintWithSignature}>
  Mint NFT
</a>

Build an internal API request

Now we will take the info from our input fields and pass them to our backend, so our backend can do its magic.

To do that, we'll create an internal API call to our backend.

The address is grabbed from the connected wallet using the useAddress hook.

Make a request to the API route:

const signedPayloadReq = await fetch(`/api/server`, {
  method: "POST",
  body: JSON.stringify({
    authorAddress: address, // Address of the current user
    nftName: nftName,
    imagePath: url,
  }),
});

The backend

The backend will handle the internal API Request and send back a signed payload if the unique code is valid.

Instantiate the SDK, connect your smart contract and then build the following function.

If you don't know how to instantiate our SDK, check this guide.

If you want to see the full backend code, check out the server.ts file in the example repo here.

The backend validates the code, by checking if a pre-compiled list contains the code the user has entered.

An example of this list can be found in the repo here. In this case the unique codes are animal names.

if (!animalNames.includes(nftName?.toLowerCase())) {
  res.status(400).json({ error: "That's not one of the animals we know!" });
  return;
}

Once the unique code is validated, we take the info passed from the front end and generate a unique signature.

Then send that signature to the front-end, which will be used to mint the NFT. Without this unique signature, the minting functionality does not work!

// Generate the signature for the page NFT
const signedPayload = await nftCollection.signature.generate({
  to: authorAddress,
  metadata: {
    name: nftName,
    image: imagePath,
    description: "An awesome animal NFT",
    properties: {
      // Add any properties you want to store on the NFT
    },
  },
});

// Return back the signedPayload to the client.
res.status(200).json({
  signedPayload: JSON.parse(JSON.stringify(signedPayload)),
});

Build the minting function

Now we will build the minting function and bind it to our button.

const signedPayload = json.signedPayload;
const nft = await nftCollection.signature.mint(signedPayload);

That's it!

That's how you use unique codes to mint NFTs.

This guide is just an example of how to do it. You can decide what settings you want to apply in your application.