How to Create a Web3 Loyalty Program (with Dynamic NFTs)

How to Create a Web3 Loyalty Program (with Dynamic NFTs)

In this guide, you'll learn how to build your own NFT loyalty program using thirdweb's Loyalty Card smart contract. This will allow you to:

  • Create NFTs
  • Issue them to users
  • Update their metadata to create a points or rewards system

Let's get started!

Video Tutorial


Before we begin, make sure you have the following:

  • thirdweb account
  • A wallet like MetaMask to connect to thirdweb
  • Some test MATIC tokens on the Mumbai testnet to pay gas fees

Step 1: Deploy the Loyalty Card Smart Contract

First, we need to deploy the Loyalty Card smart contract that will power our program.

Loyalty Card - ERC721 | Published Smart Contract
A loyalty card NFT collection. Issue unique loyalty cards to your customers.. Deploy Loyalty Card in one click with thirdweb.
  1. Go to the thirdweb dashboard and connect your wallet
  2. Click 'Deploy New Contract' and search for the 'Loyalty Card' contract
  3. Give your contract a name, symbol, description and set the Mumbai testnet as the network
  4. Click 'Deploy Now' and approve the transaction in your wallet

Once deployed, save the contract address somewhere as we'll need it later.

Step 2: Set Up the Project

Next, let's set up our Next.js project:

  1. Open a terminal and run:
npx thirdweb create app 
  1. Name your project, choose Next.js and TypeScript when prompted
  2. Change into the project directory:
cd your-project-name
  1. Open the project in your code editor
  2. In the _app.tsx file, set the active chain to 'mumbai'
  3. Create a new folder called const and add a file addresses.ts with the following:
export const LOYALTY_CARD_ADDRESS = 'your-loyalty-card-contract-address';

Step 3: Create a Claim Page

Now let's create a page where users can claim their loyalty card NFT.

  1. Inside the pages folder, create a new file claim.tsx
  2. Add the following code:
import { useState } from 'react';
import { useAddress, useContract, useSDK } from '@thirdweb-dev/react';
import { LOYALTY_CARD_ADDRESS } from '../const/addresses';

export default function Claim() {
  const address = useAddress();
  const { contract } = useContract(LOYALTY_CARD_ADDRESS);
  const sdk = useSDK();
  const [isClaiming, setIsClaiming] = useState(false);

  async function claim() {
    try {
      const sig = await sdk?.wallet.sign('loyalty-card-claim');
      await contract?.erc721.claim(address, sig);
      alert('Loyalty Card claimed!');
    } catch (err) {
      alert('Failed to claim Loyalty Card');

  return (
      <h1>Claim Loyalty Card</h1>
      <button disabled={isClaiming} onClick={claim}>
        {isClaiming ? 'Claiming...' : 'Claim'}

Here's what this does:

  • We use the useAddress hook to get the connected wallet address
  • We use useContract to get an instance of the loyalty card contract
  • We use useSDK to get the thirdweb SDK instance
  • We have a claim function that:
    • Signs a message with the connected wallet
    • Calls the claim function on the contract, passing the wallet address and signature
    • Displays an alert if successful or logs an error if it fails
  • The component renders a button to claim the loyalty card

Step 4: Create the Profile Page

Next, let's create a profile page that shows the user's loyalty card and points balance.

  1. Inside the pages folder, create a new file profile.tsx
  2. Add the following code:
import { useAddress, useContract, useNFT } from '@thirdweb-dev/react';
import { LOYALTY_CARD_ADDRESS } from '../const/addresses';

export default function Profile() {
  const address = useAddress();
  const { contract } = useContract(LOYALTY_CARD_ADDRESS);
  const { data: nft, isLoading } = useNFT(contract, 0);

  if (isLoading) {
    return <div>Loading...</div>;

  return (
      {nft ? (
          <img src={nft.metadata.image} alt={} />
          <p>Points: {nft.metadata.attributes[0].value}</p>
      ) : (
        <p>No loyalty card found. Claim one first!</p>

Here's what this does:

  • We use useAddress to get the connected wallet address
  • We use useContract to get the loyalty card contract instance
  • We use useNFT to get the loyalty card NFT data for token ID 0 (assuming the user only has one)
  • If the NFT exists, we display its image, name, and points balance from the metadata
  • If no NFT exists, we show a message prompting the user to claim one

Step 5: Updating Points

The last piece is giving admins a way to update a user's points balance.

  1. Inside the pages folder, create a file admin.tsx
  2. Add this code:
import { FormEvent, useState } from 'react';
import { useContract } from '@thirdweb-dev/react';
import { LOYALTY_CARD_ADDRESS } from '../const/addresses';

export default function Admin() {
  const { contract } = useContract(LOYALTY_CARD_ADDRESS);
  const [tokenId, setTokenId] = useState('');
  const [points, setPoints] = useState('');

  async function handleSubmit(e: FormEvent) {
    await contract?.call('updatePoints', [parseInt(tokenId), parseInt(points)]); 
    alert(`Updated token ${tokenId} with ${points} points`);

  return (
      <h1>Admin - Update Points</h1> 
      <form onSubmit={handleSubmit}>
          Token ID:
            onChange={(e) => setTokenId(}
        <br />
            onChange={(e) => setPoints(}  
        <br />
        <button type='submit'>Update Points</button>

Here's what this does:

  • We use useContract to get the loyalty card contract instance
  • We have a form with fields for token ID and points
  • When submitted, it calls the updatePoints function on the contract with the provided values
  • The updatePoints function must be implemented in your contract to update the NFT metadata

Step 6: Update the Navbar

To make it easy to navigate between the pages we just created, let's add them to the navbar.

  1. Open the components/Navbar.tsx file
  2. Add the following links:
<Link href='/claim'>
  <a>Claim Card</a>
<Link href='/profile'>
<Link href='/admin'>

Step 7: Add Styling (Optional)

If you want to style your pages, you can add CSS to the corresponding .module.css files in the styles folder. For example, to style the claim page:

  1. Open styles/Claim.module.css
  2. Add your styles, for example:
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;

.title {
  font-size: 2rem;
  margin-bottom: 1rem;

.button {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 0.25rem;
  background-color: #0070f3;
  color: #fff;
  font-size: 1rem;
  cursor: pointer;

.button:hover {
  background-color: #0060d9;

.button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
  1. Import and use the styles in pages/claim.tsx:
import styles from '../styles/Claim.module.css';

// ...

return (
  <div className={styles.container}>
    <h1 className={styles.title}>Claim Loyalty Card</h1>
      {isClaiming ? 'Claiming...' : 'Claim'}

Repeat this process for the other pages to add your own custom styles.


Congratulations! You've now built a complete NFT loyalty program using the thirdweb Loyalty Card contract and Next.js.

In this guide, we covered how to:

  1. Deploy the Loyalty Card contract
  2. Set up a Next.js project with TypeScript
  3. Create pages for claiming cards, viewing profiles, and updating points
  4. Add navigation between pages
  5. Style the pages with CSS modules

That's it! You now have the key components for an NFT-based loyalty program.

What's next?

Some additional features you could add:

  • Requiring admin authentication for updating points
  • Adding more metadata fields like user info, rank, expiration date, etc.
  • Creating a mechanism for users to redeem points for rewards

I hope this guide has been helpful! If you have any questions or feedback, feel free to reach out. Happy building and good luck with your web3 loyalty program?