本文主要由六部分组成,分别为:准备、搭建环境、代码编译、代码测试、部署Dapp、发布与提交。
1、结合之前前序文档,创建好本周的github代码库以及同步到replit.com上。
如图,依次点击右上角github账号--“repositories”--填写“name”(随意)--“create repository”(切记勾选“add a README file”不然会是一个空库,无法导入replit)


2、登陆你的replit账号,依次点击“create”--“import”,然后选择上一步创建的代码库“magweek2”,再次点击“import from github”,完成代码的导入工作


3、进去项目后,如下图所示,点击“Done”

1、点击右边“Shell”,首先输入mkdir BuyABCACoffee-contracts并回车(ABC3个字母处,可改为任意名字,后文涉及到ABC处的均可替换为自己定义的名字,也可以不改~);
再次输入cd BuyABCACoffee-contracts并回车。

2、输入npm init -y并回车。

3、输入npx hardhat并回车。(中间大约需要点5次回车,就是那几个√的地方需要按回车才能继续)

大概成功安装后会如下图所示:

4、输入npm install --save-dev @nomiclabs/hardhat-waffle 并回车,等待安装完成。(中间各种warn不要管)

5、输入npm install dotenv并回车,等待安装完成。至此环境搭建步骤已经完成。

1、点开contracts目录,把Lock.sol重命名(rename)为BuyABCACoffee.sol(注意,文件名最好与前面创建的目录前缀一致)

2、点击刚才改名的“BuyABCACoffee”文件,将里面的内容替换为如下代码:
(特别注意ABC可以改成自己设置的名字,需与前面保持一致。 记得保存文件内容)
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
// Import this file to use console.log
import "hardhat/console.sol";
// Switch this to your own contract address once deployed, for bookkeeping!
// Example Contract Address on Goerli: 0xDBa03676a2fBb6711CB652beF5B7416A53c1421D
contract BuyABCACoffee {
// Event to emit when a Memo is created.
event NewMemo(
address indexed from,
uint256 timestamp,
string name,
string message
);
// Memo struct.
struct Memo {
address from;
uint256 timestamp;
string name;
string message;
}
// Address of contract deployer. Marked payable so that
// we can withdraw to this address later.
address payable owner;
// List of all memos received from coffee purchases.
Memo[] memos;
constructor() {
// Store the address of the deployer as a payable address.
// When we withdraw funds, we'll withdraw here.
owner = payable(msg.sender);
}
/**
* @dev fetches all stored memos
*/
function getMemos() public view returns (Memo[] memory) {
return memos;
}
/**
* @dev buy a coffee for owner (sends an ETH tip and leaves a memo)
* @param _name name of the coffee purchaser
* @param _message a nice message from the purchaser
*/
function buyCoffee(string memory _name, string memory _message) public payable {
// Must accept more than 0 ETH for a coffee.
require(msg.value > 0, "can't buy coffee for free!");
// Add the memo to storage!
memos.push(Memo(
msg.sender,
block.timestamp,
_name,
_message
));
// Emit a NewMemo event with details about the memo.
emit NewMemo(
msg.sender,
block.timestamp,
_name,
_message
);
}
/**
* @dev send the entire balance stored in this contract to the owner
*/
function withdrawTips() public {
require(owner.send(address(this).balance));
}
}
2、在scripts目录鼠标右键或者左键点击3个点处,选“Add file”,输入buycoffee.js回车来新建文件。

3、在“buycoffee.js”文件里输入以下代码。(同样,ABC可以修改,需与前面保持一致。若前面改了,此处修改会较多) const hre = require("hardhat");
// Returns the Ether balance of a given address.
async function getBalance(address) {
const balanceBigInt = await hre.ethers.provider.getBalance(address);
return hre.ethers.utils.formatEther(balanceBigInt);
}
// Logs the Ether balances for a list of addresses.
async function printBalances(addresses) {
let idx = 0;
for (const address of addresses) {
console.log(`Address ${idx} balance: `, await getBalance(address));
idx ++;
}
}
// Logs the memos stored on-chain from coffee purchases.
async function printMemos(memos) {
for (const memo of memos) {
const timestamp = memo.timestamp;
const tipper = memo.name;
const tipperAddress = memo.from;
const message = memo.message;
console.log(`At ${timestamp}, ${tipper} (${tipperAddress}) said: "${message}"`);
}
}
async function main() {
// Get the example accounts we'll be working with.
const [owner, tipper, tipper2, tipper3] = await hre.ethers.getSigners();
// We get the contract to deploy.
const BuyABCACoffee = await hre.ethers.getContractFactory("BuyABCACoffee");
const buyABCACoffee = await BuyABCACoffee.deploy();
// Deploy the contract.
await buyABCACoffee.deployed();
console.log("BuyABCACoffee deployed to:", buyABCACoffee.address);
// Check balances before the coffee purchase.
const addresses = [owner.address, tipper.address, buyABCACoffee.address];
console.log("== start ==");
await printBalances(addresses);
// Buy the owner a few coffees.
const tip = {value: hre.ethers.utils.parseEther("1")};
await buyABCACoffee.connect(tipper).buyCoffee("Carolina", "You're the best!", tip);
await buyABCACoffee.connect(tipper2).buyCoffee("Vitto", "Amazing teacher", tip);
await buyABCACoffee.connect(tipper3).buyCoffee("Kay", "I love my Proof of Knowledge", tip);
// Check balances after the coffee purchase.
console.log("== bought coffee ==");
await printBalances(addresses);
// Withdraw.
await buyABCACoffee.connect(owner).withdrawTips();
// Check balances after withdrawal.
console.log("== withdrawTips ==");
await printBalances(addresses);
// Check out the memos.
console.log("== memos ==");
const memos = await buyABCACoffee.getMemos();
printMemos(memos);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
完成后如下图所示:

4、点击deploy.js文件,将原文替换成以下代码。

// scripts/deploy.js
const hre = require("hardhat");
async function main() {
// We get the contract to deploy.
const BuyABCACoffee = await hre.ethers.getContractFactory("BuyABCACoffee");
const buyABCACoffee = await BuyABCACoffee.deploy();
await buyABCACoffee.deployed();
console.log("BuyABCACoffee deployed to:", buyABCACoffee.address);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
5、在scripts目录左键点击右侧3个点处,选“Add file”,新建withdraw.js文件,并回车。

6、在上一步新建的“withdraw.js”文件里,输入以下代码。
// scripts/withdraw.js
const hre = require("hardhat");
const abi = require("../artifacts/contracts/BuyABCACoffee.sol/BuyABCACoffee.json");
async function getBalance(provider, address) {
const balanceBigInt = await provider.getBalance(address);
return hre.ethers.utils.formatEther(balanceBigInt);
}
async function main() {
// Get the contract that has been deployed to Goerli.
const contractAddress="0x314Cb061D81759F339c6F026c86D09Ad528A31b5";
const contractABI = abi.abi;
// Get the node connection and wallet connection.
const provider = new hre.ethers.providers.AlchemyProvider("goerli", process.env.GOERLI_API_KEY);
// Ensure that signer is the SAME address as the original contract deployer,
// or else this script will fail with an error.
const signer = new hre.ethers.Wallet(process.env.PRIVATE_KEY, provider);
// Instantiate connected contract.
const buyABCACoffee = new hre.ethers.Contract(contractAddress, contractABI, signer);
// Check starting balances.
console.log("current balance of owner: ", await getBalance(provider, signer.address), "ETH");
const contractBalance = await getBalance(provider, buyABCACoffee.address);
console.log("current balance of contract: ", await getBalance(provider, buyABCACoffee.address), "ETH");
// Withdraw funds if there are funds to withdraw.
if (contractBalance !== "0.0") {
console.log("withdrawing funds..")
const withdrawTxn = await buyABCACoffee.withdrawTips();
await withdrawTxn.wait();
} else {
console.log("no funds to withdraw!");
}
// Check ending balance.
console.log("current balance of owner: ", await getBalance(provider, signer.address), "ETH");
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
7、左键点击BuyABCACoffee-contracts右边三个点,选“Add file”,新建.env文件(注意,此文件最前面有小数点)。

8、先暂时将以下代码输入.env文件
GOERLI_URL=https://eth-goerli.alchemyapi.io/v2/ GOERLI_API_KEY= PRIVATE_KEY= 9、登陆alchemy账号并创建ETH-Goerli的API(如何创建见 前序文章 第4步) 点击VIEW KEY,然后复制下图所示API KEY,粘贴到上一步的GOERLI_API_KEY=后面;复制HTTPS,粘贴到上一步的GOERLI_URL=后面。 再去metamask钱包导出钱包私钥(如何导出见 前序文章 第5步,最好新建一个新钱包),将私钥粘贴到上一步的PRIVATE_KEY=后面。 10、点击hardhat.config.js文件,并将原文替换成以下代码: // hardhat.config.js require("@nomiclabs/hardhat-ethers"); require("@nomiclabs/hardhat-waffle"); require('dotenv').config(); // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more const GOERLI = process.env.GOERLI_URL; const PRIVATE = process.env.PRIVATE_KEY; //console.log(GOERLI); /** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.9", networks: { goerli: { url: GOERLI, accounts: [PRIVATE] } } }; 四、代码完成,开始项目测试 1、又回到右边栏了,点击“Shell”,若观察回到上一层目录了,请执行cd BuyABCACoffee-contracts (同样ABC为你应与前面你设置的名字一样**)**切换到子目录中。没有则不管。以后遇到都需要这么处理,即cd +文件名 。如下图: 然后输入npx hardhat run scripts/buycoffee.js并回车。(中间会遇到有一次”?直接回车,以后遇到该问题也是如此操作)如下图: 当运行完成后,看图可发现我们的控制台又回到了上一层目录,同理我们通过输入cd BuyABCACoffee-contracts回到子目录。 2、输入npx hardhat run scripts/deploy.js并回车。(中间会遇到有一次”?直接回车,以后遇到该问题也是如此操作) 当出现如下图:deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3,则代表运行成功。当然,这只是尝试部署,还没真正到链上。 3、输入npx hardhat run scripts/deploy.js --network goerli并回车。 若出现下图:INSUFFICIENT_FUNDS代码,则代表钱包每天Goerli网上的ETH。去https://www.goerlifaucet.com 并登陆Alchemy 帐户以获得一些免费的测试币。 正常成功的部署应该是如下图所示,会出现deployed to:后面的地址就是我们部署的合约地址,复制下来后续步骤会要用到。 这里有的朋友可能等很久都不会出现合约地址,可以试着按下回车,或者直接去自己的钱包查看链上是否有合约部署记录。 4、输入npx hardhat run scripts/withdraw.js并回车(中间可能会有一次回车)。 五、部署dapp 1、先回到上一层magweek2(你们命名的可能不一样),输入cd 并回车(如果已经回到上一层目录了,就不用管了),然后输入cd magweek2。 输入npx create-next-app buyabcacoffee-dapp,并回车 (此目录名可以自命名,后续需保持与此一致。中间可能有一次回车。 出现SUCCESS表示成功。 2、输入cd buyabcacoffee-dapp并回车,然后输入npm install --save ethers并回车。 3、安装成功后,左边栏不一定及时出现新目录,需要手动刷新。 (1)先点左边第二个按钮version control,直到出现红框出的buyabcacoffee即可。 (2)再点回第一个按钮file,等待更新目录,会等几分钟。 4、为了让dapp找到合约,需要把BuyABCACoffee产生的合约属性文件拷贝到dapp目录下。操作流程如下:(ABC如果前面有改,则是你们改的名字) (1)点开BuyABCACoffee-contracts目录,选择artifacts/contract/BuyABCACoffee.sol这个目录,选择其中叫BuyABCACoffee.json的文件,先复制其里面的内容到其他地方(比如新建一个记事本拷贝进去),点BuyABCACoffee.json文件右边的三个点,选“rename“,复制其文件名,如图 (2)点击buyabcacoffee-dapp目录右边的三个点,选择“Add folder“,新建utils文件夹。 (3)点击utils右边三个点,选择“Add file“,新建BuyABCACoffee.json文件,再将之前复制的内容拷贝到该文件,即完成。 5、打开pages目录,点击index.js文件,将原文用以下代码替换掉。 注意:①下面ABC几处同样需要和前面保持一致。②第19行代码“contractAddress”处填上第四大步第3点里面记录的合约地址。③名字(hymnmark有3处)可以改为任意名字。④@FinderTechnical处可以改成自己的twitter号。 import abi from '../utils/BuyABCACoffee.json'; import { ethers } from "ethers"; import Head from 'next/head' import Image from 'next/image' import React, { useEffect, useState } from "react"; import styles from '../styles/Home.module.css' export default function Home() { // Contract Address & ABI const contractAddress = "0x2a112B58765c93B0151C10D0Aa54a70698859676"; const contractABI = abi.abi; // Component state const [currentAccount, setCurrentAccount] = useState(""); const [name, setName] = useState(""); const [message, setMessage] = useState(""); const [memos, setMemos] = useState([]); const onNameChange = (event) => { setName(event.target.value); } const onMessageChange = (event) => { setMessage(event.target.value); } // Wallet connection logic const isWalletConnected = async () => { try { const { ethereum } = window; const accounts = await ethereum.request({ method: 'eth_accounts' }) console.log("accounts: ", accounts); if (accounts.length > 0) { const account = accounts[0]; console.log("wallet is connected! " + account); } else { console.log("make sure MetaMask is connected"); } } catch (error) { console.log("error: ", error); } } const connectWallet = async () => { try { const { ethereum } = window; if (!ethereum) { console.log("please install MetaMask"); } const accounts = await ethereum.request({ method: 'eth_requestAccounts' }); setCurrentAccount(accounts[0]); } catch (error) { console.log(error); } } const buyCoffee = async () => { try { const { ethereum } = window; if (ethereum) { const provider = new ethers.providers.Web3Provider(ethereum, "any"); const signer = provider.getSigner(); const buyABCACoffee = new ethers.Contract( contractAddress, contractABI, signer ); console.log("buying coffee..") const coffeeTxn = await buyABCACoffee.buyCoffee( name ? name : "anon", message ? message : "Enjoy your coffee!", { value: ethers.utils.parseEther("0.001") } ); await coffeeTxn.wait(); console.log("mined ", coffeeTxn.hash); console.log("coffee purchased!"); // Clear the form fields. setName(""); setMessage(""); } } catch (error) { console.log(error); } }; // Function to fetch all memos stored on-chain. const getMemos = async () => { try { const { ethereum } = window; if (ethereum) { const provider = new ethers.providers.Web3Provider(ethereum); const signer = provider.getSigner(); const buyABCACoffee = new ethers.Contract( contractAddress, contractABI, signer ); console.log("fetching memos from the blockchain.."); const memos = await buyABCACoffee.getMemos(); console.log("fetched!"); setMemos(memos); } else { console.log("Metamask is not connected"); } } catch (error) { console.log(error); } }; useEffect(() => { let buyABCACoffee; isWalletConnected(); getMemos(); // Create an event handler function for when someone sends // us a new memo. const onNewMemo = (from, timestamp, name, message) => { console.log("Memo received: ", from, timestamp, name, message); setMemos((prevState) => [ ...prevState, { address: from, timestamp: new Date(timestamp * 1000), message, name } ]); }; const { ethereum } = window; // Listen for new memo events. if (ethereum) { const provider = new ethers.providers.Web3Provider(ethereum, "any"); const signer = provider.getSigner(); buyABCACoffee = new ethers.Contract( contractAddress, contractABI, signer ); buyABCACoffee.on("NewMemo", onNewMemo); } return () => { if (buyABCACoffee) { buyABCACoffee.off("NewMemo", onNewMemo); } } }, []); return ( <div className={styles.container}> <Head> <title>Buy hymnmark a Coffee!</title> <meta name="description" content="Tipping site" /> <link rel="icon" href="/favicon.ico" /> </Head> <main className={styles.main}> <h1 className={styles.title}> Buy hymnmark a Coffee! </h1> {currentAccount ? ( <div> <form> <div> <label> Name </label> <br /> <input id="name" type="text" placeholder="anon" onChange={onNameChange} /> </div> <br /> <div> <label> Send hymnmark a message </label> <br /> <textarea rows={3} placeholder="Enjoy your coffee!" id="message" onChange={onMessageChange} required > </textarea> </div> <div> <button type="button" onClick={buyCoffee} > Send 1 Coffee for 0.001ETH </button> </div> </form> </div> ) : ( <button onClick={connectWallet}> Connect your wallet </button> )} </main> {currentAccount && (<h1>Memos received</h1>)} {currentAccount && (memos.map((memo, idx) => { return ( <div key={idx} style={{ border: "2px solid", "borderRadius": "5px", padding: "5px", margin: "5px" }}> <p style={{ "fontWeight": "bold" }}>"{memo.message}"</p> <p>From: {memo.name} at {memo.timestamp.toString()}</p> </div> ) }))} <footer className={styles.footer}> <a href="https://alchemy.com/?a=roadtoweb3weektwo" target="_blank" rel="noopener noreferrer" > Created by @FinderTechnical for Alchemy's Road to Web3 lesson two! </a> </footer> </div> ) } 6、开始准备运行dapp了。点击最上面Files右边的三个点,选择“show hidden files“,显示.replit的文件。 7、点击.replit,将原文用以下代码替换:(若之前目录名字是自定义的,请换成你们自定义的) ,最后点击上方绿色按钮“RUN” run = "npm --prefix=./buyabcacoffee-dapp run dev" entrypoint = './buyabcacoffee-dapp/pages/index.js' 8、最终出现如下图,即代表成功。我改过名字,所以显示是maggy。 点开右上角“open in a new tab”,可以试用下打赏功能。 9、点击“connect your wallet”连上钱包,进行打赏,然后可以查看钱包是否交易成功。至此所有任务步骤都已经完成,dapp已经完美运行。 10、切记!!!!!!!!删掉.env文件(env文件里有私钥),如下图。 六、发布与提交 1、发布,切记在发布钱删掉.env文件。发布没什么好说的,该填的框框填起来,就是无脑下一步,最后会有个网址保存下来作为最后的任务证明就行啦。 2、提交任务 Week 2: https://forms.gle/HeJrvT72mRcnasr99 打开该网址提交任务。 主要是提交repit项目地址即上一步保存的网址,类似于:https://replit.com/你的github名字/项目名字?v=1 这样子。 还有提交第四步第3点部署的合约地址,类似于:https://goerli.etherscan.io/address/合约地址。

