如何构建“Buy Me a Coffee”DeFi dApp

发布于:2023-05-14 ⋅ 阅读:(427) ⋅ 点赞:(0)
🥸 本教程来自官网: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 帐户(在此免费注册!)

以下不是必需的,但非常有用:

现在让我们开始构建我们的智能合约!

编写 BuyMeACoffee.sol 智能合约

Github 参考链接:https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Contracts

如果你之前使用过类似于 OpenZeppelin WizardRemix 的工具,那么你已经准备好使用 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文件以及一些包含示例代码的文件夹,包括contractsscriptstest

要检查一切是否正常工作,请运行:

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合约开始。

  1. 将合约文件重命名为BuyMeACoffee.sol
  2. 用以下代码替换合约代码:
//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: 0xDBa03676a2fBb6711CB652beF5B7416A53c1421D

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));
    }
}

请花些时间阅读合同注释,看看能不能了解目前的情况!

以下是重点:

  • 当我们部署合同时,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() 函数中。注释的代码显示了脚本的流程:

  1. 获取我们将使用的示例帐户。
  2. 获取要部署的合约。
  3. 部署合约。
  4. 在购买咖啡之前检查余额。
  5. 给所有者买几杯咖啡。
  6. 在购买咖啡之后检查余额。
  7. 提款。
  8. 撤回后检查余额。
  9. 查看备忘录。

这个脚本测试了我们在智能合约中实现的所有功能!太棒了。

您还可以注意到我们正在进行有趣的调用,例如:

  • 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 StackExchangeAlchemy 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-ethershardhat-waffledotenv,我们的整个Hardhat项目都可以访问这些依赖项。
  • 我知道我们还没有介绍dotenv,这是一个重要的工具,我们稍后会谈到它。
  • process.env.GOERLI_URLprocess.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.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 = "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://files.readme.io/c0057db-Fork_repl.png

您还可以在此查看完整的网站代码:https://github.com/alchemyplatform/RTW3-Week2-BuyMeACoffee-Website

在fork repl之后,您会被带到一个IDE页面,您可以:

  • 查看一个Next.js Web应用程序的代码
  • 获得访问控制台、终端和README.md文件的预览的权限
  • 查看您dapp的热重新加载版本

它应该看起来像这样:

https://files.readme.io/517c1cf-albert.png

这个教程的这个部分将会快速而有趣——我们将更新一些变量,使其连接到我们在项目早期部署的智能合约,并且在网站上显示您自己的名字!

首先,让我们将所有东西连接并使其正常工作,然后我会向您解释每个部分的内容。

这里是我们需要进行的更改:

  1. 更新 pages/index.js 中的 contractAddress
  2. 在 pages/index.js 中更新名称字符串为您自己的名称
  3. 确保 utils/BuyMeACoffee.json 中的合约ABI与您的合约匹配

在 pages/index.js 中更新 contractAddress

您可以看到 contractAddress 变量已经填充了一个地址。这是一个我部署的示例合约,您可以使用它,但是如果您这样做...所有发送到您的网站的小费都将发送到我的地址 :)

您可以通过从我们之前部署 BuyMeACoffee.sol 智能合约时粘贴您的地址来解决此问题。

https://files.readme.io/9f1ee86-buya.sol.png

在pages/index.js中更新名称字符串为您自己的名字

现在这个网站上到处都是我的名字。查找所有使用“Albert”的地方,并将其替换为您的名字/匿名资料/ENS域,或者您希望人们称呼您的任何内容。

您可以使用“cmd + F”或“ctrl + F”查找所有实例以进行替换。

https://files.readme.io/d03249f-theGuy.png

确保utils/BuyMeACoffee.json中的合同ABI匹配

这也是一个关键的检查点,特别是当您稍后对智能合约进行更改(在本教程之后)时。

ABI是应用程序二进制接口,它只是一种让我们的前端代码知道可以在智能合约上调用哪些函数的花哨方法。 ABI在智能合约编译时生成在json文件中。您可以在智能合约文件夹中的路径artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.json找到它。

每当您更改您的智能合约代码并重新部署时,您的ABI也会更改。将其复制并粘贴到Replit文件中:utils/BuyMeACoffee.json

https://files.readme.io/1aaabe1-utils.png

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

https://files.readme.io/9ea1320-connect_wallet.png

Replit的精妙之处在于,一旦你的网站建好,你可以回到你的个人资料,找到Replit项目链接,然后将其发送给朋友,让他们访问你的小费页面。

现在让我们来逐步浏览一下网站和代码。你已经可以从上面的截图看出,当你第一次访问Dapp时,它会检查你是否已安装了MetaMask,并且你的钱包是否已连接到该网站。第一次访问时,你将无法连接,所以会出现一个按钮,要求你“连接你的钱包”。

当你点击“连接你的钱包”后,MetaMask的窗口会弹出,询问你是否要通过签署消息确认连接。这个消息签署不需要任何燃气费或成本。

一旦签名完成,网站将确认你的连接,你将能够看到咖啡表单,以及其他访问者留下的任何以前的备忘录。

https://files.readme.io/ae9a697-momo.png

砰!就是这样!这就是整个项目。请花一秒钟时间为自己鼓掌,并回顾一下你走过的旅程。

总结一下:

  • 我们使用 Hardhat 和 Ethers.js 编写、测试和部署了一个自定义的 Solidity 智能合约。
  • 我们使用 Alchemy 和 MetaMask 将智能合约部署到 Goerli 测试网络。
  • 我们实现了一个提款脚本,以便我们能够享受我们的劳动成果。
  • 我们使用 Ethers.js 来加载合约 ABI,将一个使用 Next.js、React 和 Replit 构建的前端网站连接到智能合约上。

这是很多的工作!

本文由mdnice多平台发布

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到