How to Build a Web3 Play-To-Earn Platform with Next.js, Typescript, and Solidity
Tags: web3, dapp, nextjs, solidity
Published: 2024-01-28 13:42:52

What you will be building, see our [git repo](https://github.com/Daltonic/play2earnX) for the finished work and our [live demo](https://play2earn-x.vercel.app/).

![Play2EarnX](https://github.com/Daltonic/play2earnX/raw/master/screenshots/0.png)

![Play2EarnX](https://github.com/Daltonic/play2earnX/raw/master/screenshots/1.png)

## Introduction

Welcome to our comprehensive guide on "Building a Web3 Play-To-Earn Platform with Next.js, Typescript, and Solidity". In this tutorial, we'll build a decentralized Play-To-Earn platform that leverages the power of blockchain technology. You'll gain a clear understanding of the following:


- Building dynamic interfaces with Next.js
- Crafting Ethereum smart contracts with Solidity
- Incorporating static type checking using TypeScript
- Deploying and interacting with your smart contracts
- Understanding the fundamentals of blockchain-based Play-To-Earn platforms

By the end of this guide, you'll have a functioning decentralized platform where users can participate in Play-To-Earn games, with all activities managed and secured by Ethereum smart contracts.

As an added incentive for participating in this tutorial, we're giving away a copy of our prestigious book on becoming an in-demand Solidity developer. This offer is free for the first 300 participants. For instructions on how to claim your copy, please watch the short video below.



![Capturing Smart Contract Development](https://paper-attachments.dropboxusercontent.com/s_70534477D8955F3500A3FBCD4F334C4E7B1BEDE321CB5F5FC0792B12F0A26F99_1702985122073_tier.png)

## Prerequisites

You will need the following tools installed to build along with me:

- Node.js
- Yarn
- Git Bash
- MetaMask
- Next.js
- Solidity
- Redux Toolkit
- Tailwind CSS

To set up MetaMask for this tutorial, please watch the instructional video below:


{% embed https://www.youtube.com/watch?v=qV1mbFOtkxo %}


Once you have successfully completed the setup, you are eligible to receive a free copy of our book. To claim your book, [please fill out the form to submit your proof-of-work](https://forms.gle/uGtm3ZcEHb5pqd3YA).

Watch the following [**instructional videos**](https://www.youtube.com/playlist?list=PLUDcVqFK2t-C36KaTMzV3y1wu8Hf-qyAI) to receive up to 3-months of free premium courses on Dapp Mentors Academy, including:


- [Fullstack NFT Minting Course](https://dappmentors.org/courses/details/fullstack-nft-minting-course)
- [Fullstack NFT Marketplace Course](https://dappmentors.org/courses/details/fullstack-nft-marketplace-course)
- [Fullstack Blockchain Course](https://dappmentors.org/courses/details/fullstack-blockchain-101-course)
- etc.

Ready to take your Bitfinity skills to the next level? Dive into the intricacies of smart contract development on ICP as you build a Ticketing Management System in the upcoming module. [**Create, deploy, and manage your smart contracts on the Bitfinity network.**](https://bitfinity.network/)

With that said, let’s jump into the tutorial and set up our project.

## Setup

We'll start by cloning a prepared frontend repository and setting up the environment variables. Run the following commands:


    git clone https://github.com/Daltonic/play2earnX
    cd play2earnX
    yarn install
    git checkout 01_no_redux

Next, create a `.env` file at the root of the project and include the following keys:


    NEXT_PUBLIC_RPC_URL=http://127.0.0.1:8545
    NEXT_PUBLIC_ALCHEMY_ID=<YOUR_ALCHEMY_PROJECT_ID>
    NEXT_PUBLIC_PROJECT_ID=<WALLET_CONNECT_PROJECT_ID>
    NEXTAUTH_URL=http://localhost:3000
    NEXTAUTH_SECRET=somereallysecretsecret

Replace `<YOUR_ALCHEMY_PROJECT_ID>` and `<WALLET_CONNECT_PROJECT_ID>` with your respective project IDs. 

`YOUR_ALCHEMY_PROJECT_ID`: [Get Key Here](https://dashboard.alchemy.com/) 
`WALLET_CONNECT_PROJECT_ID`: [Get Key Here](https://cloud.walletconnect.com/sign-in)

Finally, run `yarn dev` to start the project.


![Dummy Data](https://paper-attachments.dropboxusercontent.com/s_F9FCBF33A439F5C47CE531BD9DE520F29B626C39CE80F80AB4BE4CBA7F48FC46_1703239727528_Screenshot+2023-12-22+at+11.08.39+AM.png)


Our user interface is prepared to incorporate smart contracts, however, we still need to integrate Redux in order to facilitate the sharing of data.


## Building the Redux Store
![Store Structure](https://paper-attachments.dropboxusercontent.com/s_70534477D8955F3500A3FBCD4F334C4E7B1BEDE321CB5F5FC0792B12F0A26F99_1702989191922_Screenshot+2023-12-19+at+1.33.05+PM.png)


The above image represents the structure of our Redux store, it will be simple since we are not creating some overly complex project.

We'll set up Redux to manage our application's global state. Follow these steps:


1. Create a `store` folder at the project root.
2. Inside `store`, create two folders: `actions` and `states`.
3. Inside `states`, create a `globalStates.ts` file.


{% gist https://gist.github.com/Daltonic/f542e5769c9f9f92009c0ab97e26ffe0 %}



4. Inside `actions`, create a `globalActions.ts` file.


{% gist https://gist.github.com/Daltonic/9d5487e2e3f2517b36a1a38805e5c989 %}



5. Create a `globalSlices.ts` file inside the `store` folder.


{% gist https://gist.github.com/Daltonic/0af23b96f972b0f3a934c4a26b90286d %}



6. Create an `index.ts` file inside the `store` folder.


{% gist https://gist.github.com/Daltonic/7b7ec88441e0b03b3c11ada05aa49104 %}



7. Update the `pages/_app.tsx` file with the Redux provider.


{% gist https://gist.github.com/Daltonic/b13c27143cfd13d5e2949a8e530f160d %}


We have implemented Redux toolkit in our application and plan to revisit its usage when integrating the backend with the frontend.


## Smart Contract Development

Next, we'll develop the smart contract for our platform:


1. Create a `contracts` folder at the project root.
2. Inside `contracts`, create a `PlayToEarnX.sol` file and add the contract code below.


{% gist https://gist.github.com/Daltonic/4543f76e6a0731a8795f618248d663da %}


The above smart contract is a Play-To-Earn (P2E) gaming platform, which is created with Solidity. The contract integrates features such as game creation, player invitations, score recording, and payout distribution. 

**Here's a breakdown of its logic, function by function:**


1. **Constructor (**`**constructor(uint256 _pct) ERC20('Play To Earn', 'P2E')**`**):** This function initializes the contract. It sets the service fee percentage and assigns initial token parameters.
    
2. **createGame Function:** This function allows a user to create a new game, specifying the title, description, number of participants, number of winners, start date, and end date. It requires a stake greater than zero and validates all input data. A new game and score structure are created and stored in their respective mappings.
    
3. **deleteGame Function:** This function allows the owner of a game to delete it, provided no participants have joined. It refunds the stake to the game owner and marks the game as deleted.
    
4. **playedSaved Function:** This function is an internal function that creates a new player for a game. It increments the total players count, adds the new player to the players mapping, and increases the game's acceptees count.
    
5. **invitePlayer Function:** This function allows a user to invite another player to a game. It checks if the game exists and if there is room for more players. An invitation is then created and added to the invitationsOf mapping.
    
6. **acceptInvitation Function:** This function allows a user to accept an invitation to a game, provided they send enough funds to cover the game's stake. It creates a new player for the game and updates the invitation status.
    
7. **rejectInvitation Function:** This function allows a user to reject an invitation.
    
8. **payout Function:** This function handles the payout distribution at the end of a game. It checks if the game has ended and if it has not already been paid out. It calculates the total stakes, platform fee, creator fee, and game revenue. It pays out the platform fee to the contract owner, the creator fee to the game owner, and distributes the game revenue among the winners. It also mints new tokens for the winners.
    
9. **sortScores Function:** This function sorts players based on their scores. It uses a simple bubble sort algorithm with a twist: it also takes into account whether a player has played the game or not. Players who have not played are sorted to the bottom.
    
10. **saveScore Function:** This function allows a player to save their score for a game. It validates the game and player, then updates the player's score.
    
11. **setFeePercent Function:** This function allows the contract owner to change the service fee percentage.
    
12. **getGames, getPaidOutGames, getMyGames Functions:** These functions return lists of games based on different criteria (all games, games that have been paid out, and games owned by the caller).
    
13. **getGame Function:** This function returns the details of a specific game.
    
14. **getInvitations Function:** This function returns all invitations for a specific game.
    
15. **getMyInvitations Function:** This function returns all invitations received by the caller.
    
16. **getScores Function:** This function returns all scores for a specific game.
    
17. **payTo Function:** This internal function sends funds to a specified address.
    
18. **currentTime Function:** This internal function returns the current timestamp.


## Contract Deployment and Seeding

Now, let's deploy our smart contract and populate it with some dummy data:

1. Create a `scripts` folder at the project root.
2. Inside `scripts`, create a `deploy.js` and a `seed.js` file and add the following codes.

**Deploy Script**

{% gist https://gist.github.com/Daltonic/de4cb519cefa6baa8d3b2d37b24c6afd %}


**Seed**

{% gist https://gist.github.com/Daltonic/723ba48f1f3c7fdac38225afc5eb3466 %}



3. Run the following commands to deploy the contract and seed it with data:


    yarn hardhat node # Run in terminal 1
    yarn hardhat run scripts/deploy.js # Run in terminal 2
    yarn hardhat run scripts/seed.js # Run in terminal 2

If you did that correctly, you should see a similar output like the one below:

![Deployment](https://paper-attachments.dropboxusercontent.com/s_70534477D8955F3500A3FBCD4F334C4E7B1BEDE321CB5F5FC0792B12F0A26F99_1702992266380_Screenshot+2023-12-18+at+10.01.28+AM.png)


At this point we can start the integration of our smart contract to our frontend.


{% embed https://www.youtube.com/watch?v=fJIiqeevqoU %}



## Frontend Integration

First, create a `services` folder at the project root, and inside it, create a `blockchain.tsx` file. This file will contain functions to interact with our smart contract.


{% gist https://gist.github.com/Daltonic/1a1fc8d33d508ea39a8bdc640a3072ec %}


The above code is a service that interacts with our Play-To-Earn gaming smart contract on the chain. It uses the `Ethers.js` library to interact with the Ethereum blockchain for contract interaction.

**Here's a detailed breakdown of its functions:**


1. **getEthereumContracts Function:** This function sets up a connection to the Ethereum blockchain and the smart contract. It uses the ethers library to create a provider (to interact with the Ethereum blockchain) and a signer (to sign transactions). Then, it creates a contract instance that can be used to interact with the smart contract.
    
2. **getOwner Function:** This function retrieves the owner of the contract. It calls the owner function of the contract, which is defined in the OpenZeppelin's Ownable contract.
    
3. **getGames, getMyGames, getGame Functions:** These functions retrieve information about the games. They call the corresponding functions in the contract and structure the returned data.
    
4. **getInvitations, getMyInvitations Functions:** These functions retrieve information about the game invitations. They call the corresponding functions in the contract and structure the returned data.
    
5. **getScores Function:** This function retrieves scores of a game. It calls the corresponding function in the contract and structures the returned data.
    
6. **createGame Function:** This function creates a new game by calling the createGame function of the contract. It checks if the browser provider is installed and handles any errors that might occur.
    
7. **deleteGame Function:** This function deletes a game by calling the deleteGame function of the contract. It checks if the browser provider is installed and handles any errors that might occur.
    
8. **invitePlayer Function:** This function invites a player to a game by calling the invitePlayer function of the contract. It checks if the browser provider is installed and handles any errors that might occur.
    
9. **saveScore Function:** This function saves a player's score by calling the saveScore function of the contract. It checks if the browser provider is installed and handles any errors that might occur.
    
10. **payout Function:** This function handles payout distribution by calling the payout function of the contract. It checks if the browser provider is installed and handles any errors that might occur.
    
11. **respondToInvite Function:** This function allows a user to respond to an invitation by calling either the acceptInvitation or rejectInvitation function of the contract, based on the user's response. It checks if the browser provider is installed and handles any errors that might occur.
    
12. **structuredGames, structuredInvitations, structuredScores Functions:** These functions structure the data returned from the contract into a more manageable format. They convert the data types from the contract's format to JavaScript's format and sort the data.
    

Next, update the `provider.tsx` file inside `services` to include the `bitfinity` network using the following codes.


{% gist https://gist.github.com/Daltonic/5cfe4e97fe6d258a256f76c46cb7beaa %}



## Page Interacting with Smart Contract

Next, we'll link the functions in the blockchain service to their respective interfaces in the frontend:

**No 1:  Displaying Available Games**
Update `pages/index.tsx` to get data from the `getGames()` function.


{% gist https://gist.github.com/Daltonic/c275c2e007ad1d8ca56c2b5adc5f21c3 %}



**No 2:  Displaying User’s Games**
Update `pages/games.tsx` to get data from the `getMyGames()` function.


{% gist https://gist.github.com/Daltonic/c0226ed85d75fb5c1ea39909ab692b05 %}


Notice how we combined `useEffect()` to dispatch the games into the store before rendering on screen.

**No 3:  Displaying User’s Invitations**
Update `pages/invitations.tsx` to get data from the `getMyInvitations()` function.


{% gist https://gist.github.com/Daltonic/d10d5dca4d8c8d7423345bbd39e83cbc %}


We also combined `useEffect()` to dispatch the invitations into the store before rendering on screen.

**No 4: Displaying Game Invitations**
Update `pages/invitations/[id].tsx` to use the `getServerSideProps()`, `getGame()`, and `getInvitations()` to retrieve a game invitations by specifying the game Id.


{% gist https://gist.github.com/Daltonic/232c8a64779ababf873a3e94e609b11d %}



**No 5: Showcasing a Gameplay**
Update `pages/gameplay/[id].tsx` to use the `getServerSideProps()`, `getGame()`, and `getScores()` to retrieve a game players by specifying the game Id.


{% gist https://gist.github.com/Daltonic/fb73e1ed6f11043a5e63a6df903392ed %}


The script mentioned above is the core of the frontend code for this page. It contains the necessary functions and behaviors to create a memory game where users can compete against each other.

**No 6: Displaying Game Result**
Update `pages/results/[id].tsx` to use the `getServerSideProps()`, `getGame()`, and `getScores()` to retrieve a game score by specifying the game Id.


{% gist https://gist.github.com/Daltonic/e448bc7e13356314d7e7169dfd5d43ad %}


It's worth mentioning that users can make a payout from this page after the game duration has ended.

## Components with Smart Contract

Let's apply the same approach we used for the previous pages and update the following components to interact with the smart contract.

**No 1: Creating New Games**
Update `components/CreateGame.tsx` file to use the `handleGameCreation()` function to call the `createGame()` function for form submission.


{% gist https://gist.github.com/Daltonic/6106948a6b3d49896ec1bb6847b51bad %}


**No 2: Handling Game Deletion**
Update `components/GameActions.tsx` file to use the `handleDelete()` function to call the `deleteGame()` function.


{% gist https://gist.github.com/Daltonic/593b697b274e0fab608801bbc7e5515f %}


**No 3: Displaying Game Details Modal**
Update `components/GameDetails.tsx` file to use the `closeModal()` function to call the `setResultModal()` function.


{% gist https://gist.github.com/Daltonic/f89025c44a207a710cf04ac79515d9dd %}


**No 4: Inviting Players**
Update `components/GameInvitation.tsx` file to use the `handleResponse()` function to call the `respondToInvite()` function.


{% gist https://gist.github.com/Daltonic/4405f7b64b34177e6be707d29301ab25 %}


**No 5: Launching Game Details Modal**
Update `components/GameList.tsx` file to use the `openModal()` function to call the `setResultModal()` function.


{% gist https://gist.github.com/Daltonic/877971532ffa1a1e364bc1fb8ba03234 %}


Please note that this `setResultModal()` function actually launches the game details modal, that’s a little oversight in function name.

**No 6: Launching the Create Game Modal**
Update `components/Hero.tsx` file to `dispatch` the `setCreateModal()` function, this will help us launch the create game form modal.


{% gist https://gist.github.com/Daltonic/cb1c9ee1024f9219a9813fc261d4befd %}


**No 7: Launching the Invite Modal**
Lastly, update `components/InviteModal.tsx` file to use the `sendInvitation()`  function to call the `invitePlayer()` function.


{% gist https://gist.github.com/Daltonic/6ca046aef2762c32a7c382b9c648d29f %}


The project is now complete as all components and pages are connected to the smart contract through the implementation of these updates.

If your Next.js server was offline, you can bring it back up by executing the command `**yarn dev**`.

For further learning, we recommends watch the full video of this build on our [**YouTube channel**](https://www.youtube.com/@dappmentors?sub_confirmation=1) and [**visiting our website for additional resources**](https://dappmentors.org/). 


{% embed https://www.youtube.com/watch?v=zi_6RWkOB1w %}

## Conclusion

In this tutorial, we've successfully built a decentralized Play-To-Earn platform using Next.js, TypeScript, and Solidity. We've established the development environment, constructed the Redux store, and deployed our smart contract to our local chain. 

By merging the smart contract with the frontend, we've ensured a seamless gaming experience. This guide has equipped you with the skills to create dynamic interfaces, craft Ethereum smart contracts, manage shared data with Redux, and interact with smart contracts from the frontend. Now, you're ready to create your own Web3 Play-To-Earn platform. Happy coding!


## About Author

I am a web3 developer and the founder of [Dapp Mentors](https://dappmentors.org/), a company that helps businesses and individuals build and launch decentralized applications. I have over 7 years of experience in the software industry, and I am passionate about using blockchain technology to create new and innovative applications. I run a [**YouTube channel called Dapp Mentors**](https://www.youtube.com/@dappmentors?sub_confirmation=1) where I share tutorials and tips on web3 development, and I regularly post articles online about the latest trends in the blockchain space.

**Stay connected with us, join communities on**
Discord: [Join](https://discord.gg/PgFDUVT6n9)
X-Twitter: [Follow](https://twitter.com/iDaltonic)
LinkedIn: [Connect](https://www.linkedin.com/company/dapp-mentors)
GitHub: [Explore](https://github.com/Daltonic)
Website: [Visit](https://dappmentors.org/)