如何构建“Buy Me a Coffee”DeFi dApp
🥸 本教程来自官网:https://docs.alchemy.com/docs。对原文部分内容进行了修改。教程中所有实例经过本人实践,代码可见:https://github.com/ChuXiaoYi/web3Study
区块链技术令人惊叹,因为它使我们能够使用代码和软件编程货币。只需几行代码,就可以构建各种应用程序和协议,为世界各地的人们创造新的机会。
“Buy Me A Coffee”是一个流行的网站,创作者、教育家、娱乐者和各种人们用它来创建一个着陆页,任何人都可以发送一些钱作为他们的服务的感谢。然而,为了使用它,你必须有银行账户和信用卡。并非每个人都有!
建立在区块链之上的分散化应用程序的一个好处是,世界各地的任何人都可以使用一个以太坊钱包来访问该应用程序,任何人都可以在不到1分钟的时间内免费设置一个以太坊钱包。让我们看看如何利用它来获得优势!
在本教程中,您将学习如何开发和部署一个分散化的“Buy Me A Coffee”智能合约,允许访问者发送给您(虚假)以太作为小费并留下好的消息,使用Alchemy、Hardhat、Ethers.js和以太坊Goerli。
通过本教程,您将学习以下内容:
-
使用Hardhat开发环境构建、测试和部署智能合约。 -
连接您的MetaMask钱包到Goerli测试网络,使用Alchemy RPC端点。 -
从goerlifaucet.com获取免费的Goerli ETH。 -
使用Ethers.js与部署的智能合约进行交互。 -
使用Replit为您的分散化应用程序构建前端网站。
视频教程版本在此处: https://youtu.be/cxxKdJk55Lk
先决条件
为了准备本教程的其余部分,您需要拥有:
-
npm
(npx
) 版本 8.5.5 -
node
版本 16.13.1 -
一个 Alchemy 帐户( 在此免费注册!)
以下不是必需的,但非常有用:
-
一些熟悉 命令行 -
一些熟悉 JavaScript
现在让我们开始构建我们的智能合约!
编写 BuyMeACoffee.sol 智能合约
Github 参考链接:https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Contracts
如果你之前使用过类似于 OpenZeppelin Wizard 和 Remix 的工具,那么你已经准备好使用 Hardhat 了。
Hardhat 类似于开发环境和编码工具,但它更加可定制,并且从你自己计算机的命令行界面运行,而不是浏览器应用程序。
我们将使用 Hardhat 来:
-
生成项目模板 -
测试我们的智能合约代码 -
部署到 Goerli 测试网络
让我们开始吧!
打开你的终端并创建一个新目录。
mkdir BuyMeACoffee-contracts
cd BuyMeACoffee-contracts
在此目录中,我们想要启动一个新的npm
项目(默认设置即可):
npm init -y
这将为您创建一个名为package.json
的文件,其内容应如下所示:
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npm init -y
Wrote to /Users/chuxiaoyi/work/web3/web3Study/BuyMeACoffee-contracts/package.json:
{
"name": "buymeacoffee-contracts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
安装 Hardhat:
npm install --save-dev hardhat
现在我们创建一个示例项目:
npx hardhat
您应该会看到一个欢迎消息和可以执行的操作选项。选择Create a JavaScript project
:
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.14.0 👷
同意所有默认设置(项目根目录、添加.gitignore
文件、安装所有示例项目依赖项):
✔ What do you want to do? · Create a JavaScript project
✔ Hardhat project root: · /Users/chuxiaoyi/work/web3/web3Study/BuyMeACoffee-contracts
✔ Do you want to add a .gitignore? (Y/n) · y
✔ Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)? (Y/n) · y
Hardhat会为我们生成一个hardhat.config.js
文件以及一些包含示例代码的文件夹,包括contracts
、scripts
和test
。
要检查一切是否正常工作,请运行:
npx hardhat test
我们现在已经成功配置了我们的 hardhat 开发环境。
你的项目目录现在应该看起来像这样(我在使用 tree 进行可视化):
tree -C -L 1
.
├── README.md
├── contracts
├── hardhat.config.js
├── node_modules
├── package-lock.json
├── package.json
├── scripts
└── test
重要的文件夹和文件包括:
-
contracts
- 存放智能合约的文件夹-
在这个项目中,我们只会创建一个智能合约,来组织我们的 BuyMeACoffee
逻辑
-
-
scripts
- 存放 Hardhat JavaScript 脚本的文件夹-
我们将编写 deploy
逻辑 -
例如 buy-coffee
脚本 -
还有一个 withdraw
脚本来兑现我们的小费
-
-
hardhat.config.js
- 配置文件,包含 Solidity 版本和部署设置
现在使用任何代码编辑器打开项目文件夹!我喜欢使用 VSCode。

您会注意到通过Hardhat示例项目工具已经自动生成了许多文件。我们将会替换所有这些文件,从Lock.sol
合约开始。
-
将合约文件重命名为 BuyMeACoffee.sol
-
用以下代码替换合约代码:
//SPDX-License-Identifier: Unlicense// contracts/BuyMeACoffee.sol
pragma solidity ^0.8.9;// Switch this to your own contract address once deployed, for bookkeeping!
// Example Contract Address on Goerli: 0xDBa03676a2fBb6711CB652beF5B7416A53c1421Dcontract 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));}
}
请花些时间阅读合同注释,看看能不能了解目前的情况!
以下是重点:
-
当我们部署合同时, constructor
将负责部署的钱包地址保存在owner
变量中作为可付款地址。这对于以后我们想要提取合同收集的任何小费非常有用。 -
buyCoffee
函数是合同上最重要的函数。它接受两个字符串_name
和_message
,同时也接受以太币,由于payable
修饰符。它使用_name
和_message
输入创建一个在区块链上存储的Memo
结构体。-
当访客调用 buyCoffee
函数时,他们必须提交一些以太币,由于require(msg.value > 0)
语句。以太币随后将被保留在合同balance
中,直到提取。
-
-
memos
数组存储所有从咖啡购买中生成的Memo
结构体。 -
NewMemo
日志事件在每次购买咖啡时发出。这使我们能够从我们的前端网站上监听新的咖啡购买。 -
withdrawTips
是任何人都可以调用的函数,但只会发送资金到合同的最初部署者。-
address(this).balance
获取存储在合同上的以太币 -
owner.send(...)
是创建带有以太币的发送交易的语法 -
包裹所有内容的 require(...)
语句是为了确保如果出现任何问题,交易将被回滚,不会丢失任何东西 -
这就是我们得到 require(owner.send(address(this).balance))
的方式
-
拥有这个智能合同代码,我们现在可以编写一个脚本来测试我们的逻辑!
创建一个buy-coffee.js脚本来测试你的合约
在scripts
文件夹下,应该已经有一个已经填好的示例脚本deploy.js
。 让我们将该文件重命名为buy-coffee.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("0.01") };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);});
此时,您的项目目录应该长这样:

请随意花几分钟阅读脚本代码。在顶部定义了一些实用程序函数,以方便执行诸如获取钱包余额并将其打印出来等操作。
脚本的主要逻辑在 main()
函数中。注释的代码显示了脚本的流程:
-
获取我们将使用的示例帐户。 -
获取要部署的合约。 -
部署合约。 -
在购买咖啡之前检查余额。 -
给所有者买几杯咖啡。 -
在购买咖啡之后检查余额。 -
提款。 -
撤回后检查余额。 -
查看备忘录。
这个脚本测试了我们在智能合约中实现的所有功能!太棒了。
您还可以注意到我们正在进行有趣的调用,例如:
-
hre.waffle.provider.getBalance
-
hre.ethers.getContractFactory
-
hre.ethers.utils.parseEther
-
等等。
这些代码行是我们利用 Hardhat (hre) 开发环境以及 Ethers 和 Waffle SDK 插件的功能来访问读取区块链钱包账户余额、部署合约和格式化 Ether 加密货币值等功能。
在本教程中,我们不会过于深入地讨论这些代码,但您可以通过查阅 Hardhat 和 Ethers.js 文档来了解更多信息。
说了这么多,现在是时候玩乐了,让我们运行脚本:
npx hardhat run scripts/buy-coffee.js
你应该在终端看到类似于这样的输出:
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npx hardhat run scripts/buy-coffee.js
Compiled 1 Solidity file successfully
BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
== start ==
Address 0 balance: 9999.99857170375
Address 1 balance: 10000.0
Address 2 balance: 0.0
== bought coffee ==
Address 0 balance: 9999.99857170375
Address 1 balance: 9999.989752217303447058
Address 2 balance: 0.03
== withdrawTips ==
Address 0 balance: 10000.028525789500231956
Address 1 balance: 9999.989752217303447058
Address 2 balance: 0.0
== memos ==
At 1684043147, Carolina (0x70997970C51812dc3A010C7d01b50e0d17dc79C8) said: "You're the best!"
At 1684043148, Vitto (0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC) said: "Amazing teacher"
At 1684043149, Kay (0x90F79bf6EB2c4f870365E785982E1f101E93b906) said: "I love my Proof of Knowledge"
在脚本开始之初(即在部署合约后),请注意“0”地址有9999.99877086625个ETH。这是因为它作为一种预填充的hardhat地址之一,开始时有10k个ETH,但它必须花费少量ETH来部署到本地区块链。
在第二步“==bought coffee==
”中,地址1购买了一杯咖啡。其他两个未显示的钱包也购买了咖啡。总共购买了3杯咖啡,小费总额为0.03个ETH。您可以看到地址2(代表合约地址)持有0.03个ETH。
在“==withdrawTips==
”中调用“withdrawTips()
”函数后,合约回到0个ETH,并且原始的部署者,即地址0,现在已经赚了一些钱,持有10000.028525789500231956ETH。
我们开心吗?!?!你能想象一下你即将赚到的小费吗?我可以。
现在让我们实现一个孤立的部署脚本,以保持真正的部署简单,同时准备好部署到Goerli测试网络!
使用Alchemy和MetaMask将您的BuyMeACoffe.sol智能合约部署到以太坊Goerli测试网
让我们创建一个新文件scripts/deploy.js
,非常简单,只是为了将我们的合约部署到我们稍后选择的任何网络上(如果您还没有注意到,我们将选择Goerli)。
deploy.js
文件应如下所示:
// scripts/deploy.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);
});
为了审查项目结构,我们现在有一个智能合约和两个Hardhat脚本:

现在有了这个编码并保存的deploy.js
脚本,如果你运行以下命令:
npx hardhat run scripts/deploy.js
你将会看到一行被打印出来:
BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
有趣的是,如果您一遍又一遍地运行它,每次都会看到完全相同的部署地址:
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npx hardhat run scripts/deploy.js
BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npx hardhat run scripts/deploy.js
BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npx hardhat run scripts/deploy.js
BuyMeACoffee deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
为什么呢?这是因为当你运行脚本时,Hardhat工具默认使用本地开发网络,直接在你的电脑上运行。它快速而确定性高,非常适合快速进行一些基本检查。
然而,为了实际部署到运行在全球各地节点上的测试网络,我们需要更改我们的Hardhat配置文件以给我们提供这个选项。
这就是hardhat.config.json
文件的作用。
在我们深入研究之前,快速提醒一句:
-
📘配置很难!保护好你的秘密!
有很多小细节可能出错,而且事情总是在变化。最危险的是秘密值,例如你的MetaMask私钥和Alchemy URL。
如果你遇到问题,请查看Ethereum StackExchange、Alchemy Discord,或者谷歌你的错误信息。
而且,永远不要分享你的秘密!你的密钥,你的硬币!
当您打开您的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.18",
networks: {
goerli: {
url: GOERLI_URL,
accounts: [PRIVATE_KEY],
},
},
};
这里发生了一些事情:
-
在配置文件的顶部导入 hardhat-ethers
、hardhat-waffle
和dotenv
,我们的整个Hardhat项目都可以访问这些依赖项。 -
我知道我们还没有介绍 dotenv
,这是一个重要的工具,我们稍后会谈到它。 -
process.env.GOERLI_URL
和process.env.PRIVATE_KEY
是我们可以访问环境变量以在配置文件中使用而不暴露秘密值的方法。 -
在 modules.exports
内部,我们使用Solidity编译器版本0.8.18
。不同的编译器版本支持不同的功能和语法集,因此匹配此版本与我们的BuyMeACoffee.sol
智能合约顶部的pragma
声明非常重要。 -
如果您返回到该文件,您可以交叉检查语句 pragma solidity ^0.8.9;
。在这种情况下,即使数字不完全匹配,也可以,因为符号^
表示任何大于或等于0.8.9
的版本都可以使用。 -
还在 modules.exports
中,我们定义了一个包含一个名为goerli
的测试网络配置的networks
设置。现在在我们进行部署之前,我们需要确保安装最后一个工具,即dotenv
模块。正如它的名字所示,dotenv
帮助我们将.env
文件连接到我们项目的其余部分。让我们设置它。
安装 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>
你会注意到我没有泄漏任何我的机密。是的。安全第一。你可以把它放在那个文件里,只要你也有一个 .gitignore
来确保你不会意外地将该文件推送到版本控制。确保 .env
在你的 .gitignore
中列出。
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts
此外,为了获取所需的环境变量,您可以使用以下资源:
-
GOERLI_URL
- 在 Alchemy上注册账户,创建一个 Ethereum -> Goerli 应用程序,并使用 HTTP URL -
GOERLI_API_KEY
- 从您相同的 Alchemy Ethereum Goerli 应用程序中,您可以获取 URL 的最后一部分,那将是您的 API KEY -
PRIVATE_KEY
- 按照 MetaMask 的这些 说明导出您的私钥。
现在,已经安装了 dotenv
,并在您的 .env
文件中填写了内容,我们几乎准备好将其部署到 Goerli 测试网!
我们需要做的最后一件事是确保您拥有一些 Goerli ETH。这是一种虚假以太币,使您能够在 Goerli 测试网络上练习进行各种操作,这是构建以太坊应用程序的实践区域。这样,您就不必在以太坊主网上花费真钱。
请前往 https://www.goerlifaucet.com 并使用您的 Alchemy 账户登录以获取一些免费的测试以太币。
现在我们可以开始部署!
运行部署脚本,这次添加一个特殊标志来使用 Goerli 网络:
npx hardhat run scripts/deploy.js --network goerli
如果你在这里遇到任何错误,例如 Error HH8
,我强烈建议你在 Google、Stack Overflow 或 Ethereum Stackexchange 上搜索解决方案。当你的 hardhat.config.js
、.env
或你的 dotenv
模块设置不正确时,遇到这些问题是很常见的。
如果一切顺利,几秒钟后你应该能够在控制台看到你的合约地址:
BuyMeACoffee deployed to: 0xbDde17D0BEA683db1bDa89f26c5c8C786230525E
🎉 恭喜!🎉
您现在已经在Goerli测试网上部署了合约。您可以通过在此处粘贴您的地址来查看它:https://goerli.etherscan.io/

在我们继续本教程的前端网站(dapp)部分之前,让我们准备一个我们稍后需要使用的脚本,withdraw.js
脚本。
实现一个withdraw
脚本
稍后当我们发布我们的网站时,我们需要一种方式来收集我们的朋友和粉丝留给我们的所有精彩小费。我们可以编写另一个hardhat脚本来实现这个目的!
在scripts/withdraw.js
中创建一个文件。
// scripts/withdraw.jsconst 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 = "0xbDde17D0BEA683db1bDa89f26c5c8C786230525E";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);});
您的项目结构应该如下所示:

这个脚本最重要的部分是当我们调用 withdrawTips()
函数时,从合约余额中取出资金并发送到所有者的钱包中:
// Withdraw funds if there are funds to withdraw.if (contractBalance !== "0.0") {console.log("withdrawing funds..")const withdrawTxn = await buyMeACoffee.withdrawTips();await withdrawTxn.wait();}
如果合约中没有资金,我们会避免尝试提取以避免不必要地支付燃气费用。
运行脚本时,您将看到如下输出:
(base) ➜ BuyMeACoffee-contracts git:(main) ✗ npx hardhat run scripts/withdraw.js
current balance of owner: 3.164940181128555531 ETH
current balance of contract: 0.0 ETH
no funds to withdraw!
current balance of owner: 3.164940181128555531 ETH
请注意,这次我们没有添加-network goerli
标志,这是因为我们的脚本直接在逻辑中硬编码网络配置:
const provider = new hre.ethers.providers.AlchemyProvider(
"goerli",
process.env.GOERLI_API_KEY
);
太好了,现在我们有一种方法来提取合同的小费!
让我们进入这个项目的 dapp 部分,这样我们就可以与所有朋友分享我们的小费页面 :)
使用 Replit 和 Ethers.js 构建前端的 Buy Me A Coffee 网站 dapp
为了保持简洁和清晰,我们将使用一个快速启动演示项目的神奇工具,称为 Replit IDE。
访问我的示例项目,然后分叉它以创建您自己的副本以进行修改:https://replit.com/@thatguyintech/BuyMeACoffee-Solidity-DeFi-Tipping-app

您还可以在此查看完整的网站代码:https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Website
在fork repl之后,您会被带到一个IDE页面,您可以:
-
查看一个 Next.js
Web应用程序的代码 -
获得访问控制台、终端和README.md文件的预览的权限 -
查看您dapp的热重新加载版本
它应该看起来像这样:

这个教程的这个部分将会快速而有趣——我们将更新一些变量,使其连接到我们在项目早期部署的智能合约,并且在网站上显示您自己的名字!
首先,让我们将所有东西连接并使其正常工作,然后我会向您解释每个部分的内容。
这里是我们需要进行的更改:
-
更新 pages/index.js
中的contractAddress
-
在 pages/index.js
中更新名称字符串为您自己的名称 -
确保 utils/BuyMeACoffee.json
中的合约ABI与您的合约匹配
在 pages/index.js 中更新 contractAddress
您可以看到 contractAddress 变量已经填充了一个地址。这是一个我部署的示例合约,您可以使用它,但是如果您这样做...所有发送到您的网站的小费都将发送到我的地址 :)
您可以通过从我们之前部署 BuyMeACoffee.sol
智能合约时粘贴您的地址来解决此问题。

在pages/index.js中更新名称字符串为您自己的名字
现在这个网站上到处都是我的名字。查找所有使用“Albert”的地方,并将其替换为您的名字/匿名资料/ENS域,或者您希望人们称呼您的任何内容。
您可以使用“cmd + F”或“ctrl + F”查找所有实例以进行替换。

确保utils/BuyMeACoffee.json中的合同ABI匹配
这也是一个关键的检查点,特别是当您稍后对智能合约进行更改(在本教程之后)时。
ABI是应用程序二进制接口,它只是一种让我们的前端代码知道可以在智能合约上调用哪些函数的花哨方法。 ABI在智能合约编译时生成在json文件中。您可以在智能合约文件夹中的路径artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.json
找到它。
每当您更改您的智能合约代码并重新部署时,您的ABI也会更改。将其复制并粘贴到Replit文件中:utils/BuyMeACoffee.json

如果应用程序尚未运行,您可以转到 shell 并使用“npm run dev”启动本地服务器以测试您的更改。该网站应在几秒钟内加载:

Replit的精妙之处在于,一旦你的网站建好,你可以回到你的个人资料,找到Replit项目链接,然后将其发送给朋友,让他们访问你的小费页面。
现在让我们来逐步浏览一下网站和代码。你已经可以从上面的截图看出,当你第一次访问Dapp时,它会检查你是否已安装了MetaMask,并且你的钱包是否已连接到该网站。第一次访问时,你将无法连接,所以会出现一个按钮,要求你“连接你的钱包”。
当你点击“连接你的钱包”后,MetaMask的窗口会弹出,询问你是否要通过签署消息确认连接。这个消息签署不需要任何燃气费或成本。
一旦签名完成,网站将确认你的连接,你将能够看到咖啡表单,以及其他访问者留下的任何以前的备忘录。

砰!就是这样!这就是整个项目。请花一秒钟时间为自己鼓掌,并回顾一下你走过的旅程。
总结一下:
-
我们使用 Hardhat 和 Ethers.js 编写、测试和部署了一个自定义的 Solidity 智能合约。 -
我们使用 Alchemy 和 MetaMask 将智能合约部署到 Goerli 测试网络。 -
我们实现了一个提款脚本,以便我们能够享受我们的劳动成果。 -
我们使用 Ethers.js 来加载合约 ABI,将一个使用 Next.js、React 和 Replit 构建的前端网站连接到智能合约上。
这是很多的工作!
本文由 mdnice 多平台发布
相关文章:

如何构建“Buy Me a Coffee”DeFi dApp
🥸 本教程来自官网:https://docs.alchemy.com/docs。对原文部分内容进行了修改。教程中所有实例经过本人实践,代码可见:https://github.com/ChuXiaoYi/web3Study 区块链技术令人惊叹,因为它使我们能够使用代码和软件编…...

Redis 实战篇:巧用 Bitmap 实现亿级海量数据统计
目录 二值状态统计判断用户登陆态SETBIT 命令GETBIT 命令第一步,执行以下指令,表示用户已登录。第二步,检查该用户是否登陆,返回值 1 表示已登录。第三步,登出,将 offset 对应的 value 设置成 0。 用户每个…...

3 天,入门 TAURI 并开发一个跨平台 ChatGPT 客户端
TAURI 是什么 TAURI 是一个使用 Rust 编写的程序框架,它允许我们使用 Web 技术和 Rust 语言构建跨端应用。它提供了大量特性,例如系统通知、网络请求、全局快捷键、本地文件处理等,它们都可以在前端通过 JavaScript 便捷的调用。 TAURI 应用…...

14个最佳创业企业WordPress主题
要创建免费网站?从易服客建站平台免费开始 500M免费空间,可升级为20GB电子商务网站 创建免费网站 您网站的设计使您能够展示产品的独特卖点。通过正确的主题,您将能够解释为什么客户应该选择您的品牌而不是其他品牌。 在本文中࿰…...

MySQL基础(三十)PowerDesigner的使用
1 PowerDesigner的使用 PowerDesigner是一款开发人员常用的数据库建模工具,用户利用该软件可以方便地制作 数据流程图 、概念数据模型 、 物理数据模型,它几乎包括了数据库模型设计的全过程,是Sybase公司为企业建模和设计提供的一套完整的集…...

nginx 服务器总结
一. 负载均衡的作用有哪些? 1、转发功能 按照一定的算法【权重、轮询】,将客户端请求转发到不同应用服务器上,减轻单个服务器压力,提高 系统并发量。 2、故障移除 通过心跳检测的方式,判断应用服务器当前是否可以正常…...

基于Hebb学习的深度学习方法总结
基于Hebb学习的深度学习方法总结 0 引言1 前置知识1.1 Hebb学习规则1.2 Delta学习规则 2 SoftHebb学习算法2.1 WTA(Winner Take All)2.2 SoftHebb2.3 多层Hebb网络2.4 Hebb学习的性能测评 3 参考文献 0 引言 总所周知,反向传播算法(back-propagating, B…...

思科模拟器 | 访问控制列表ACL实现网段精准隔绝
文章目录 一、ACL工作原理二、ACL分类初步介绍三、标准ACL1、标准ACL的决策过程2、标通配符掩码关键字3、标准ACL网络拓扑4、标准ACL演示5、实战讲解 四、扩展ACL1、基础语法明细2、扩展ACL示例3、扩展ACL网络拓扑4、实战讲解 五、总结与提炼 一、ACL工作原理 ACL(A…...

Python os模块详解
1. 简介 os就是“operating system”的缩写,顾名思义,os模块提供的就是各种 Python 程序与操作系统进行交互的接口。通过使用os模块,一方面可以方便地与操作系统进行交互,另一方面页也可以极大增强代码的可移植性。如果该模块中相…...

Oracle PL/SQL基础语法学习13:比较运算符
系列文章目录 Oracle PL/SQL基础语法学习12:短路求值 Oracle PL/SQL基础语法学习13:比较运算符 Oracle PL/SQL基础语法学习14:BOOLEAN表达式 文章目录 系列文章目录Oracle PL/SQL基础语法学习13:比较运算符比较运算符介绍官方文档…...

金仓数据库适配记录
金仓数据库适配记录 人大金仓数据库管理系统KingbaseES(简称:金仓数据库或KingbaseES)是北京人大金仓信息技术股份有限公司自主研制开发的具有自主知识产权的通用关系型数据库管理系统。 金仓数据库主要面向事务处理类应用,兼顾各类数据分析类应用,可用做管理信息系统、…...

ElasticSearch 学习 ==ELK== 进阶
二、ElasticSearch 学习 ELK 进阶 (1)文档局部更新 我们也说过文档是不可变的——它们不能被更改,只能被替换。 update API必须遵循相同的规则。表面看来,我们似乎是局部更新了文档的位置,内部却是像我们之前说的一样…...

【数据结构 -- C语言】 双向带头循环链表的实现
目录 1、双向带头循环链表的介绍 2、双向带头循环链表的接口 3、接口实现 3.1 开辟结点 3.2 创建返回链表的头结点 3.3 判断链表是否为空 3.4 打印 3.5 双向链表查找 3.6 双向链表在pos的前面进行插入 3.6.1 头插 3.6.2 尾插 3.6.3 更新头插、尾插写法 3.7 双向链…...

自然语言处理与其Mix-up数据增强方法报告
自然语言处理与其Mix-up数据增强方法 1绪论1.课题背景与意义1.2国内外研究现状 2 自然语言经典知识简介2.1 贝叶斯算法2.2 最大熵模型2.3神经网络模型 3 Data Augmentation for Neural Machine Translation with Mix-up3.1 数据增强3.2 对于神经机器翻译的软上下文的数据增强3.…...

Vue(组件化编程:非单文件组件、单文件组件)
一、组件化编程 1. 对比传统编写与组件化编程(下面两个解释图对比可以直观了解) 传统组件编写:不同的HTML引入不同的样式和行为文件 组件方式编写:组件单独,复用率高(前提组件拆分十分细致) 理…...

【MATLAB数据处理实用案例详解(22)】——基于BP神经网络的PID参数整定
目录 一、问题描述二、算法仿真2.1 BP_PID参数整定初始化2.2 优化PID2.3 绘制图像 三、运行结果四、完整程序 一、问题描述 基于BP神经网络的PID控制的系统结构如下图所示: 考虑仿真对象,输入为r(k)1.0,输入层为4,隐藏层为5&…...

第11章 项目人力资源管理
文章目录 项目人力资源管理 过程11.2.1 编制项目人力资源计划的工具与技术(1)层次结构图(工作、组织、资源 分解结构)(2)矩阵图(责任分配矩阵,RAM)(3…...

07-Vue技术栈之(组件之间的通信方式)
目录 1、组件的自定义事件1.1 绑定自定义事件:1.1.1 第一种方式1.1.2 第二种方式1.1.3 自定义事件只触发一次 1.2 解绑自定义事件1.3绑定原生DOM事件1.4 总结 2、全局事件总线(GlobalEventBus)2.1 应用全局事件总线 3、 消息订阅与发布&#…...

度量学习Metirc Learning和基于负例的对比学习Contrastive Learning的异同点思考
参考:对比学习(Contrastive Learning):研究进展精要 - 知乎 参考:对比学习论文综述【论文精读】_哔哩哔哩_bilibili 参考:度量学习DML之Contrastive Loss及其变种_对比损失的变种_胖胖大海的博客-CSDN博客 参考&…...

3.编写油猴脚本之-helloword
3.编写油猴脚本之-helloword Start 通过上一篇文章的学习,我们安装完毕了油猴插件。今天我们来编写一个helloword的脚步,体验一下油猴。 1. 开始 点击油猴插件>添加新脚本 默认生成的脚本 // UserScript // name New Userscript // name…...

openwrt的openclash提示【更新失败,请确认设备闪存空间足够后再试】
网上搜索了一下,问题应该是出在“无法从网络下载内核更新包”或者“无法识别内核的版本号” 解决办法:手动下载(我是只搞了DEV内核就搞定了TUN和Meta没有动) --> 上传到路由器上 --> 解压缩 --> 回到openclash界面更新配…...

torch.nn.Module
它是所有的神经网络的根父类! 你的神经网络必然要继承 可以看一下这篇文章...

论文解析-基于 Unity3D 游戏人工智能的研究与应用
1.重写 AgentAction 方法 1.1 重写 AgentAction 方法 这段代码是一个重写了 AgentAction 方法的方法。以下是对每行代码解释: ①public override void AgentAction(float[] vectorAction) 这行代码声明了一个公共的、重写了父类的 AgentAction 方法的方法。它接受…...

6、Flutterr聊天界面网络请求
一、准备网络数据 1.1 数据准备工作 来到网络数据制造的网址,注册登录后,新建仓库,名为WeChat_flutter;点击进入该仓库,删掉左侧的示例接口,新建接口. 3. 接着点击右上角‘编辑’按钮,新建响应内容,类型为Array,一次生成50条 4. 点击chat_list左侧添加按钮,新建chat_list中的…...

Java 8 腰斩!Java 17 暴涨 430%!!(文末福利)
New Relic 最新发布了一份 “2023 年 Java 生态系统状况报告”,旨在提供有关当今 Java 生态系统状态的背景和见解。该报告基于从数百万个提供性能数据的应用程序中收集的数据,对生产中使用最多的版本、最受欢迎的 JDK 供应商、容器的兴起等多方面进行了调…...

如何手写一个支持H.265的高清播放器
概述 音视频编解码技术在当前的互联网行业中十分热门,特别是高清视频播放器的开发,其中包括4K、8K等超高清分辨率的播放器,具有极高的市场需求和广泛的应用场景。H265编码技术更是实现高清视频压缩的重要手段之一。如果想要掌握音视频编解码…...

Day 1 认识软件测试——(软件测试定义、目的、原则)
Day 1 认识软件测试——(软件测试定义、目的、原则) 文章目录 Day 1 认识软件测试——(软件测试定义、目的、原则)软件测试的定义软件测试的目的软件测试的经济学问题黑盒测试白盒测试软件测试原则小结所谓软件测试,就是一个过程或一系列过程,用来确定计算机代码完成了其…...

Docker Harbor
目录 一、Docker Harbor概述 1、Harbor的优势 2、Harbor知识点 3、Docker私有仓库架构 二、Harbor构建Docker私有仓库 1、环境配置 2、案例需求 3、部署docker-compose服务 4、部署harbor服务 5、启动harbor ① 访问 ② 添加项目并填写项目名称 ③ 通过127.0.0.1来…...

第三十四章 Unity人形动画(上)
在我们DirectX课程中,我们讲过一个模型最少拥有网格和材质,可以没有动画。游戏场景中的静态物体就可以是这样的模型,例如花草树木,建筑物等等,他们通过MeshRenderer就可以渲染。对于一个带有动画的FBX文件,…...

计算机图形学-GAMES101-7
引言 场景中有很多的三角形,如果实现可见性和遮挡呢? 一个简单的想法是,从远到近画,近处的物体自然会覆盖掉远处的物体,这种画法也叫画家算法。 但是实际绘制中物体的顺序是不容易确定的,比如如下图绘制…...