# Alchemy第六周教程 **Published by:** [xiaoge](https://paragraph.com/@xiaoge/) **Published on:** 2022-11-12 **URL:** https://paragraph.com/@xiaoge/alchemy-6 ## Content 开始部署 打开下方网站: https://gitpod.io/#github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-1-decentralized-staking 当我们进入的时候会提示我们登录,我们这里可以选择使用Github登录. 选择我们需要使用的工具基于浏览器的VScode,等待我们项目初始化完成。 当出现下面的页面则说明我们的项目初始化可以了。(等右上角的窗口显示一样的东西后) 因为我们后续需要去提交Git的地址,所以这里需要去新建一个分支,毕竟抄作业交的作业肯定是我们自己的。创建分支后会要求输入一个名字填一个就行 当我们点击publish branch的时候,页面右下角会提示我们如下所示,第一个问题,我们上面说了默认权限未开启,需要我们去开启,按照上面的开启即可,因为我们是clone人家的代码,作者是没有给你权限进行修改的,所以我们需要去fork作者的代码在自己的仓库,如下操作即可。(fork完成后会提示打开github提前打开也行如果fork失败可以看下面第一张图~第四张图开启权限) 下面这张图是创建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; 部署合约 当我们将上面的代码修改完毕后,后续可以我们将合约再次部署更新下。(最下面控制台第三个窗口) 在控制台输入下面的命令 yarn deploy --reset 打开一个新的tab页面 提交到仓库 最后一步也是最重要的一步,将你修改的代码提交到你自己的github仓库。 我们去验证下,确实代码已经提交成功了:(如果没有提交成功注意看一下上图的Commit按钮,点击后过一会按钮会变再点击一次就好了) 表格里直接填仓库的链接就行 ## Publication Information - [xiaoge](https://paragraph.com/@xiaoge/): Publication homepage - [All Posts](https://paragraph.com/@xiaoge/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xiaoge): Subscribe to updates - [Twitter](https://twitter.com/Chrish9010): Follow on Twitter