🎉 ⏱ Ready to build? Get early access now!⏱ 🎉
Using signature based minting
This guide shows how to create a signature and allow anyone holding that signature to mint an NFT!
Intro
Our latest feature allows you to create unique ID's to hand out to anyone you want. Only users with that code will be able to mint or claim an unique NFT! thirdweb has created an easy component for you to integrate in your app! Let's take a look. Also you can find a GitHub repo here, where we show how to use this code.
Sidenote: in this tutorial we will allow an user, who has a unique signature, to mint an NFT. So the actual minting happens once the user uses their unique signature.
Requirements
Before we start make sure you have the dependencies installed and understand how we instantiate the sdk. If you want to understand how to do that, click here! Also we're going to show you how to set this up in a basic next app. Click here if you want to know how to set that up.
What we're going to do
After instantiating the sdk
, we're going to:
- Build a component for the
connect wallet
button to... connect our wallet and use it to create a signature - Create a unique signature that the owner of the NFT can sell or gift to someone
- Allow anyone, holding that unique signature, to claim a NFT
1. Setup the SDK & Navigation
In this example project, we're going to create a hook
for our sdk. Create a folder in your project directory called hook
and create a file in it called useSdk.ts
and use the following code. The provider
contains the necessary data from our wallet to authorise transactions from our app. If you want to know more about how the sdk and provider
works, click here.
import { useWeb3 } from "@3rdweb/hooks"; import { ThirdwebSDK } from "@3rdweb/sdk"; import { Signer } from "ethers"; export default function useSdk() { const { provider } = useWeb3(); const sdk = new ThirdwebSDK(provider?.getSigner() as Signer); return sdk; }
The second file in this folder is the useProtectedPage.ts
to navigate the request to the correct page.
import { useWeb3 } from "@3rdweb/hooks"; import { useRouter } from "next/router"; import { useEffect } from "react"; export default function useProtectedPage() { const { address } = useWeb3(); const router = useRouter(); useEffect(() => { if (!address) { router.replace("/"); } }, [address]); }
2. Create the layout for our page
Next up we create a component folder and inside it create a Layout.ts
file for our layout.
The Flex
library is used to style our component and we're using the connect wallet
component here to get our connect wallet button. If you want to know more about the connect wallet button component, click here.
import { ConnectWallet } from "@3rdweb/react"; import { Flex } from "@chakra-ui/react"; const Layout: React.FC = ({ children }) => { return ( <Flex flexDir="column" padding={6}> <ConnectWallet /> {children} </Flex> ); }; export default Layout;
3. Build our homepage
We need two buttons. one to create a signature and one to use a signature 👇
Each button will lead the user to a page to create the signature and the other to use the signature.
import { Button, Flex, Heading, Text } from "@chakra-ui/react"; import { useRouter } from "next/router"; import useProtectedPage from "../hooks/useProtectedPage"; export default function Home() { // using our hook to navigate the request of the user // when we click our buttons in the page we can lead the user to the right page useProtectedPage(); const router = useRouter(); return ( <Flex flexDir="column" alignContent="center" textAlign="center"> <Heading size="lg">Home</Heading> <Text> If you're a creator, click the creator button. If you're a claimer, click the claimer button. </Text> <Flex flexDir="row" sx={{ button: { margin: "1rem", }, }} justifyContent="center" > <Button colorScheme="blue" onClick={() => router.push("creator")}> Creator </Button> <Button colorScheme="green" onClick={() => router.push("claimer")}> Claimer </Button> </Flex> </Flex> ); }
Don't forget to connect the homepage to the index.ts
import { useWeb3 } from "@3rdweb/hooks"; import { Heading } from "@chakra-ui/react"; import { useRouter } from "next/router"; import { useEffect } from "react"; export default function LandingPage() { const { address } = useWeb3(); const router = useRouter(); useEffect(() => { if (address) { router.replace("/home"); } }, [address]); return ( <div> <Heading size="lg">Connect your wallet to get started!</Heading> </div> ); }
5. The ‘Creator' page
On this page we create the signature and the payload. We need both the signature and payload to mint an exclusive NFT.
This page sets the metadata for the nft and creates a payload and signature.
The code below also contains comments explaining the code.
// we're importing 'NATIVE_TOKEN_ADDRESS' to make use of the native currency import { NATIVE_TOKEN_ADDRESS } from "@3rdweb/sdk"; // importing features for alerts and styling import { Button, Flex, Heading, Text, Textarea, useToast, } from "@chakra-ui/react"; import { useCallback, useMemo, useState } from "react"; // here we import the sdk we instantiated import useSdk from "../hooks/useSdk"; export default function Creator() { const [loading, setLoading] = useState(false); const sdk = useSdk(); const toast = useToast(); // remember in this example we're creating a signature for the user to mint a NFT const module = useMemo( () => sdk.getNFTModule("0xc335111d58913C6A382F40c8B020b4ff1ee13Ba1"), [sdk], ); // define all the variables // we're using 'useState' to update the variable dynamically // payload is used to contain both the signature and the NFT the signature is meant for const [payload, setPayload] = useState<any>(); // signature is to contain the unique string const [signature, setSignature] = useState<string>(); // metadata is used to pass the description, name and image to mint the nft const [metadata, setMetadata] = useState(` { "name": "Some Awesome NFT", "description": "This is a description of the NFT", "image": "ipfs://bafkreiemrdnm26x3mpzjkhpewirwrzubjvuje2rbj2lgqexesbqq72utey" } `); // this function checks if the metadata has any syntax error const generateSignature = useCallback(async () => { let metadataJson: any; try { metadataJson = JSON.parse(metadata.trim()); } catch (err) { toast({ status: "error", title: "There's a syntax error in your metadata JSON", }); return; } console.log("Generating sig with metadata:", metadataJson); // here we will create the unique string to create a signature // the payload contains both the metadata and the signature to validate the signature for an nft const { payload, signature } = await module.generateSignature({ metadata: metadataJson, price: 0, currencyAddress: NATIVE_TOKEN_ADDRESS, mintStartTimeEpochSeconds: Math.floor(Date.now() / 1000), mintEndTimeEpochSeconds: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 365, to: "0x0000000000000000000000000000000000000000", }); setPayload(payload); setSignature(signature); }, [module, metadata]); // here we render our page // once our signature is created, this page returns the payload and the sig return ( <Flex flexDirection={"column"}> <Heading textAlign={"center"}>Creator</Heading> <Heading size="sm">Token Metadata</Heading> <Textarea onChange={(ev) => setMetadata(ev.target.value)} value={metadata} minHeight="300px" /> <Button colorScheme="green" onClick={async () => { setLoading(true); try { await generateSignature(); } catch (err) { console.error("Failed to generate signature", err); } setLoading(false); }} isLoading={loading} > Create Signature! </Button> {payload && ( <Flex flexDirection="column" textAlign="center"> <Heading m={2} size="md"> Your new signature is ready 🎉 </Heading> <Heading size="sm">Payload:</Heading> <Text>{JSON.stringify(payload)}</Text> <Heading size="sm">Signature:</Heading> <Text>{signature}</Text> </Flex> )} </Flex> ); }
6. Claimer page
Once we've created our payload and signature, we can go to the Claimer
page to pass our payload and signature. Anyone holding both will be authorised to mint a NFT with the specific metadata we passed to the payload in the Creator
page.
import { Alert, Button, Flex, Heading, Input, Text, Textarea, useToast, } from "@chakra-ui/react"; import { useCallback, useMemo, useState } from "react"; import useSdk from "../hooks/useSdk"; export default function Claimer() { const [loading, setLoading] = useState(false); const sdk = useSdk(); const toast = useToast(); const module = useMemo( () => sdk.getNFTModule("0xc335111d58913C6A382F40c8B020b4ff1ee13Ba1"), [sdk], ); const [payloadValid, setPayloadValid] = useState(true); const [payload, setPayload] = useState<any>(); const [signature, setSignature] = useState<string>(); const mint = useCallback(async () => { const id = await module.mintWithSignature(payload, signature as string); toast({ status: "success", title: `Successfully minted NFT with ID: ${id}`, }); }, [payload, signature, module]); return ( <Flex flexDirection="column"> <Heading textAlign="center">Claimer</Heading> <Text textAlign={"center"}> Bring your payload and signature to claim your NFT! </Text> <Heading size="sm">Payload</Heading> <Textarea minHeight="300px" onChange={(ev) => { const text = ev.target.value; if (!text.length) { setPayloadValid(true); return; } try { const payload = JSON.parse(text); setPayload(payload); setPayloadValid(true); } catch (err) { setPayloadValid(false); } }} /> {!payloadValid && ( <Alert status="warning"> There is a JSON syntax error in your payload. </Alert> )} <Heading marginTop={2} size="sm"> Signature </Heading> <Input onChange={(ev) => setSignature(ev.target.value)}></Input> <Button marginTop={2} colorScheme="green" onClick={async () => { setLoading(true); try { await mint(); } catch (err: any) { toast({ status: "error", title: "Error minting NFT", description: err.message, }); } setLoading(false); }} isLoading={loading} disabled={ signature === undefined || payload === undefined || signature === "" } > Mint NFT </Button> </Flex> ); }
That's it!
Anyone passing the payload and signature can mint an unique NFT!
This is what it looks like 👇
A demo of minting an NFT using an unique signature