# 百亿项目Alchemy Road to Web3 第六周NFT获取教程

By [tantan](https://paragraph.com/@tantan) · 2022-09-20

---

2019年12月，Alchemy完成**1500万美元A轮融资**，资方为Pantera Capital，斯坦福大学，Coinbase，三星等。

2021年4月，Alchemy以5.05亿美元估值完成**8000万美元B轮融资**，Coatue和Addition领投，DFJ Growth、K5 Global、Chainsmokers（烟鬼组合）、演员Jared Leto和Glazer家族参投。

2021年10月，Alchemy以35亿美元估值完成**2.5亿美元C轮融资**，由a16z领投的。

2022年2月，Alchemy以102亿美元估值完成**2亿美元融资**，Lightspeed与Silver Lake领投。

**Alchemy是一个**背景强大、经费充足、踏实做事、没有发币的团队，这样的项目不刷，难道去刷土狗吗？

并且，Alchemy计划将新资金用于推广Web3采用，这方面的一些举措包括推出Web3 University，就是现在的 Road to Web3 活动，活动为期10周，每周一个NFT。看了下nft数量极少，估计由于任务难度大，很多小伙伴直接放弃，这样的项目若是空投，绝对是大毛。

手把手第六周教程开始：. 如何构建 Staking Dapp
------------------------------

![](https://storage.googleapis.com/papyrus_images/081c55c3caa0996c23be70700c21b811e52c94622d864795d88631326d38914c.png)

### step1 下载 git并安装

从git官网下载windows版本的git：[http://git-scm.com/downloads](http://git-scm.com/downloads)

![](https://storage.googleapis.com/papyrus_images/2b57dd981d5762006637235b2988df7786fdbc7fbc187c912b15ace6b3e3fddd.png)

一般使用默认设置即可：一路next，最后install，git安装完毕！

### step2 下载 Scaffold-Eth

1.在控制台输入git clone [https://github.com/scaffold-eth/scaffold-eth-challenges.git](https://github.com/scaffold-eth/scaffold-eth-challenges.git) challenge-1-decentralized-staking ，按回车，等一会下载完毕。

![](https://storage.googleapis.com/papyrus_images/842f532082f231afdf73d3460a7faf0916f4722fc9d0fb2f7fcfe3cf87b40e23.png)

2.控制台输入cd challenge-1-decentralized-staking，再按回车。

![](https://storage.googleapis.com/papyrus_images/8356072772b77da146cd3d22453af5dbad24acebb72e89f997d15e2be5bbeb23.png)

3.控制台输入git checkout challenge-1-decentralized-staking ，按回车。

![](https://storage.googleapis.com/papyrus_images/55b8a2ab3fc9f487da1514c779cb8e7d6970982115b7ccab3ee39cff730d0dfa.png)

4.控制台输入yarn install ，按回车，多等一会。

![](https://storage.googleapis.com/papyrus_images/f1ba1be3a59d4e103bbcb758b55914cb5e84d29f3aba0e5400bc861070ed1df7.png)

5.都装好后，在challenge-1-decentralized-staking这个文件夹中应该能看到这样的目录。

![](https://storage.googleapis.com/papyrus_images/57e1389c10653ff1af5603f31b183517641c469c6045cf6b195abbb3a36ba580.png)

注意：在本教程中，我们将主要致力于 `Staker.sol`和 `App.jsx`.这两个文件的代码编写。

![](https://storage.googleapis.com/papyrus_images/433d5ee87744d3bfc63e0974d312f2c34be8250ec88bd8c5145e1195950bd1cc.png)

![](https://storage.googleapis.com/papyrus_images/3cba1ceb65e549e08d0522cb36a7f19f08fa059070e9c30515a4465cdb4c277f.png)

### step3 设置环境

**注意，接下来用到三个单独的控制台，我开了三个控制台。**

1.在控制台输入yarn chain，启动您的安全帽后端。

![](https://storage.googleapis.com/papyrus_images/892d5b777cdf910aca55040ee7afcf8bf4f9c7c72ba86f75b4210ce1da59c4de.png)

2.再开一个控制台，cd进入那个文件夹，在控制台输入yarn deploy。

![](https://storage.googleapis.com/papyrus_images/e8712cd258561e5bb170fe1d2aabd4aef0772c14e828c4550213ec1f1ba8ce91.png)

3.再开一个控制台，cd进入那个文件夹，在控制台输入yarn start ，启动你的 React 前端。

![](https://storage.googleapis.com/papyrus_images/9f78580b1df51b0bff8978d9df031f195f89735973a75d061b4e90e794d73fa2.png)

然后我们就可以看到网页跳出了这个页面！！！

![](https://storage.googleapis.com/papyrus_images/158e5b34fcd0c8408d0a23d63a693cee7fb73665f3fef892bf79289cf018c8e6.png)

### step4 熟悉 Scaffold-Eth（只拿NFT，这步可以不管）

在我们的默认视图中，我们有两个选项卡—— `Staker UI`& `Debug Contracts`.

![](https://storage.googleapis.com/papyrus_images/8093eaae9ffd972244b63822a3ae25a557e56cdfd473f4bea703c9f7e2172ced.png)

1.往本地钱包发送测试币，先不要点那个连接！复制右上角的地址（这就是本地地址），然后粘贴到右下角，点击小飞机。

![](https://storage.googleapis.com/papyrus_images/cb62593a7749928f50dae83ea6ff83fd09a51b3e14038ea7c7e74c3f5fcd25c5.png)

2.可以看到下面有提示，并且左上角余额发生变化。

![](https://storage.googleapis.com/papyrus_images/f6227efaa469e2af3f53ba110d962e50155afebcebda1d7e5e6944e6c145837a.png)

3.或者这里也可以，点击send。充值本地钱包后，您将能够与您的合约进行交互！

![](https://storage.googleapis.com/papyrus_images/83f5f1d4c7e77fa11ae2318cc6241ef1efd68bdd316baf154a6409b9aac4e4a5.png)

### step5 深入研究 Solidity

1.打开vscode，点击open folder，找到你创建的那个文件夹，点击选择文件夹。

![](https://storage.googleapis.com/papyrus_images/3afda657107a3f0393f7c1d99d8a3b528acbe70ec905621ce4e32ad74536f50e.png)

![](https://storage.googleapis.com/papyrus_images/58898e7a7a1a5f258909c5a4449c5f06ab9261eab9955bcbac42b90dc326f7bf.png)

2.找到packages——hardhat——contracts——Staker.sol

![](https://storage.googleapis.com/papyrus_images/936652f1e78aeb89d8e98191cdf1b6f918464d9f7b63da48153ec1a6c9aef37f.png)

3.将下面的代码直接复制粘贴进去。（[**官方链接**](https://docs.alchemy.com/docs/how-to-build-a-staking-dapp)**对每一步都做了解释，很有意思，感兴趣的可以看一下每段代码对应的模块功能， 而不是单纯的粘贴最终代码哈哈，但是用官方代码的话，第78行的**rewardRatePerBlock**要改成**rewardRatePerSecond，下面这个是改好的）

    // SPDX-License-Identifier: MIT
    pragma solidity 0.8.4;
    
    import "hardhat/console.sol";
    import "./ExampleExternalContract.sol";
    
    contract Staker {
    
      ExampleExternalContract public exampleExternalContract;
    
      mapping(address => uint256) public balances;
      mapping(address => uint256) public depositTimestamps;
    
      uint256 public constant rewardRatePerSecond = 0.1 ether;
      uint256 public withdrawalDeadline = block.timestamp + 120 seconds;
      uint256 public claimDeadline = block.timestamp + 240 seconds;
      uint256 public currentBlock = 0;
    
      // Events
      event Stake(address indexed sender, uint256 amount);
      event Received(address, uint);
      event Execute(address indexed sender, uint256 amount);
    
      // Modifiers
      /*
      Checks if the withdrawal period has been reached or not
      */
      modifier withdrawalDeadlineReached( bool requireReached ) {
        uint256 timeRemaining = withdrawalTimeLeft();
        if( requireReached ) {
          require(timeRemaining == 0, "Withdrawal period is not reached yet");
        } else {
          require(timeRemaining > 0, "Withdrawal period has been reached");
        }
        _;
      }
    
      /*
      Checks if the claim period has ended or not
      */
      modifier claimDeadlineReached( bool requireReached ) {
        uint256 timeRemaining = claimPeriodLeft();
        if( requireReached ) {
          require(timeRemaining == 0, "Claim deadline is not reached yet");
        } else {
          require(timeRemaining > 0, "Claim deadline has been reached");
        }
        _;
      }
    
      /*
      Requires that the contract only be completed once!
      */
      modifier notCompleted() {
        bool completed = exampleExternalContract.completed();
        require(!completed, "Stake already completed!");
        _;
      }
    
      constructor(address exampleExternalContractAddress){
          exampleExternalContract = ExampleExternalContract(exampleExternalContractAddress);
      }
    
      // Stake function for a user to stake ETH in our contract
      function stake() public payable withdrawalDeadlineReached(false) claimDeadlineReached(false){
        balances[msg.sender] = balances[msg.sender] + msg.value;
        depositTimestamps[msg.sender] = block.timestamp;
        emit Stake(msg.sender, msg.value);
      }
    
      /*
      Withdraw function for a user to remove their staked ETH inclusive
      of both principal and any accrued interest
      */
      function withdraw() public withdrawalDeadlineReached(true) claimDeadlineReached(false) notCompleted{
        require(balances[msg.sender] > 0, "You have no balance to withdraw!");
        uint256 individualBalance = balances[msg.sender];
        uint256 indBalanceRewards = individualBalance + ((block.timestamp-depositTimestamps[msg.sender])*rewardRatePerSecond);
        balances[msg.sender] = 0;
    
        // Transfer all ETH via call! (not transfer) cc: https://solidity-by-example.org/sending-ether
        (bool sent, bytes memory data) = msg.sender.call{value: indBalanceRewards}("");
        require(sent, "RIP; withdrawal failed :( ");
      }
    
      /*
      Allows any user to repatriate "unproductive" funds that are left in the staking contract
      past the defined withdrawal period
      */
      function execute() public claimDeadlineReached(true) notCompleted {
        uint256 contractBalance = address(this).balance;
        exampleExternalContract.complete{value: address(this).balance}();
      }
    
      /*
      READ-ONLY function to calculate the time remaining before the minimum staking period has passed
      */
      function withdrawalTimeLeft() public view returns (uint256 withdrawalTimeLeft) {
        if( block.timestamp >= withdrawalDeadline) {
          return (0);
        } else {
          return (withdrawalDeadline - block.timestamp);
        }
      }
    
      /*
      READ-ONLY function to calculate the time remaining before the minimum staking period has passed
      */
      function claimPeriodLeft() public view returns (uint256 claimPeriodLeft) {
        if( block.timestamp >= claimDeadline) {
          return (0);
        } else {
          return (claimDeadline - block.timestamp);
        }
      }
    
      /*
      Time to "kill-time" on our local testnet
      */
      function killTime() public {
        currentBlock = block.timestamp;
      }
    
      /*
      \Function for our smart contract to receive ETH
      cc: https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function
      */
      receive() external payable {
          emit Received(msg.sender, msg.value);
      }
    
    }
    

### step6 进军前端

1.找到packages——react-app——src——App.jsx 。[**官方链接**](https://docs.alchemy.com/docs/how-to-build-a-staking-dapp)**对每一步都做了解释，很有意思，感兴趣的可以看一下每段代码对应的模块功能， 而不是单纯的粘贴最终代码哈哈**）

![](https://storage.googleapis.com/papyrus_images/0cf1bc8b565e7a275794f84b87f1ae5e2058861ac2c5c7adac8b605bd2ea3c5c.png)

2.将如下代码复制粘贴进去。

    import WalletConnectProvider from "@walletconnect/web3-provider";
    //import Torus from "@toruslabs/torus-embed"
    import WalletLink from "walletlink";
    import { Alert, Button, Col, Menu, Row, List, Divider } from "antd";
    import "antd/dist/antd.css";
    import React, { useCallback, useEffect, useState } from "react";
    import { BrowserRouter, Link, Route, Switch } from "react-router-dom";
    import Web3Modal from "web3modal";
    import "./App.css";
    import { Account, Address, Balance, Contract, Faucet, GasGauge, Header, Ramp, ThemeSwitch } from "./components";
    import { INFURA_ID, NETWORK, NETWORKS } from "./constants";
    import { Transactor } from "./helpers";
    import {
      useBalance,
      useContractLoader,
      useContractReader,
      useGasPrice,
      useOnBlock,
      useUserProviderAndSigner,
    } from "eth-hooks";
    import { useEventListener } from "eth-hooks/events/useEventListener";
    import { useExchangeEthPrice } from "eth-hooks/dapps/dex";
    // import Hints from "./Hints";
    import { ExampleUI, Hints, Subgraph } from "./views";
    
    import { useContractConfig } from "./hooks";
    import Portis from "@portis/web3";
    import Fortmatic from "fortmatic";
    import Authereum from "authereum";
    import humanizeDuration from "humanize-duration";
    
    const { ethers } = require("ethers");
    /*
        Welcome to 🏗 scaffold-eth !
    
        Code:
        https://github.com/austintgriffith/scaffold-eth
    
        Support:
        https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA
        or DM @austingriffith on Twitter or Telegram
    
        You should get your own Infura.io ID and put it in `constants.js`
        (this is your connection to the main Ethereum network for ENS etc.)
    
    
        🌏 EXTERNAL CONTRACTS:
        You can also bring in contract artifacts in `constants.js`
        (and then use the `useExternalContractLoader()` hook!)
    */
    
    /// 📡 What chain are your contracts deployed to?
    const targetNetwork = NETWORKS.localhost; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet)
    
    // 😬 Sorry for all the console logging
    const DEBUG = true;
    const NETWORKCHECK = true;
    
    // 🛰 providers
    if (DEBUG) console.log("📡 Connecting to Mainnet Ethereum");
    // const mainnetProvider = getDefaultProvider("mainnet", { infura: INFURA_ID, etherscan: ETHERSCAN_KEY, quorum: 1 });
    // const mainnetProvider = new InfuraProvider("mainnet",INFURA_ID);
    //
    // attempt to connect to our own scaffold eth rpc and if that fails fall back to infura...
    // Using StaticJsonRpcProvider as the chainId won't change see https://github.com/ethers-io/ethers.js/issues/901
    const scaffoldEthProvider = navigator.onLine
      ? new ethers.providers.StaticJsonRpcProvider("https://rpc.scaffoldeth.io:48544")
      : null;
    const poktMainnetProvider = navigator.onLine
      ? new ethers.providers.StaticJsonRpcProvider(
          "https://eth-mainnet.gateway.pokt.network/v1/lb/611156b4a585a20035148406",
        )
      : null;
    const mainnetInfura = navigator.onLine
      ? new ethers.providers.StaticJsonRpcProvider("https://mainnet.infura.io/v3/" + INFURA_ID)
      : null;
    // ( ⚠️ Getting "failed to meet quorum" errors? Check your INFURA_ID
    
    // 🏠 Your local provider is usually pointed at your local blockchain
    const localProviderUrl = targetNetwork.rpcUrl;
    // as you deploy to other networks you can set REACT_APP_PROVIDER=https://dai.poa.network in packages/react-app/.env
    const localProviderUrlFromEnv = process.env.REACT_APP_PROVIDER ? process.env.REACT_APP_PROVIDER : localProviderUrl;
    if (DEBUG) console.log("🏠 Connecting to provider:", localProviderUrlFromEnv);
    const localProvider = new ethers.providers.StaticJsonRpcProvider(localProviderUrlFromEnv);
    
    // 🔭 block explorer URL
    const blockExplorer = targetNetwork.blockExplorer;
    
    // Coinbase walletLink init
    const walletLink = new WalletLink({
      appName: "coinbase",
    });
    
    // WalletLink provider
    const walletLinkProvider = walletLink.makeWeb3Provider(`https://mainnet.infura.io/v3/${INFURA_ID}`, 1);
    
    // Portis ID: 6255fb2b-58c8-433b-a2c9-62098c05ddc9
    /*
      Web3 modal helps us "connect" external wallets:
    */
    const web3Modal = new Web3Modal({
      network: "mainnet", // Optional. If using WalletConnect on xDai, change network to "xdai" and add RPC info below for xDai chain.
      cacheProvider: true, // optional
      theme: "light", // optional. Change to "dark" for a dark theme.
      providerOptions: {
        walletconnect: {
          package: WalletConnectProvider, // required
          options: {
            bridge: "https://polygon.bridge.walletconnect.org",
            infuraId: INFURA_ID,
            rpc: {
              1: `https://mainnet.infura.io/v3/${INFURA_ID}`, // mainnet // For more WalletConnect providers: https://docs.walletconnect.org/quick-start/dapps/web3-provider#required
              42: `https://kovan.infura.io/v3/${INFURA_ID}`,
              100: "https://dai.poa.network", // xDai
            },
          },
        },
        portis: {
          display: {
            logo: "https://user-images.githubusercontent.com/9419140/128913641-d025bc0c-e059-42de-a57b-422f196867ce.png",
            name: "Portis",
            description: "Connect to Portis App",
          },
          package: Portis,
          options: {
            id: "6255fb2b-58c8-433b-a2c9-62098c05ddc9",
          },
        },
        fortmatic: {
          package: Fortmatic, // required
          options: {
            key: "pk_live_5A7C91B2FC585A17", // required
          },
        },
        // torus: {
        //   package: Torus,
        //   options: {
        //     networkParams: {
        //       host: "https://localhost:8545", // optional
        //       chainId: 1337, // optional
        //       networkId: 1337 // optional
        //     },
        //     config: {
        //       buildEnv: "development" // optional
        //     },
        //   },
        // },
        "custom-walletlink": {
          display: {
            logo: "https://play-lh.googleusercontent.com/PjoJoG27miSglVBXoXrxBSLveV6e3EeBPpNY55aiUUBM9Q1RCETKCOqdOkX2ZydqVf0",
            name: "Coinbase",
            description: "Connect to Coinbase Wallet (not Coinbase App)",
          },
          package: walletLinkProvider,
          connector: async (provider, _options) => {
            await provider.enable();
            return provider;
          },
        },
        authereum: {
          package: Authereum, // required
        },
      },
    });
    
    function App(props) {
      const mainnetProvider =
        poktMainnetProvider && poktMainnetProvider._isProvider
          ? poktMainnetProvider
          : scaffoldEthProvider && scaffoldEthProvider._network
          ? scaffoldEthProvider
          : mainnetInfura;
    
      const [injectedProvider, setInjectedProvider] = useState();
      const [address, setAddress] = useState();
    
      const logoutOfWeb3Modal = async () => {
        await web3Modal.clearCachedProvider();
        if (injectedProvider && injectedProvider.provider && typeof injectedProvider.provider.disconnect == "function") {
          await injectedProvider.provider.disconnect();
        }
        setTimeout(() => {
          window.location.reload();
        }, 1);
      };
    
      /* 💵 This hook will get the price of ETH from 🦄 Uniswap: */
      const price = useExchangeEthPrice(targetNetwork, mainnetProvider);
    
      /* 🔥 This hook will get the price of Gas from ⛽️ EtherGasStation */
      const gasPrice = useGasPrice(targetNetwork, "fast");
      // Use your injected provider from 🦊 Metamask or if you don't have it then instantly generate a 🔥 burner wallet.
      const userProviderAndSigner = useUserProviderAndSigner(injectedProvider, localProvider);
      const userSigner = userProviderAndSigner.signer;
    
      useEffect(() => {
        async function getAddress() {
          if (userSigner) {
            const newAddress = await userSigner.getAddress();
            setAddress(newAddress);
          }
        }
        getAddress();
      }, [userSigner]);
    
      // You can warn the user if you would like them to be on a specific network
      const localChainId = localProvider && localProvider._network && localProvider._network.chainId;
      const selectedChainId =
        userSigner && userSigner.provider && userSigner.provider._network && userSigner.provider._network.chainId;
    
      // For more hooks, check out 🔗eth-hooks at: https://www.npmjs.com/package/eth-hooks
    
      // The transactor wraps transactions and provides notificiations
      const tx = Transactor(userSigner, gasPrice);
    
      // Faucet Tx can be used to send funds from the faucet
      const faucetTx = Transactor(localProvider, gasPrice);
    
      // 🏗 scaffold-eth is full of handy hooks like this one to get your balance:
      const yourLocalBalance = useBalance(localProvider, address);
    
      // Just plug in different 🛰 providers to get your balance on different chains:
      const yourMainnetBalance = useBalance(mainnetProvider, address);
    
      const contractConfig = useContractConfig();
    
      // Load in your local 📝 contract and read a value from it:
      const readContracts = useContractLoader(localProvider, contractConfig);
    
      // If you want to make 🔐 write transactions to your contracts, use the userSigner:
      const writeContracts = useContractLoader(userSigner, contractConfig, localChainId);
    
      // EXTERNAL CONTRACT EXAMPLE:
      //
      // If you want to bring in the mainnet DAI contract it would look like:
      const mainnetContracts = useContractLoader(mainnetProvider, contractConfig);
    
      // If you want to call a function on a new block
      useOnBlock(mainnetProvider, () => {
        console.log(`⛓ A new mainnet block is here: ${mainnetProvider._lastBlockNumber}`);
      });
    
      // Then read your DAI balance like:
      const myMainnetDAIBalance = useContractReader(mainnetContracts, "DAI", "balanceOf", [
        "0x34aA3F359A9D614239015126635CE7732c18fDF3",
      ]);
    
      //keep track of contract balance to know how much has been staked total:
      const stakerContractBalance = useBalance(
        localProvider,
        readContracts && readContracts.Staker ? readContracts.Staker.address : null,
      );
      if (DEBUG) console.log("💵 stakerContractBalance", stakerContractBalance);
    
      const rewardRatePerSecond = useContractReader(readContracts, "Staker", "rewardRatePerSecond");
      console.log("💵 Reward Rate:", rewardRatePerSecond);
    
      // ** keep track of a variable from the contract in the local React state:
      const balanceStaked = useContractReader(readContracts, "Staker", "balances", [address]);
      console.log("💸 balanceStaked:", balanceStaked);
    
      // ** 📟 Listen for broadcast events
      const stakeEvents = useEventListener(readContracts, "Staker", "Stake", localProvider, 1);
      console.log("📟 stake events:", stakeEvents);
    
      const receiveEvents = useEventListener(readContracts, "Staker", "Received", localProvider, 1);
      console.log("📟 receive events:", receiveEvents);
    
      // ** keep track of a variable from the contract in the local React state:
      const claimPeriodLeft = useContractReader(readContracts, "Staker", "claimPeriodLeft");
      console.log("⏳ Claim Period Left:", claimPeriodLeft);
    
      const withdrawalTimeLeft = useContractReader(readContracts, "Staker", "withdrawalTimeLeft");
      console.log("⏳ Withdrawal Time Left:", withdrawalTimeLeft);
    
    
      // ** Listen for when the contract has been 'completed'
      const complete = useContractReader(readContracts, "ExampleExternalContract", "completed");
      console.log("✅ complete:", complete);
    
      const exampleExternalContractBalance = useBalance(
        localProvider,
        readContracts && readContracts.ExampleExternalContract ? readContracts.ExampleExternalContract.address : null,
      );
      if (DEBUG) console.log("💵 exampleExternalContractBalance", exampleExternalContractBalance);
    
      let completeDisplay = "";
      if (complete) {
        completeDisplay = (
          <div style={{padding: 64, backgroundColor: "#eeffef", fontWeight: "bold", color: "rgba(0, 0, 0, 0.85)" }} >
            -- 💀 Staking App Fund Repatriation Executed 🪦 --
            <Balance balance={exampleExternalContractBalance} fontSize={32} /> ETH locked!
          </div>
        );
      }
    
      /*
      const addressFromENS = useResolveName(mainnetProvider, "austingriffith.eth");
      console.log("🏷 Resolved austingriffith.eth as:", addressFromENS)
      */
    
      //
      // 🧫 DEBUG 👨🏻‍🔬
      //
      useEffect(() => {
        if (
          DEBUG &&
          mainnetProvider &&
          address &&
          selectedChainId &&
          yourLocalBalance &&
          yourMainnetBalance &&
          readContracts &&
          writeContracts &&
          mainnetContracts
        ) {
          console.log("_____________________________________ 🏗 scaffold-eth _____________________________________");
          console.log("🌎 mainnetProvider", mainnetProvider);
          console.log("🏠 localChainId", localChainId);
          console.log("👩‍💼 selected address:", address);
          console.log("🕵🏻‍♂️ selectedChainId:", selectedChainId);
          console.log("💵 yourLocalBalance", yourLocalBalance ? ethers.utils.formatEther(yourLocalBalance) : "...");
          console.log("💵 yourMainnetBalance", yourMainnetBalance ? ethers.utils.formatEther(yourMainnetBalance) : "...");
          console.log("📝 readContracts", readContracts);
          console.log("🌍 DAI contract on mainnet:", mainnetContracts);
          console.log("💵 yourMainnetDAIBalance", myMainnetDAIBalance);
          console.log("🔐 writeContracts", writeContracts);
        }
      }, [
        mainnetProvider,
        address,
        selectedChainId,
        yourLocalBalance,
        yourMainnetBalance,
        readContracts,
        writeContracts,
        mainnetContracts,
      ]);
    
      let networkDisplay = "";
      if (NETWORKCHECK && localChainId && selectedChainId && localChainId !== selectedChainId) {
        const networkSelected = NETWORK(selectedChainId);
        const networkLocal = NETWORK(localChainId);
        if (selectedChainId === 1337 && localChainId === 31337) {
          networkDisplay = (
            <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
              <Alert
                message="⚠️ Wrong Network ID"
                description={
                  <div>
                    You have <b>chain id 1337</b> for localhost and you need to change it to <b>31337</b> to work with
                    HardHat.
                    <div>(MetaMask -&gt; Settings -&gt; Networks -&gt; Chain ID -&gt; 31337)</div>
                  </div>
                }
                type="error"
                closable={false}
              />
            </div>
          );
        } else {
          networkDisplay = (
            <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}>
              <Alert
                message="⚠️ Wrong Network"
                description={
                  <div>
                    You have <b>{networkSelected && networkSelected.name}</b> selected and you need to be on{" "}
                    <Button
                      onClick={async () => {
                        const ethereum = window.ethereum;
                        const data = [
                          {
                            chainId: "0x" + targetNetwork.chainId.toString(16),
                            chainName: targetNetwork.name,
                            nativeCurrency: targetNetwork.nativeCurrency,
                            rpcUrls: [targetNetwork.rpcUrl],
                            blockExplorerUrls: [targetNetwork.blockExplorer],
                          },
                        ];
                        console.log("data", data);
    
                        let switchTx;
                        // https://docs.metamask.io/guide/rpc-api.html#other-rpc-methods
                        try {
                          switchTx = await ethereum.request({
                            method: "wallet_switchEthereumChain",
                            params: [{ chainId: data[0].chainId }],
                          });
                        } catch (switchError) {
                          // not checking specific error code, because maybe we're not using MetaMask
                          try {
                            switchTx = await ethereum.request({
                              method: "wallet_addEthereumChain",
                              params: data,
                            });
                          } catch (addError) {
                            // handle "add" error
                          }
                        }
    
                        if (switchTx) {
                          console.log(switchTx);
                        }
                      }}
                    >
                      <b>{networkLocal && networkLocal.name}</b>
                    </Button>
                  </div>
                }
                type="error"
                closable={false}
              />
            </div>
          );
        }
      } else {
        networkDisplay = (
          <div style={{ zIndex: -1, position: "absolute", right: 154, top: 28, padding: 16, color: targetNetwork.color }}>
            {targetNetwork.name}
          </div>
        );
      }
    
      const loadWeb3Modal = useCallback(async () => {
        const provider = await web3Modal.connect();
        setInjectedProvider(new ethers.providers.Web3Provider(provider));
    
        provider.on("chainChanged", chainId => {
          console.log(`chain changed to ${chainId}! updating providers`);
          setInjectedProvider(new ethers.providers.Web3Provider(provider));
        });
    
        provider.on("accountsChanged", () => {
          console.log(`account changed!`);
          setInjectedProvider(new ethers.providers.Web3Provider(provider));
        });
    
        // Subscribe to session disconnection
        provider.on("disconnect", (code, reason) => {
          console.log(code, reason);
          logoutOfWeb3Modal();
        });
      }, [setInjectedProvider]);
    
      useEffect(() => {
        if (web3Modal.cachedProvider) {
          loadWeb3Modal();
        }
      }, [loadWeb3Modal]);
    
      const [route, setRoute] = useState();
      useEffect(() => {
        setRoute(window.location.pathname);
      }, [setRoute]);
    
      let faucetHint = "";
      const faucetAvailable = localProvider && localProvider.connection && targetNetwork.name.indexOf("local") !== -1;
    
      const [faucetClicked, setFaucetClicked] = useState(false);
      if (
        !faucetClicked &&
        localProvider &&
        localProvider._network &&
        localProvider._network.chainId === 31337 &&
        yourLocalBalance &&
        ethers.utils.formatEther(yourLocalBalance) <= 0
      ) {
        faucetHint = (
          <div style={{ padding: 16 }}>
            <Button
              type="primary"
              onClick={() => {
                faucetTx({
                  to: address,
                  value: ethers.utils.parseEther("0.01"),
                });
                setFaucetClicked(true);
              }}
            >
              💰 Grab funds from the faucet ⛽️
            </Button>
          </div>
        );
      }
    
      return (
        <div className="App">
          {/* ✏️ Edit the header and change the title to your project name */}
          <Header />
          {networkDisplay}
          <BrowserRouter>
            <Menu style={{ textAlign: "center" }} selectedKeys={[route]} mode="horizontal">
              <Menu.Item key="/">
                <Link
                  onClick={() => {
                    setRoute("/");
                  }}
                  to="/"
                >
                  Staker UI
                </Link>
              </Menu.Item>
              <Menu.Item key="/contracts">
                <Link
                  onClick={() => {
                    setRoute("/contracts");
                  }}
                  to="/contracts"
                >
                  Debug Contracts
                </Link>
              </Menu.Item>
            </Menu>
    
            <Switch>
              <Route exact path="/">
                {completeDisplay}
    
                <div style={{ padding: 8, marginTop: 16 }}>
                  <div>Staker Contract:</div>
                  <Address value={readContracts && readContracts.Staker && readContracts.Staker.address} />
                </div>
    
                <Divider />
    
                <div style={{ padding: 8, marginTop: 16 }}>
                  <div>Reward Rate Per Second:</div>
                  <Balance balance={rewardRatePerSecond} fontSize={64} /> ETH
                </div>
    
                <Divider />
    
                <div style={{ padding: 8, marginTop: 16, fontWeight: "bold" }}>
                  <div>Claim Period Left:</div>
                  {claimPeriodLeft && humanizeDuration(claimPeriodLeft.toNumber() * 1000)}
                </div>
    
                <div style={{ padding: 8, marginTop: 16, fontWeight: "bold"}}>
                  <div>Withdrawal Period Left:</div>
                  {withdrawalTimeLeft && humanizeDuration(withdrawalTimeLeft.toNumber() * 1000)}
                </div>
    
                <Divider />
    
                <div style={{ padding: 8, fontWeight: "bold"}}>
                  <div>Total Available ETH in Contract:</div>
                  <Balance balance={stakerContractBalance} fontSize={64} />
                </div>
    
                <Divider />
    
                <div style={{ padding: 8,fontWeight: "bold" }}>
                  <div>ETH Locked 🔒 in Staker Contract:</div>
                  <Balance balance={balanceStaked} fontSize={64} />
                </div>
    
                <div style={{ padding: 8 }}>
                  <Button
                    type={"default"}
                    onClick={() => {
                      tx(writeContracts.Staker.execute());
                    }}
                  >
                    📡 Execute!
                  </Button>
                </div>
    
                <div style={{ padding: 8 }}>
                  <Button
                    type={"default"}
                    onClick={() => {
                      tx(writeContracts.Staker.withdraw());
                    }}
                  >
                    🏧 Withdraw
                  </Button>
                </div>
    
                <div style={{ padding: 8 }}>
                  <Button
                    type={balanceStaked ? "success" : "primary"}
                    onClick={() => {
                      tx(writeContracts.Staker.stake({ value: ethers.utils.parseEther("0.5") }));
                    }}
                  >
                    🥩 Stake 0.5 ether!
                  </Button>
                </div>
    
                {/*
                    🎛 this scaffolding is full of commonly used components
                    this <Contract/> component will automatically parse your ABI
                    and give you a form to interact with it locally
                */}
    
                {/* uncomment for a second contract:
                <Contract
                  name="SecondContract"
                  signer={userProvider.getSigner()}
                  provider={localProvider}
                  address={address}
                  blockExplorer={blockExplorer}
                  contractConfig={contractConfig}
                />
                */}
              </Route>
              <Route path="/contracts">
                <Contract
                  name="Staker"
                  signer={userSigner}
                  provider={localProvider}
                  address={address}
                  blockExplorer={blockExplorer}
                  contractConfig={contractConfig}
                />
                <Contract
                  name="ExampleExternalContract"
                  signer={userSigner}
                  provider={localProvider}
                  address={address}
                  blockExplorer={blockExplorer}
                  contractConfig={contractConfig}
                />
              </Route>
            </Switch>
          </BrowserRouter>
    
          <ThemeSwitch />
    
          {/* 👨‍💼 Your account is in the top right with a wallet at connect options */}
          <div style={{ position: "fixed", textAlign: "right", right: 0, top: 0, padding: 10 }}>
            <Account
              address={address}
              localProvider={localProvider}
              userSigner={userSigner}
              mainnetProvider={mainnetProvider}
              price={price}
              web3Modal={web3Modal}
              loadWeb3Modal={loadWeb3Modal}
              logoutOfWeb3Modal={logoutOfWeb3Modal}
              blockExplorer={blockExplorer}
            />
            {faucetHint}
          </div>
    
          <div style={{ marginTop: 32, opacity: 0.5 }}>
            {/* Add your address here */}
            Created by <Address value={"Your...address"} ensProvider={mainnetProvider} fontSize={16} />
          </div>
    
          <div style={{ marginTop: 32, opacity: 0.5 }}>
            <a target="_blank" style={{ padding: 32, color: "#000" }} href="https://github.com/scaffold-eth/scaffold-eth">
              🍴 Fork me!
            </a>
          </div>
    
          {/* 🗺 Extra UI like gas price, eth price, faucet, and support: */}
          <div style={{ position: "fixed", textAlign: "left", left: 0, bottom: 20, padding: 10 }}>
            <Row align="middle" gutter={[4, 4]}>
              <Col span={8}>
                <Ramp price={price} address={address} networks={NETWORKS} />
              </Col>
    
              <Col span={8} style={{ textAlign: "center", opacity: 0.8 }}>
                <GasGauge gasPrice={gasPrice} />
              </Col>
              <Col span={8} style={{ textAlign: "center", opacity: 1 }}>
                <Button
                  onClick={() => {
                    window.open("https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA");
                  }}
                  size="large"
                  shape="round"
                >
                  <span style={{ marginRight: 8 }} role="img" aria-label="support">
                    💬
                  </span>
                  Support
                </Button>
              </Col>
            </Row>
    
            <Row align="middle" gutter={[4, 4]}>
              <Col span={24}>
                {
                  /*  if the local provider has a signer, let's show the faucet:  */
                  faucetAvailable ? (
                    <Faucet localProvider={localProvider} price={price} ensProvider={mainnetProvider} />
                  ) : (
                    ""
                  )
                }
              </Col>
            </Row>
          </div>
        </div>
      );
    }
    
    export default App;
    

3.再打开控制台，输入yarn deploy --reset，可以看到前端界面和刚才有细微差别。

![](https://storage.googleapis.com/papyrus_images/7da078f00a1ee774fd3a98a40383e942f12cc95fdfc0c87a17e0c36f5a3dcd69.png)

4.测试stake功能，点击stake，测试一下，据说正常部署都会提示取款时间已过。。。

![](https://storage.googleapis.com/papyrus_images/e34c62a6b9b6bd472d656d49c76c35e789682475a4c19ae77371ff08eb9638c9.png)

### step7 上传至github

1.登录github，点击New。

![](https://storage.googleapis.com/papyrus_images/cdbb9288df68a646a6176d8f37efebca416f0c55570903e0289653df0941172f.png)

2.这三个地方填一下,然后直接拉到最下面，点击Create repository。

![](https://storage.googleapis.com/papyrus_images/450a52205c38abf24f2d9180226d8206b0639430835e87f0a6e3b6c111a8e85e.png)

![](https://storage.googleapis.com/papyrus_images/3e19e7c5207820761b5c2e3edece0f9d379d7e7eb4c38e82d3a31fe01f16fd6c.png)

3.点击code，复制仓库地址备用。

![](https://storage.googleapis.com/papyrus_images/170fa690b31e1fe3278a761692dc61f79d57286a0477065052b8c7f40d3c7e5c.png)

4.**.接下来就到本地操作了，首先确保你已经成功安装Git这个软件，在电脑上找到你要上传到Github上面的那个项目文件夹，进入项目文件夹，单击鼠标右键，选择Git Bash Here,如下图所示。**

![](https://storage.googleapis.com/papyrus_images/fbcad83e1bd4e90ba83959f2ad2106217ffa8fff089a62f7f5b3a35da4cc40cb.png)

5.接下来输入如下代码（关键步骤），把github上面的仓库克隆到本地

git clone [https://github.com/qpc666/road-to-web3-06.git（https://github.com/qpc666/road-to-web3-06.git替换成你之前复制的地址），如图即可。](https://github.com/qpc666/road-to-web3-06.git%EF%BC%88https://github.com/qpc666/road-to-web3-06.git%E6%9B%BF%E6%8D%A2%E6%88%90%E4%BD%A0%E4%B9%8B%E5%89%8D%E5%A4%8D%E5%88%B6%E7%9A%84%E5%9C%B0%E5%9D%80%EF%BC%89%EF%BC%8C%E5%A6%82%E5%9B%BE%E5%8D%B3%E5%8F%AF%E3%80%82)

![](https://storage.googleapis.com/papyrus_images/aaf5b439c077d61f149777fe22c23fd810e3745ba0a9f786788f9303caeabf0f.png)

6.这个步骤以后你的本地项目文件夹下面就会多出个文件夹，该文件夹名即为你github上面的项目名，如图我多出了个road-to-web3-06文件夹，我们把本地项目文件夹下的所有文件（除了新多出的那个文件夹不用），其余都复制到那个新多出的文件夹下。（扔那里不管，去打把王者再回来差不多就复制完成了，电脑慢的去打把dota回来估计差不多了，毕竟时间要用在刀刃上）

![](https://storage.googleapis.com/papyrus_images/c40c5e7a9ce9bebd504aa3c968d58af02ab36dd2d2a5608fa3be5316143d6b26.png)

7.接着继续输入命令 cd road-to-web3-06，进入road-to-web3-06文件夹（road-to-web3-06是我建的仓库，这里应该改成你自己的仓库名字）

![](https://storage.googleapis.com/papyrus_images/69484e23ce090b99594b4c66a797f9f31c6b35c77f95cfccb9b6edab6b95dbfa.png)

 8.接下来依次输入以下代码即可完成其他剩余操作：

git add .  （注：别忘记后面的 . ，此操作是把Test文件夹下面的文件都添加进来）

git commit  -m  "提交信息"  （注：“提交信息”里面换成你需要，也可以不管，这里可能会出现如下第一张图提示，按照提示输入git config --global user.email "[you@example.com](http://mailto:you@example.com/)"\\ git config --global [user.name](http://user.name/) "Your Name" 就可以了 ）

git push -u origin main   （注：此操作目的是把本地仓库push到github上面，此步骤需要你输入帐号和密码）

![](https://storage.googleapis.com/papyrus_images/ff2caf912a91b89383d34b7394010a2367ba869dadbe83d2b0e700ac3b899be8.png)

![](https://storage.googleapis.com/papyrus_images/58e929e4d70c5a4a5b7193eac8b614e027a92377303146c9c54b1f340f8d3d64.png)

9.第一次使用Git,会弹框要求登录，把这个码复制下来。

![](https://storage.googleapis.com/papyrus_images/1aaed81af381f5773756ecd9049697e5d31c8893d34cd3d4a39bdac07934ba95.png)

10.点第九步码下面那个链接，进去登录自己的git账号，然后把把复制粘贴在这里，授权就可以了。

![](https://storage.googleapis.com/papyrus_images/b1baed5f15d86b30259a1ba13f2a1570664380f77e870f90267ef0a49f3f58f6.png)

![](https://storage.googleapis.com/papyrus_images/dccb00d727f8eaf30e43350aae314dadde873858990b57c138202fe4f3e0fa6f.png)

11.复制这个链接即可。

![](https://storage.googleapis.com/papyrus_images/11e27fa9c669737254bfd73078a03368fdbdbd1b0d528bd1de404e229b0062fa.png)

### step8 提交项目

提交[链接](https://alchemyapi.typeform.com/roadtoweeksix)

### step9 领取NFT

前几周的内容在推特@greta0086看哦

---

*Originally published on [tantan](https://paragraph.com/@tantan/alchemy-road-to-web3-nft-2)*
