Sight AI
  • 👋Overview
    • đŸĨFHE
    • 🍍The Problem
  • 🍇Sight Oracle
    • 💡Concepts
      • 🍉Architecture
      • 📔Contract
      • 🟠Decentralized Oracle Node Network
      • 📠FHE Compute Execution Virtual Machine
      • 📃RequestBuilder
    • ⏊Supported Networks
  • Sight Contracts
    • Getting Started
    • Understanding Request Model
  • SIGHT.JS
    • Getting Started
    • Make an encrypt value - CLI
    • Make an encrypt value - Frontend
  • â„šī¸Tutorial
    • Environment Setup
      • Prerequisite
      • Setup Local Development Environment
      • Fund Local Wallet
    • 👷Create Your First dAPP using Hardhat
    • đŸ› ī¸Create Your First dAPP using Foundry
    • 🎲Create a Crypto Pusher Game
      • Contract
      • Interface
    • 📄Remix FHE Demo Local Deployment Tutorial- Preparation
    • 📄Remix FHE Demo Local Deployment Tutorial- Deploying Contracts Using Remix
    • 📄Remix FHE Demo Local Deployment Tutorial- Contracts Explanation&Interaction
  • 🔎Research
    • ✅Verifiable FHE
    • 🌟SPARC
    • 📅Research Updates
  • 🍓Innovative Use Cases and dApps
    • đŸĨĨFHE On-Chain Game
    • A Fairer Prediction Markets with Sight Oracle
  • đŸģRoadmap
  • 🎨Community
    • Website
    • Twitter
    • Telegram
    • Github
  • Sight Incentive Plan - Season 1
  • Sight Incentive Plan - Season 2
Powered by GitBook
On this page
  • Prerequisites
  • Create a Nextjs project
  • Install dependencies
  • Import the RainbowKit configuration
  • Retrieve the status from contact
  • Deposit function
  • CSS Style
  • Coin Pusher Interface
  1. Tutorial
  2. Create a Crypto Pusher Game

Interface

PreviousContractNextRemix FHE Demo Local Deployment Tutorial- Preparation

Last updated 6 months ago

Prerequisites

In blockchain projects, it's often not practical to directly use ethers.js or web3.js to interact with MetaMask, as it can be cumbersome. To address this and facilitate faster connection and interaction with the blockchain, many excellent third-party libraries have been developed, such as web3-react, web3-modal, rainbow-kit (which is an encapsulation based on wagmi), and wagmi. To quickly build the Coin Pusher dapp, we will use the , , , and technology stack for development.

  • Nextjs: It is a React framework for building server-rendered and static websites and web applications.

  • Wagmi: It is a React Hooks library for Ethereum, designed to simplify interactions with the Ethereum blockchain and smart contracts. It supports over 40 out-of-the-box Ethereum features for accounts, wallets, contracts, transactions, signatures, ENS, and more. Wagmi also supports almost all wallets through its official connectors, EIP-6963 support, and extensible API.

  • Rainbow-kit: It is a React library that provides components to build a Connect Wallet UI with just a few lines of code. It supports many wallets, including Metamask, Rainbow, Coinbase Wallet, WalletConnect, and more. It is also highly customizable and comes with stunning built-in themes.

  • Viem: It is a low-level TypeScript interface for Ethereum that enables developers to interact with the Ethereum blockchain, including: JSON-RPC API abstraction, smart contract interactions, wallet and signature implementations, encoding/parsing utilities, and more. Wagmi Core is essentially a wrapper for Viem, providing multi-chain capabilities through Wagmi Config and automatic account management through connectors.

Final effect display

Create a Nextjs project

mkdir coin_pusher_app
cd coin_pusher_app
npx create-next-app ./

The default version of React used in the current Next.js project is version 19, but it is not very stable and may have compatibility issues with other dependencies. Therefore, I suggest changing the version of react and react-dom in package.json to "^18.2.0", and then re-running npm install.

Run the project in development mode.

npm run dev

The image below is the Next.js initialization page.

Project structure.

This is the project structure when initializing Next.js, but the contract folder is newly created, where is store the contract's ABI and contract address. You can copy the ABI and address of the contract you just deployed.

Install dependencies

npm install @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query react-hot-toast 

Import the RainbowKit configuration

Copy the following code snippet into the /context/Web3Provider.tsx file.

"use client";

import "@rainbow-me/rainbowkit/styles.css";
import {
  getDefaultConfig,
  RainbowKitProvider,
  lightTheme,
} from "@rainbow-me/rainbowkit";
import { WagmiProvider } from "wagmi";
import { sepolia } from "wagmi/chains";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";

const config = getDefaultConfig({
  appName: "Coin Pusher",
  projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
  chains: [sepolia],
  ssr: true,
});

const queryClient = new QueryClient();

export default function Web3Provider({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <RainbowKitProvider
          theme={lightTheme({
            accentColor: "#FF652B",
            accentColorForeground: "white",
          })}
        >
          {children}
        </RainbowKitProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}

Place the project ID in the .env folder.

NEXT_PUBLIC_PROJECT_ID=<YOUR_PROJECT_ID>

Import the Web3Provider configuration file into app/layout.tsx.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <Web3Provider>{children}</Web3Provider>
        <Toaster />
      </body>
    </html>
  );
}

The Toaster component comes from the react-hot-toast library, which is a lightweight, customizable notification library designed for React applications.

Retrieve the status from contact

Copy the following code snippet into the hooks/useGameStatus.ts file.

import { useEffect, useState } from "react";
import { useAccount, useReadContracts } from "wagmi";
import { abi } from "@/contract/contract-abi.json";
import { coinPusherAddress } from "@/contract/contract-address.json";
import { Address, formatUnits, zeroAddress } from "viem";

type GameStatus = {
  isCompleted: boolean;
  winner: Address;
  sum: string;
  target: string;
  state: number;
  myDeposit: string;
};

type GameStatusResult = {
  isCompleted: boolean;
  winner: Address;
  sum: bigint;
  target: bigint;
  state: number;
};

const wagmigotchiContract = {
  address: coinPusherAddress as `0x${string}`,
  abi: abi,
} as const;

export const useGameStatus = () => {
  const { address } = useAccount();
  const [gameStatus, setGameStatus] = useState<GameStatus>({
    isCompleted: false,
    winner: zeroAddress,
    sum: "0",
    target: "0",
    state: 0,
    myDeposit: "0",
  });

  const result = useReadContracts({
    contracts: [
      {
        ...wagmigotchiContract,
        functionName: "getGameStatus",
      },
      {
        ...wagmigotchiContract,
        functionName: "depositOf",
        args: [address],
      },
    ],
  });

  const data = result.data as [
    { result: GameStatusResult },
    { result: bigint | undefined }
  ];
  const { isLoading, refetch } = result;

  useEffect(() => {
    if (!data) return;
    setGameStatus({
      isCompleted: data[0].result.isCompleted,
      winner: data[0].result.winner,
      sum: formatUnits(data[0].result.sum, 6),
      target: formatUnits(data[0].result.target, 6),
      state: data[0].result.state,
      myDeposit: formatUnits(data[1].result || BigInt(0), 6),
    });
  }, [data]);

  return {
    gameStatus,
    isLoading,
    refetch,
  };
};

This is a custom Hook named useGameStatus, which is mainly used to retrieve the status of the Coin Pusher game.

Explanation:

  • useAccount: a Hook for getting current account.

  • gameStatues: This is a state declaration in a React Hook, using the useState Hook, which stores the state of the game.

  • useReadContracts: useReadContracts is a Hook provided by wagmi for querying multiple contract functions at once. We use it to query the getGameStatus and depositOf functions. wagmigotchiContract contains the contract's ABI and address, which are necessary information for calling smart contracts.

  • isLoading and refetch: isLoading and refetch are both data returned from the useReadContracts hook. isLoading is a boolean value indicating whether the data is currently loading, and refetch is a function used to fetch the data again.

  • useEffect: This is a React useEffect Hook, used to listen for and handle updates to game status data. When the data changes, the callback function within useEffect is executed, and within this callback function, the data from data is synchronized with gameStatus.

  • formatUnits: formatUnits is a utility function in viem (an Ethereum development library), primarily used to convert large on-chain values (usually represented in their smallest units) into a human-readable format.

Deposit function

Copy the following code snippet into the hooks/useGameDeposit.ts file.

import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
import { parseEther } from "viem";

import { coinPusherAddress } from "@/contract/contract-address.json";
import { abi } from "@/contract/contract-abi.json";
import toast from "react-hot-toast";

const wagmigotchiContract = {
  address: coinPusherAddress as `0x${string}`,
  abi: abi,
} as const;

const VALUE_BY_FLAG = ["0.001", "0.005", "0.01"];

export const useGameDeposit = () => {
  const { data: hash, isPending, writeContractAsync } = useWriteContract();

  const { isLoading: isConfirming, isSuccess: isConfirmed } =
    useWaitForTransactionReceipt({
      hash,
    });

  const handleDeposit = async (flag: number) => {
    try {
      const value = VALUE_BY_FLAG[flag];
      await writeContractAsync({
        ...wagmigotchiContract,
        functionName: "deposit",
        args: [flag],
        value: parseEther(value),
      });
      toast.loading("Confirming transaction...", { id: "hash" });
    } catch (error) {
      toast.error("Failed to send transaction");
      console.error(error);
    }
  };

  return {
    handleDeposit,
    isPending,
    isConfirming,
    isConfirmed,
  };
};

This is a custom Hook named useGameDeposit, which is used to handle the deposit functionality in blockchain games.

Explanation:

  • useWriteContract: useWriteContract is a Hook in Wagmi designed for executing write functions on a contract. It returns several properties, from which we extract attributes using destructuring: data, isPending, and writeContractAsync. data is the transaction hash returned after sending a transaction. isPending is a boolean value indicating whether there is an ongoing write operation (such as a transaction); if isPending returns true, it means the transaction has not yet been confirmed by the blockchain. writeContractAsync is an asynchronous function that allows you to send transactions to a smart contract.

  • useWriteForTransactionReceipt: useWaitForTransactionReceipt is also a hook provided by Wagmi. The role of this hook is to listen for a transaction receipt, ensuring that the transaction has been fully confirmed. isLoading is a property from the object returned by useWaitForTransactionReceipt, which is renamed to isConfirming here. This property is a boolean value indicating whether the transaction is still pending confirmation.

  • handleDeposit: This function accepts a flag parameter and retrieves the corresponding amount from the VALUE_BY_FLAG array. It then uses writeContractAsync to call the smart contract's deposit function, passing the flag as an argument with args: [flag]. The value: parseEther(value) converts the ETH amount to Wei units for the transaction.

CSS Style

Copy the following code snippet into the app/globals.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

@layer components {
  .dataPanel {
    @apply relative m-4 flex h-80 w-11/12 p-8 flex-col items-center justify-between gap-4 rounded-[38px] border border-black bg-white sm:m-8 sm:h-96 sm:w-[450px] lg:w-[400px] xl:h-[400px] 2xl:mx-10 2xl:h-[450px] 2xl:w-[500px] 2xl:border-2;
  }

  .dataItem {
    @apply flex justify-between w-full border-b border-black pb-2;
  }

  .depositButton {
    @apply w-full rounded-lg bg-[#FF652B] border 2xl:border-2 border-black p-4 text-white;
  }
}

Coin Pusher Interface

Copy the following code snippet into the app/page.tsx file.

In Next.js, app/page.tsx is a special file used to define the root page of the application, that is, the UI that is displayed when you visit the root URL of the application (usually /).

"use client";

import { ConnectButton } from "@rainbow-me/rainbowkit";
import { useGameStatus } from "@/hooks/useGameStatus";
import { zeroAddress } from "viem";
import { useGameDeposit } from "@/hooks/useGameDeposit";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";

export const POOL_STATE = [
  "Initial",
  "Launching",
  "Launched",
  "Completed",
  "Revealing",
  "Revealed",
];

const DEPOSIT_AMOUNTS = [
  { value: "0.001", flag: 0 },
  { value: "0.005", flag: 1 },
  { value: "0.01", flag: 2 },
];

const Home = () => {
  const [activeDepositFlag, setActiveDepositFlag] = useState<number | null>(
    null
  );
  const { gameStatus, isLoading, refetch: refetchGameStatus } = useGameStatus();
  const { handleDeposit, isPending, isConfirmed, isConfirming } =
    useGameDeposit();
  const isDepositing = isPending || isConfirming;

  const handleDepositClick = (flag: number) => {
    setActiveDepositFlag(flag);
    handleDeposit(flag);
  };

  useEffect(() => {
    if (isConfirmed) {
      toast.dismiss("hash");
      toast.success("Transaction confirmed!");
      refetchGameStatus();
      setActiveDepositFlag(null);
    }
  }, [isConfirmed, refetchGameStatus]);

  if (isLoading) {
    return;
  }

  return (
    <div className="flex min-h-svh w-screen items-center bg-white">
      <div className="mx-auto flex flex-col items-center gap-6 max-w-[1440px] px-4 md:px-10">
        {/* "ConnectButton" component from RainbowKit */}
        <ConnectButton />
        <div className="flex flex-col items-center rounded-[38px] border border-black bg-[#FF652B] shadow lg:mb-0 2xl:border-2">
          <div className="dataPanel">
            <p className="2x:mb-10 rounded-full border border-[#FF652B] px-5 py-2 text-[#FF652B] text-lg">
              pool state: {POOL_STATE[gameStatus.state]}
            </p>
            <div className="flex flex-col gap-4 w-full text-lg ">
              <div className="dataItem">
                <p>Pool Winner:</p>
                <p>
                  {gameStatus.winner === zeroAddress
                    ? "No Winner Yet"
                    : gameStatus.winner.slice(0, 6) +
                      "..." +
                      gameStatus.winner.slice(-4)}
                </p>
              </div>
              <div className="dataItem">
                <p>Pool Target:</p>
                <p>{gameStatus.target} ETH</p>
              </div>
              <div className="dataItem">
                <p>Pool Sum:</p>
                <p>{gameStatus.sum} ETH</p>
              </div>
              <div className="dataItem">
                <p>My Deposit:</p>
                <p>{gameStatus.myDeposit} ETH</p>
              </div>
            </div>
          </div>
          <div className="w-full px-4 sm:px-10 flex gap-6 pb-10 2xl:pb-20">
            {DEPOSIT_AMOUNTS.map(({ value, flag }) => (
              <button
                key={flag}
                className={`depositButton ${
                  activeDepositFlag === flag ? "bg-white text-black" : ""
                }`}
                onClick={() => handleDepositClick(flag)}
                disabled={isDepositing}
              >
                {activeDepositFlag === flag && isDepositing ? (
                  <span>Loading...</span>
                ) : (
                  `${value}ETH`
                )}
              </button>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

export default Home;

Run npm run dev, and then you will be able to view the page locally on port http://localhost:3000.

Explanation:

This code snippet imports the ConnectButton component from RainbowKit, which is primarily responsible for rendering the connect/disconnect wallet button.Then render the gameStatus data returned from the useGameStatus() hook onto the interface.

const handleDepositClick = (flag: number) => {
    setActiveDepositFlag(flag);
    handleDeposit(flag);
};

The handleDepositClick function receives a flag parameter and primarily accomplishes two operations:

  • setActiveDepositFlag(flag) is a state update function used to track the deposit button currently selected by the user.

  • handleDeposit(flag) is a function obtained from the useGameDeposit custom Hook, responsible for handling the actual deposit transaction logic.

 useEffect(() => {
    if (isConfirmed) {
      toast.dismiss("hash");
      toast.success("Transaction confirmed!");
      refetchGameStatus();
      setActiveDepositFlag(null);
    }
  }, [isConfirmed, refetchGameStatus]);

The purpose of this code is to refetch the game status using refetchGameStatus and reset the current active deposit state when the transaction is confirmed (isConfirmed=true).

Next, you can try playing the game.

Click the button corresponding to the amount you wish to deposit, and then the button you clicked will turn white and display 'Loading...'.

After a few seconds, the MetaMask wallet will pop up a transaction confirmation window. Click the confirm button to send a deposit transaction.

You can switch between multiple different wallet addresses to send deposit request until the total game amount reaches the encrypted target.

Note: In contract have limited each player to make only one deposit, and making multiple deposits will result in an error.

Congratulations, you have completed the development of the entire dapp.

Install RainbowKit and its peer dependencies, , , and .

Note: RainbowKit is a library.

This is the configuration file for . I have separated it and placed it in /context/Web3Provider.tsx to make the project structure clearer. Configure the required chains and generate the necessary connectors. You also need to set up a wagmi configuration.

Note: Every dApp that relies on WalletConnect now needs to obtain a projectId from . This is absolutely free and only takes a few minutes.

â„šī¸
🎲
wagmi
viem
@tanstack/react-query
React
RainbowKit
WalletConnect Cloud
NodeJS
Metamask
Next.js
Wagmi
Rainbow-kit
Viem