How to Build a Pokémon-Inspired NFT Card Game with Thirdweb

How to Build a Pokémon-Inspired NFT Card Game with Thirdweb

How to Build a Pokémon-inspired NFT Card Game with Thirdweb

In this tutorial, we'll walk through the process of creating a Pokémon-inspired NFT trading card game from scratch using Thirdweb. By the end of this guide, you'll have a fully functional web3 application with features like:

  • Custom NFT card collection.
  • Unique card packs that can be bought and opened.
  • Marketplace for selling packs.
  • User profiles to view collected cards.

To see the final result in action, check out the video tutorial:

Ready to catch 'em all? Let's get started!

Prerequisites

Before we begin, make sure you have the following:

  • A Thirdweb account
  • A wallet like MetaMask to connect to Thirdweb.
  • Some test funds on a testnet of choice (Ethereum Sepolia).

Step 1: Create the NFT Card Collection Contract

First, let's create the smart contract that will handle the on-chain logic for our NFT card collection. We'll be using the ERC1155 standard.

You can find the base template for this project, including the assets for the cards on the Github repo we created for it.

  1. Go to the Thirdweb dashboard and click "Deploy Contract"
  2. Look for the "Edition - ERC1155" contract in the NFT section and click it
  3. Fill in the details:
    • Name: Aliemon NFTs
    • Symbol: Choose any symbol
    • Description: A nice description for your collection
    • Image: Upload a cover image
    • Recipient address: Your wallet address
  4. Select your desired chain (we'll use Sepolia testnet) and click "Deploy Now"

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

Step 2: Mint the NFT Cards

With our NFT collection contract deployed, let's mint the actual cards:

  1. Go to the NFTs tab of your contract dashboard
  2. Click "Mint" and fill in the details for each card:
    • Name
    • Image
    • Description
    • Properties (attack, HP, cost, type)
    • Supply
  3. Repeat this process for all the cards in your collection

For this example, I created 9 different Aliemon creature cards, but feel free to customize this however you like.

Step 3: Deploy the Pack Contract

Next, we'll deploy a separate ERC1155 contract to represent our card packs. These packs will contain a random assortment of cards from our collection.

  1. Go back to the Thirdweb dashboard and deploy a new "Pack" contract
  2. Fill in the basic details (name, symbol, description, image)
  3. Set yourself as the recipient address
  4. Deploy the contract to your desired chain

Save the pack contract address as well for the next step.

Step 4: Create Packs with a Custom Script

To create packs containing random cards, we'll write a custom JavaScript script using the Thirdweb SDK.

  1. Create a new project folder and initialize it with npx thirdweb create app
  2. Install the dotenv package with yarn add dotenv
  3. Rename the .env.example to .env fill it up with your private key (for testing only, never share this publicly!) and Thirdweb client ID.
  4. In the scripts folder, create a new file bundlePack.mjs
  5. Run the script with node scripts/bundlePack.mjs

Add the following code (This is an example, as you would like to personalize the details of your pack):

import {
  createThirdwebClient,
  getContract,
  sendAndConfirmTransaction,
} from "thirdweb";

import { config } from "dotenv";

import { privateKeyToAccount } from "thirdweb/wallets";
import {
  isApprovedForAll,
  setApprovalForAll,
} from "thirdweb/extensions/erc1155";
import { createNewPack } from "thirdweb/extensions/pack";
import { sepolia } from "thirdweb/chains";

config();

const main = async () => {
  if (!process.env.PRIVATE_KEY) {
    throw new Error("PRIVATE_KEY is not set");
  }
  if (!process.env.THIRDWEB_SECRET_KEY) {
    throw new Error("THIRDWEB_SECRET_KEY is not set");
  }

  try {
    const EDITION_CONTRACT_ADDRESS = "";
    const PACK_CONTRACT_ADDRESS = "";

    const chain = sepolia;

    // Initialize the client and the account
    const client = createThirdwebClient({
      secretKey: process.env.THIRDWEB_SECRET_KEY,
    });

    const account = privateKeyToAccount({
      client,
      privateKey: process.env.PRIVATE_KEY,
    });

    // Get the contracts

    const contractEdition = getContract({
      address: EDITION_CONTRACT_ADDRESS,
      chain,
      client,
    });

    const contractPack = getContract({
      address: PACK_CONTRACT_ADDRESS,
      chain,
      client,
    });

    // Check if the Account is approved

    const isApproved = await isApprovedForAll({
      contract: contractEdition,
      owner: account.address,
      operator: PACK_CONTRACT_ADDRESS,
    });
    console.log("Account is approved");

    if (!isApproved) {
      const transaction = setApprovalForAll({
        contract: contractEdition,
        operator: PACK_CONTRACT_ADDRESS,
        approved: true,
      });

      const approvalData = await sendAndConfirmTransaction({
        transaction,
        account,
      });

      console.log(`Approval Transaction hash: ${approvalData.transactionHash}`);
    }

    // Create a new Pack of Cards

    const transactionPack = createNewPack({
      contract: contractPack,
      client,
      recipient: account.address,
      tokenOwner: account.address,
      packMetadata: {
        name: "Basic Pack (#1)",
        image:
          "https://d391b93f5f62d9c15f67142e43841acc.ipfscdn.io/ipfs/QmVVPgwgRpF38ja6nddnQfWnRWjhzRXet9ZkUHh5a95Lj8",
        description: "Basic Aliemon Pack",
      },

      openStartTimestamp: new Date(),

      erc1155Rewards: [
        {
          contractAddress: EDITION_CONTRACT_ADDRESS,
          tokenId: BigInt(0),
          quantityPerReward: 1,
          totalRewards: 5,
        },
        {
          contractAddress: EDITION_CONTRACT_ADDRESS,
          tokenId: BigInt(1),
          quantityPerReward: 1,
          totalRewards: 40,
        },
        {
          contractAddress: EDITION_CONTRACT_ADDRESS,
          tokenId: BigInt(2),
          quantityPerReward: 1,
          totalRewards: 20,
        }
        
      ],

      amountDistributedPerOpen: BigInt(5),
    });

    const dataPack = await sendAndConfirmTransaction({
      transaction: transactionPack,
      account,
    });

    console.log(`Pack Transaction hash: ${dataPack.transactionHash}`);
  } catch (error) {
    console.error("Something went wrong", error);
  }
};

main();

This will create a new pack with the specified metadata and rewards. The rewardsPerPack parameter determines how many random cards each pack will contain when opened.

Feel free to modify the script to create multiple packs with different rarity levels and rewards.

Step 5: List Packs for Sale on a Marketplace

To allow users to purchase packs, we'll list them on a marketplace:

  1. Deploy a new "Marketplace" contract from the Thirdweb dashboard
  2. Go to the "Listings" tab and click "Create Direct Listing"
  3. Select the pack contract and token ID of the pack you want to list
  4. Set a price and quantity, then create the listing

Your packs are now available for users to purchase! In the next part, we'll build out the frontend of our application to enable buying packs, opening them to receive cards, and viewing collections.

Step 6: Build the Profile Page

Now that we have the marketplace functionality implemented, let's build the profile page where users can view their owned packs and NFT cards.

Opening Packs

To allow users to open their packs and receive the NFT cards inside:

  1. Add an "Open Pack" button to the PackCard component that triggers the openPack function when clicked.

Implement the openPack function:

const openNewPack = async (packId: number) => {
    const transaction = await openPack({
      contract: packsContract,
      packId: BigInt(packId),
      amountToOpen: BigInt(1),
      overrides: {},
    });

    if (!account) {
      console.error("Account not found");
      return;
    }

    await sendTransaction({
      transaction,
      account: account,
    });
  };

Displaying User's NFT Cards

To display the NFT cards the user has collected:

  1. Fetch the user's NFTs from the card contract using a similar approach as fetching packs.
  2. Map over the NFTs and render an NFTCard component for each one.
  3. Implement a modal or expanded view to show more details about an NFT when clicked, such as its attributes, description, and owned quantity.

This is code snippet example to get the user NFTs:

 useEffect(() => {
    if (walletAddress !== "0x") {
      const fetchNfts = async () => {
        try {
          const fetchedNFTs = await getOwnedNFTs({
            contract: cardsContract,
            start: 0,
            count: 10,
            address: walletAddress,
          });
          const fetchedPacks = await getOwnedNFTs({
            contract: packsContract,
            start: 0,
            count: 10,
            address: walletAddress,
          });
          setNfts(fetchedNFTs);
          setPacks(fetchedPacks);
        } catch (error) {
          console.error("Error fetching NFTs:", error);
        } finally {
          setIsLoading(false);
        }
      };
      fetchNfts();
    }
  }, [walletAddress]);

And that's it! With these final additions, your Pokémon-inspired NFT card game is complete. Users can now buy packs from the marketplace, open them to receive cards, and view their entire collection on the profile page.

Feel free to further customize the UI, add more features, or integrate additional game mechanics to make it your own unique creation.

I hope you enjoyed building this project and learned a lot about web3 development with Thirdweb in the process. Happy coding!

Conclusion

Congratulations! You've now built a fully functional Pokémon-inspired NFT card game using Thirdweb. Users can buy packs from the marketplace, open them to receive cards, and view their entire collection on the profile page.

You can get the complete project completed on the separated Github repo with the entire project.

Throughout this tutorial, we covered:

  • Creating an NFT card collection contract
  • Minting unique NFT cards with attributes
  • Deploying a pack contract to bundle cards into packs
  • Writing a custom script to create packs with random cards
  • Setting up a marketplace to list and sell packs
  • Building a user interface for buying packs, opening them, and viewing collections

Feel free to further customize the game, add new features, and make it your own unique creation. The possibilities are endless!

I hope you enjoyed building this project and learned a lot about web3 development in the process. Happy coding!