# Alchemy第六周教程

By [xiaoge](https://paragraph.com/@xiaoge) · 2022-11-12

---

开始部署
----

打开下方网站：

[https://gitpod.io/#github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-1-decentralized-staking](https://gitpod.io/#github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-1-decentralized-staking)

当我们进入的时候会提示我们登录，我们这里可以选择使用Github登录.

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

选择我们需要使用的工具基于浏览器的VScode，等待我们项目初始化完成。

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

当出现下面的页面则说明我们的项目初始化可以了。（等右上角的窗口显示一样的东西后）

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

因为我们后续需要去提交Git的地址，所以这里需要去新建一个分支，毕竟抄作业交的作业肯定是我们自己的。创建分支后会要求输入一个名字填一个就行

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

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

当我们点击publish branch的时候，页面右下角会提示我们如下所示，第一个问题，我们上面说了默认权限未开启，需要我们去开启，按照上面的开启即可，因为我们是clone人家的代码，作者是没有给你权限进行修改的，所以我们需要去fork作者的代码在自己的仓库，如下操作即可。（fork完成后会提示打开github提前打开也行如果fork失败可以看下面第一张图~第四张图开启权限）

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

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

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

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

下面这张图是创建fork

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

### 修改代码

当我们把代码fork到我们仓库之后下，下面就该修改我们的代码了，我们来到代码的地方进行修改。（看下图路径）

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

代码如下：（复制替换）

    // 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);
      }
    }
    

我们智能合约的后端已写好了，现在就一起来构建我们的前端吧。（需要修改代码的路径看下图）

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

代码如下（复制替换）

    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;
    

### 部署合约

当我们将上面的代码修改完毕后，后续可以我们将合约再次部署更新下。（最下面控制台第三个窗口）

在控制台输入下面的命令

    yarn deploy --reset
    

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

打开一个新的tab页面

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

提交到仓库

最后一步也是最重要的一步，将你修改的代码提交到你自己的github仓库。

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

我们去验证下，确实代码已经提交成功了：（如果没有提交成功注意看一下上图的Commit按钮,点击后过一会按钮会变再点击一次就好了）

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

表格里直接填仓库的链接就行

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

---

*Originally published on [xiaoge](https://paragraph.com/@xiaoge/alchemy-6)*
