# Alchemy的the Road to Web3第六周文本教程- 构建 Staking Dapp **Published by:** [Coinman.eth](https://paragraph.com/@coinman-eth/) **Published on:** 2022-08-16 **URL:** https://paragraph.com/@coinman-eth/alchemy-the-road-to-web3-staking-dapp ## Content 大家好,我是帝哥(推特:@CoinmanLabs),今天帝哥带大家一起来看看第六周的任务。 在开始我们今天的课程之前,帝哥再次给大家介绍一款在线编译工具,主要是前面的课程让大家使用本地的VScode,对于大家来说确实难度有点大,因为需要安装的事宜,第六周涉及到了Github,如果让大家安装不一定都可以安装成功,所以这里给大家介绍一款工具。 Gitpod是一个基于Chorom Cloud平台的在线IDE,它可以快速的启动一个基于大多数流行语言的开发环境,并且可以很顺畅的进行开发。是一款在线IDE能即时修改github代码。 如何使用Gitpod 在chrome浏览器中输入: https://gitpod.io/#github.com/banq/jdonframework 注意,github.com/banq/jdonframework代表你要参与修改的github项目。当键入以后,会提示你授权github账户给它,然后提示你安装chrome插件,这一切安装好的,一个IDE界面显示出来,里面的项目就是你要参与的项目源码,你可以更改代码,然后提交git,也可以进行pull request. 默认git push 是不上去的,它会提醒并跳到配置界面,我们需要,开放配置公开仓库的写权限。如下所示: 上面帝哥就是简单的给大家介绍下这款工具的使用,毕竟工欲善其事必先利其器,下面我们就正式开始今天的课程。 我们已经学会了如何从头开始使用 Hardhat,构建我们自己的前端,甚至编写 Solidity。 虽然所有这些技能对于希望建立坚实基础的开发人员都非常有价值,但也有一些工具可以帮助抽象环境设置和依赖项的一些复杂性,从而使开发人员能够更轻松地进行修补! 我们推荐的这些工具之一是脚手架-eth! Scaffold-eth 的核心是为以太坊上的快速原型设计提供现成的堆栈,使开发人员能够访问最先进的工具来快速学习/发布基于以太坊的 dApp。使用 Scaffold-eth 和 Alchemy,可以轻松地在区块链上合成和部署代码。如果不熟悉加密质押,最好将其概括为将加密资产锁定/存入 DeFi 协议或智能合约以赚取利息的过程。Staking 加密已成为许多 DeFi 协议的基石,并允许开发人员创建复杂的金融衍生产品。 虽然大多数 DeFi 质押合约都非常复杂,但我们将研究最基本的合约之一,以便我们学习关键概念。 我们将一起学习以下赌注的构建块: 使用 Scaffold-Eth 构建 一起破解前端 打造 Solidity “后端” 将 ETH 从钱包转移到智能合约,反之亦然 使用 Solidity 修饰符 1. 下载 Scaffold-Eth 在浏览器输入: https://gitpod.io/#github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-1-decentralized-staking,当我们进入的时候会提示我们登录,我们这里可以选择使用Github登录,没有的同学去申请注册一个。 选择我们需要使用的工具基于浏览器的VScode,等待我们项目初始化完成。 当出现下面的页面则说明我们的项目初始化可以了。 因为我们后续需要去提交Git的地址,所以这里需要去新建一个分支,毕竟抄作业交的作业肯定是我们自己的。帝哥这个写的就是Coinman。 当我们点击publish branch的时候,页面右下角会提示我们如下所示,第一个问题,我们上面说了默认权限未开启,需要我们去开启,按照上面的开启即可,因为我们是clone人家的代码,作者是没有给你权限进行修改的,所以我们需要去fork作者的代码在自己的仓库,如下操作即可。 当我们把代码fork到我们仓库之后下,下面就该修改我们的代码了,我们来到代码的地方进行修改。 代码如下: // 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); } } 我们智能合约的后端已写好了,现在就一起来构建我们的前端吧。 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 打开一个新的tab页面 我们就可以体验下这个自己制作的DAPP了。帝哥这里就不展开讲了,大家可以执行体验。 最后一步也是最重要的一步,将你修改的代码提交到你自己的github仓库。 我们去验证下,确实代码已经提交成功了: 最后就是填写表格了,仓库地址写你后来新建分支的地址哦: https://alchemyapi.typeform.com/roadtoweeksix 我是懂币帝,用技术带你领略区块链魅力,第一时间获取行业最新资讯: 推特:@CoinmanLabs 微信:CoinmanLabs(社群客服微信进群) ## Publication Information - [Coinman.eth](https://paragraph.com/@coinman-eth/): Publication homepage - [All Posts](https://paragraph.com/@coinman-eth/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@coinman-eth): Subscribe to updates