Life Simulator dApp using Thirdweb
Tags: blockchain, javascript, beginners, tutorial
Published: 2023-01-18 00:24:07

## 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](https://code.visualstudio.com/download)

- **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](https://nodejs.org/en/download/)

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

[Foundry Book](https://book.getfoundry.sh/getting-started/installation)

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

[The crypto wallet for Defi, Web3 Dapps and NFTs | MetaMask](https://metamask.io/)

## Let’s start

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

```bash
npx thirdweb create
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eooh6j6j4phyosorsdbz.png)

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

</aside>

## 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:

```solidity
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!");
    }
```
This will make sure that only babies are claimable and they can be claimed one at a time.

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

</aside>

We can now add our `age` method below the `verifyClaim`:

```solidity
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, "");
        }
    }
```

Now it’s time to deploy our contract:

```bash
npx thirdweb deploy
```

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


![Deploy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/63gzrvkr00ngdrxr9pbw.png)

Metamask may need some gas fees, be sure to top up here: [https://goerlifaucet.com/](https://goerlifaucet.com/?__cf_chl_tk=jMGQjhAWMOAi45gPFIGZpPL6VR8khPL7HY4CII7Uvvw-1673990918-0-gaNycGzNCP0)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48ur6h5uqownry8o21bk.png)

## 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:

```bash
npx thirdweb create
```

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.

```jsx
const activeChainId = ChainId.Goerli;
```

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

```jsx
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;
```

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


![ConnectWallet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/et14ppsm41av9par9uoa.png)

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

Now let’s update `pages/index.tsx` :

```jsx
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;
```

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.

```jsx
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;
```

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.

```jsx
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;
```

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:

```css
.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;
}
```

## 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/](https://life-simulator-app.vercel.app/)


![Final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pea93gghbrc306bsccy3.png)

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


![Congrats](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gx45son58kod7r4ap10b.png)

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

[Twitter](https://twitter.com/AlexanderGekov)

[LinkedIn](https://www.linkedin.com/in/aleksandar-gekov/)

[YouTube](https://www.youtube.com/channel/UCAARceUUB_X6xsgeRBRg16A)