# Alchemy的the Road to Web3-第二周文本教程 **Published by:** [KnowYourself](https://paragraph.com/@knowyourself-2/) **Published on:** 2022-10-14 **URL:** https://paragraph.com/@knowyourself-2/alchemy-the-road-to-web3 ## Content 今天我们一起来看看第二周任务。首先看看任务的目标是什么?在本教程中,将学习如何使用Alchemy、Hardhat、Ethers.js开发和部署去中心化的“给我买杯咖啡”智能合约,允许访问者发送(假)ETH 作为提示并留下好消息。话不多说,我们现在就开始第二周课程吧。 1.先决条件npm (npx) version 8.5.5node version 16.13.1如果你会js代码更好2.创建项目#创建一个项目目录并且初始化package.json mkdir BuyMeACoffee-contracts cd BuyMeACoffee-contracts npm init -y 创建目录创建目录 创建目录 创建目录# 使用hardhat生成项目框架 npx hardhat # 该命令建议执行,因为依赖可能有问题 npm install --save-dev hardhat@^2.9.3 @nomiclabs/hardhat-waffle@^2.0.0 ethereum-waffle@^3.0.0 chai@^4.2.0 @nomiclabs/hardhat-ethers@^2.0.0 ethers@^5.0.0 # 当你创建成功后目录如下所示: . ├── README.md ├── contracts ├── hardhat.config.js ├── node_modules ├── package-lock.json ├── package.json ├── scripts └── test contracts- 您的智能合约所在的文件夹 在这个项目中,我们将只创建一个,来组织我们的逻辑BuyMeACoffee scripts- 您的安全帽 javscript 脚本所在的文件夹 我们将编写逻辑deploy 示例脚本buy-coffee 和一个兑现我们小费的脚本withdraw hardhat.config.js- 带有solidity版本和部署设置的配置文件 3.开始开发项目 我们可以使用任意的vim方式打开上面创建的项目,这里就用VScode了,如果有需要的可以去官网下载我们可以看到生成的目录已经有了一个合约,我们只需要去替换即可,首先将合约文件名替换为 BuyMeACoffee.sol 同时将合约内容替换成下面的。//SPDX-License-Identifier: Unlicense // contracts/BuyMeACoffee.sol pragma solidity ^0.8.0; // Switch this to your own contract address once deployed, for bookkeeping! contract BuyMeACoffee { // 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)); } } 替换后的合约替换后的合约 替换后的合约 4.测试部署合约 将scripts下面的deploy.js的内容替换为下面的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 BuyMeACoffee = await hre.ethers.getContractFactory("BuyMeACoffee"); const buyMeACoffee = await BuyMeACoffee.deploy(); // Deploy the contract. await buyMeACoffee.deployed(); console.log("BuyMeACoffee deployed to:", buyMeACoffee.address); // Check balances before the coffee purchase. const addresses = [owner.address, tipper.address, buyMeACoffee.address]; console.log("== start =="); await printBalances(addresses); // Buy the owner a few coffees. const tip = {value: hre.ethers.utils.parseEther("1")}; await buyMeACoffee.connect(tipper).buyCoffee("Carolina", "You're the best!", tip); await buyMeACoffee.connect(tipper2).buyCoffee("Vitto", "Amazing teacher", tip); await buyMeACoffee.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 buyMeACoffee.connect(owner).withdrawTips(); // Check balances after withdrawal. console.log("== withdrawTips =="); await printBalances(addresses); // Check out the memos. console.log("== memos =="); const memos = await buyMeACoffee.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); }); 当我们将上面的替换完毕后,通过命令行运行JSnpx hardhat run scripts/deploy.js 测试我们的合约,当你执行成功会有下面的现实BuyMeACoffee deployed to: 0x2720b4f7e22Ad1c269eee4e91aE2fD1F04c485F6 == start == Address 0 balance: 9999.998754619375 Address 1 balance: 10000.0 Address 2 balance: 0.0 == bought coffee == Address 0 balance: 9999.998754619375 Address 1 balance: 9998.999752893990255063 Address 2 balance: 3.0 == withdrawTips == Address 0 balance: 10002.998708719732606388 Address 1 balance: 9998.999752893990255063 Address 2 balance: 0.0 == memos == At 1660268820, Carolina (0x70997970C51812dc3A010C7d01b50e0d17dc79C8) said: "You're the best!" At 1660268821, Vitto (0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC) said: "Amazing teacher" At 1660268822, Kay (0x90F79bf6EB2c4f870365E785982E1f101E93b906) said: "I love my Proof of Knowledge" 5.使用 Alchemy 和 MetaMask 将 BuyMeACoffe.sol 智能合约部署到以太坊 Goerli 测试网 新建一个deploy01.js,内容如下:// scripts/deploy01.js const hre = require("hardhat"); async function main() { // We get the contract to deploy. const BuyMeACoffee = await hre.ethers.getContractFactory("BuyMeACoffee"); const buyMeACoffee = await BuyMeACoffee.deploy(); await buyMeACoffee.deployed(); console.log("BuyMeACoffee deployed to:", buyMeACoffee.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); }); 现在我们的项目整体结构就如下了:新的项目结构新的项目结构 运行我们刚才脚本:npx hardhat run scripts/deploy01.js 如果成功的话,你会看到下面这样的提示:BuyMeACoffee deployed to: 0x2720b4f7e22Ad1c269eee4e91aE2fD1F04c485F6 这里需要注意哦,当我们多次运行的话,你每次都会看到完全相同的部署地址,这是因为当你运行脚本时,Hardhat 工具使用的默认设置是本地开发网络,就在您的计算机上。它快速且具有确定性,非常适合进行一些快速的健全性检查。 但是,为了实际部署到在 Internet 上运行且节点遍布世界各地的测试网络,我们需要更改我们的 Hardhat 配置文件以提供选项。 6.修改hardhat.config.js 进行配置部署 首先我们将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_URL = process.env.GOERLI_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; /** * @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.4", networks: { goerli: { url: GOERLI_URL, accounts: [PRIVATE_KEY] } } }; # 安装dotenv npm install dotenv # 创建一个.env文件 touch .env # 将下面内容写入env中 GOERLI_URL=https://eth-goerli.alchemyapi.io/v2/<your api key> GOERLI_API_KEY=<your api key> PRIVATE_KEY=<your metamask api key> 此外,为了获得所需的环境变量,可以使用以下资源:GOERLI_URL- 注册一个帐户炼金术,创建一个 Ethereum -> Goerli 应用程序,并使用 HTTP URLGOERLI_API_KEY- 从您的同一个 Alchemy Ethereum Goerli 应用程序中,您可以获得 URL 的最后一部分,这将是您的 API KEYPRIVATE_KEY- 遵循这些来自 MetaMask 的说明导出您的私钥。项目新的结构项目新的结构 项目新的结构 项目新的结构 获取Goerli测试币,去下面网址获取测试币 运行脚本发布到测试网络npx hardhat run scripts/deploy01.js --network goerli 当你成功部署页面会显示::BuyMeACoffee-contracts paul$ npx hardhat run scripts/deploy01.js --network goerli Compiled 1 Solidity file successfully BuyMeACoffee deployed to: 0x2720b4f7e22Ad1c269eee4e91aE2fD1F04c485F6 验证下合约是否部署上测试网成功。 7.实现一个脚本withdraw 我们在刚才的脚本下面新建一个withdraw.js的脚本,内容如下:// scripts/withdraw.js const hre = require("hardhat"); const abi = require("../artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.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="你的合约地址"; 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 buyMeACoffee = 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, buyMeACoffee.address); console.log("current balance of contract: ", await getBalance(provider, buyMeACoffee.address), "ETH"); // Withdraw funds if there are funds to withdraw. if (contractBalance !== "0.0") { console.log("withdrawing funds..") const withdrawTxn = await buyMeACoffee.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); }); 在本地测试# 运行该命令在本地测试 npx hardhat run scripts/withdraw.js 如果没有报错你会有下面的提示:current balance of owner: 0.14511094798885063 ETH current balance of contract: 0.0 ETH no funds to withdraw! current balance of owner: 0.14511094798885063 ETH 8.使用 Replit 和 Ethers.js 构建前端 Buy Me A Coffee 网站 dapp 首先在Replit IDE将下面的仓库进行forkforkfork fork 在fork之后我们会来到自己的工作台,如下所示:将文件中写好的变量进行修改更新输入contractAddresspages/index.js将名称字符串更新为您自己的名字pages/index.js确保合同 ABI 与您的合同相匹配utils/BuyMeACoffee.json可以看到 contractAddress 变量已经填充了地址。修改成你自己部署的合约将复制过来的仓库的Albert修改成你想要的任意名字都可以将刚才在编译器生成的ABI复制到Replit中的utils/BuyMeACoffee.json运行项目运行项目! ## Publication Information - [KnowYourself](https://paragraph.com/@knowyourself-2/): Publication homepage - [All Posts](https://paragraph.com/@knowyourself-2/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@knowyourself-2): Subscribe to updates