DEV Community

Cover image for Life Simulator dApp using Thirdweb
Alexander Gekov
Alexander Gekov

Posted on • Edited on

Life Simulator dApp using Thirdweb

Preface

This article is my final project for the course on Bitcoin and Blockchain, taught at the Prague University of Economics.

Life Simulator dApp

We are going to make a life simulator game using smart contracts and the web3 stack. I will be trying to walk you through the process and explain everything for beginners but be aware it would make more sense with a little bit of programming experience.

Prerequisites

There are 4 main things that you need to have to get started:

  • VSCode - a text editor that we will use to write our code. You can download it from here:

Download Visual Studio Code - Mac, Linux, Windows

  • Node.js and npm - node allows us to run javascript directly on the computer (as opposed to the browser) and npm is short for (node package manager). It will allow us to download and install packages for our app. You can download them (as a bundle) here:

Download | Node.js

  • Foundry - foundry is an ethereum development framework. Forge is a tool for writing tests that ships with Foundry.

Foundry Book

  • Metamask Wallet - So we can communicate with the blockchain.

The crypto wallet for Defi, Web3 Dapps and NFTs | MetaMask

Let’s start

Go to the folder you want to create your app and open. Inside the terminal type the following:

npx thirdweb create
Enter fullscreen mode Exit fullscreen mode

This is the starting point of the Thirdweb development stack. It will ask us what kind of project do we want to create.

Select Contract and we will name our project lifesimulator_contract. On the next questions select Forge, type the name of our contract LifeUpgradeContract and select ERC1155 + Lazy Mint.

Run cd lifesimulator_contract and code . to open the project inside VSCode.

Life Stages

For our game we will have 4 NFTs symbolising each of the life stages human go through - Baby, Teenager, Adult, Elder. Users will be able to claim Baby NFTs and combine them to receive a Teenager NFT in return, and so on.

LifeStages

💡 I generated the images using OpenAI’s DALLE-2.

Make sure people can claim only Baby NFTs

In order for our game to work, people should be able to claim only Baby NFTs otherwise it defeats the purpose of our game.

Go to src/LifeUpgradeContract.sol and add the following snippet right under the constructor method:

function verifyClaim(
        address _claimer,
        uint256 _tokenId,
        uint256 _quantity
    ) public view override {
        require(_tokenId == 0, "Only Babies are claimable!");
        require(_quantity == 1, "Only one Baby can be claimed at a time!");
    }
Enter fullscreen mode Exit fullscreen mode

This will make sure that only babies are claimable and they can be claimed one at a time.

💡 TIP: Install the Solidity VSCode extension the get the proper highlighting.

We can now add our age method below the verifyClaim:

function age() public {
        if(balanceOf[msg.sender][2] >= 2){
            // Burn 2 Adult tokens
            _burn(msg.sender, 2, 2);
            // Mint 1 Elder token
            _mint(msg.sender, 3, 1, "");
        } else if (balanceOf[msg.sender][1] >= 2){
            // Burn 2 Teenager tokens
            _burn(msg.sender, 1, 2);
            // Mint 1 Adult token
            _mint(msg.sender, 2, 1, "");
        } else if (balanceOf[msg.sender][0] >= 2){
            // Burn 2 Baby tokens
            _burn(msg.sender, 0, 2);
            // Mint 1 Teenager token
            _mint(msg.sender, 1, 1, "");
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now it’s time to deploy our contract:

npx thirdweb deploy
Enter fullscreen mode Exit fullscreen mode

The thirdweb dashboard will open. Fill in the fields and click Deploy Now.

Deploy

Metamask may need some gas fees, be sure to top up here: https://goerlifaucet.com/

Wait for the transaction to be approved aaand you’re done. You have deployed your smart contract to the Goerli Testnet. Good job!

Uploading the images

Now we can go to the NFT tab in the thirdweb dashboard and click Single Upload. We will upload all of the images for our NFTs now. Order is important! First Baby, then Teenager, then Adult and finally Elder.

It should look like this:

NFTs

Build our App

So we have our contract, but there’s no frontend for users to interact with our contract yet. Let’s fix that.

Go back to the terminal and type the following again:

npx thirdweb create
Enter fullscreen mode Exit fullscreen mode

Instead of Contract, we will choose ***App*** now. Name will be life_simulator_app. Choose EVM, Next.js and TypeScript.

Run cd life_simulator_app and code . to open it in VSCode.

Firstly, go to pages/_app.tsx and change the activeChainId to be the Goerli Testnet.

const activeChainId = ChainId.Goerli;
Enter fullscreen mode Exit fullscreen mode

In pages/index.tsx make sure to delete all the boiler plate code until you are left with something like this:

import { ConnectWallet } from "@thirdweb-dev/react";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <main className={styles.main}>

        <div className={styles.connect}>
          <ConnectWallet />
        </div>

      </main>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

If you now run npm run dev in the terminal you should be able to see this:

ConnectWallet

Great! Now let’s add the functionality to see our life simulator NFTs.

Now let’s update pages/index.tsx :

import { ConnectWallet, ThirdwebNftMedia, useAddress, useContract, useOwnedNFTs } from "@thirdweb-dev/react";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  // This is the address of our Life Simulator contract. You can find it in the Thirdweb dashboard.
  const {contract} = useContract("0x09E25aE9b62d8A3221694964fc8D13Ff9fdab4cA");

  // This is the address of the currently connected user's wallet.
  const address = useAddress();

  // This is a list of all the NFTs owned by the currently connected user.
  const { data: nfts } = useOwnedNFTs(contract, address);

  return (
    <div className={styles.container}>
      <main className={styles.main}>
        {/* Connect Wallet button */}
        <div className={styles.connect}>
          <ConnectWallet />
        </div>
        {/* The Grid displaying our NFTs */}
        <div className={styles.grid}>{nfts?.map(nft => <div className={styles.nft} key={nft.metadata.id.toString()}>
          <ThirdwebNftMedia width="300"  metadata={nft.metadata} />
          {nft.metadata.name}
          <div>({nft.quantityOwned})</div>
          </div>)}
        </div>
      </main>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

We have added a Grid to display our NFTs and given it a style of grid. We use the ThirdwebNftMedia to display the image that we uploaded earlier. We also display the quantity owned of each NFT.

Now let’s add some buttons in order to claim a Baby and also to age our NFTs so they merge.

import { ConnectWallet, ThirdwebNftMedia, useAddress, useContract, useOwnedNFTs, Web3Button } from "@thirdweb-dev/react";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  // This is the address of our Life Simulator contract. You can find it in the Thirdweb dashboard.
  const {contract} = useContract("0xC3D436BE929c8820f4A5fBcb2bd2DaC86d949af4");

  // This is the address of the currently connected user's wallet.
  const address = useAddress();

  // This is a list of all the NFTs owned by the currently connected user.
  const { data: nfts } = useOwnedNFTs(contract, address);

  return (
    <div className={styles.container}>
      <main className={styles.main}>
        {/* Connect Wallet button */}
        <div className={styles.connect}>
          <ConnectWallet />
        </div>
        {/* The Grid displaying our NFTs */}
        <div className={styles.grid}>{nfts?.map(nft => <div className={styles.nft} key={nft.metadata.id.toString()}>
          <ThirdwebNftMedia width="300"  metadata={nft.metadata} />
          {nft.metadata.name}
          <div>({nft.quantityOwned})</div>
          </div>)}</div>
        {/* Button for claiming a Baby NFT */}
        <Web3Button contractAddress="0xC3D436BE929c8820f4A5fBcb2bd2DaC86d949af4" action={contract => contract.erc1155.claim(0,1)}>Claim a Baby</Web3Button>
        <hr style={{margin: 10}} />
        {/* Button for aging our human */}
        <Web3Button accentColor="blue" contractAddress="0xC3D436BE929c8820f4A5fBcb2bd2DaC86d949af4" action={contract => contract.call("age")}>Age 💀</Web3Button>
      </main>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

For the buttons we use the Web3Button component. We pass in our smart contract address and also an action. On the first button we pass in the claim action in order to mint 1 Baby NFT. On the other button we can call the age method.

Lastly, we will add some confetti and celebration message when the user owns an Elder NFT, to make the game complete.

Install react-confetti by running npm install react-confetti. Make sure to also install the react-use library - npm install react-use. We can now create a variable to check whether the user owns an Elder NFT. If it’s true, we can show the confetti and the message.

import { ConnectWallet, ThirdwebNftMedia, useAddress, useContract, useOwnedNFTs, Web3Button } from "@thirdweb-dev/react";
import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
import Confetti from 'react-confetti'
import useWindowSize from 'react-use/lib/useWindowSize'

const Home: NextPage = () => {
  // This is the address of our Life Simulator contract. You can find it in the Thirdweb dashboard.
  const {contract} = useContract("0x09E25aE9b62d8A3221694964fc8D13Ff9fdab4cA");

  // This is the address of the currently connected user's wallet.
  const address = useAddress();

  // This is a list of all the NFTs owned by the currently connected user.
  const { data: nfts } = useOwnedNFTs(contract, address);

  // This is the Elder NFT. We'll use it to show a confetti animation when the user owns it.
  const elderOwned = nfts?.find(nft => nft.metadata.id === "3");

  const { width, height } = useWindowSize()

  return (
    <div className={styles.container}>
      <main className={styles.main}>
        {/* Confetti animation if we own the Elder NFT */}
        {elderOwned ? <Confetti width={width} height={height} /> : null}
        {/* Connect Wallet button */}
        <div className={styles.connect}>
          <ConnectWallet />
        </div>
        {elderOwned ? <div className={styles.celebrateText}>CONGRATULATIONS! YOU WON!</div> : null}
        {/* The Grid displaying our NFTs */}
        <div className={styles.grid}>{nfts?.map(nft => <div className={styles.nft} key={nft.metadata.id.toString()}>
          <ThirdwebNftMedia width="300"  metadata={nft.metadata} />
          {nft.metadata.name}
          <div>({nft.quantityOwned})</div>
          </div>)}</div>
        {/* Button for claiming a Baby NFT */}
        <Web3Button contractAddress="0x09E25aE9b62d8A3221694964fc8D13Ff9fdab4cA" action={contract => contract.erc1155.claim(0,1)}>Claim a Baby</Web3Button>
        <hr style={{margin: 10}} />
        {/* Button for aging our human */}
        <Web3Button accentColor="blue" contractAddress="0x09E25aE9b62d8A3221694964fc8D13Ff9fdab4cA" action={contract => contract.call("age")}>Age 💀</Web3Button>
      </main>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

As you can see we added the Confetti and used the react-use library to get the window width and height. We only show the confetti and the congratulations message when the elderOwned variable is true.

For the styles, you can go to styles/Home.module.css and extend it with the following classes:

.nft {
  display: flex; 
  flex-direction: column;
  text-align: center;
  font-size: 3rem;
  padding: 1rem;
}  

/* make the css class celebratory */
.celebrateText{
  font-size: 1.5rem;
  padding: 1rem;
  color: #f213a4;
}
Enter fullscreen mode Exit fullscreen mode

Final product

Awesome! We have everything ready now. If you visit https://localhost:3000 you should see our final app.

If you want to try the live demo, here is the link: https://life-simulator-app.vercel.app/

Final

PS. Make sure you get an Elder NFT to see the confetti:

Congrats

If you enjoyed this tutorial you can follow me on my socials:

Twitter

LinkedIn

YouTube

Top comments (0)