# 手把手教程 ——Alchemy Week2

By [RogerZ](https://paragraph.com/@rogerz-2) · 2022-08-15

---

Alchemy 教程目录
------------

[**Week1: How to Develop an NFT Smart Contract (ERC721) with Alchemy 教程**](https://mirror.xyz/0xF4b64958cF4BB506A24cE5e8b8dB5fd5a0f70315/PWrvfMYu4x-5UVd31BUyEH7toL4XlLvzdpwSKdg_rv8)

Week2: How to Build “Buy Me a Coffee“ Defi dapp with Alchemy 教程
===============================================================

[官方教程](https://docs.alchemy.com/docs/how-to-build-buy-me-a-coffee-defi-dapp)在此，也可以看 youtube 视频链接，不想看我啰嗦的可以移步。

[![]({{DOMAIN}}/editor/youtube/play.png)](https://www.youtube.com/watch?v=cxxKdJk55Lk)

### step0 环境配置

*   **系统环境：**
    
    Window10 X64
    
*   **编程环境：**
    
    nodeJS v16.15.0
    
    NPM v8.5.5
    
    nodeJS 安装回头写一篇教程吧，最简单的方法可以 google nvm，比去官网下载 nodeJS 的安装包好用多了。另外 windows 系统下需要配置环境变量，不明白的可以先百度。
    
*   **编程IDE：**
    
    VS Code
    
    VS code [下载官网](https://code.visualstudio.com/)，exe 的安装程序，按照提示即可
    

### step1 部署 web3 环境

1\. 新建文件夹 BuyMeACoffee-contracts，在文件夹内启动 cmd，`npm init -y`初始化项目，获得文件 package.json。

2\. 安装 hardhat，在 cmd 中输入

![看到上图代表安装完成](https://storage.googleapis.com/papyrus_images/c75d464d574f4efb5036feced074f869fe2350ce2e4bd1d46d7118c1954fd387.png)

看到上图代表安装完成

根据提示依次

选择Create a basic sample project

选择默认路径

3\. 复制下列代码，安装需要的库

    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
    

![出现这样的画面，代表全部安装完成了。图中的 WARN 不用在意，JS 特色之一](https://storage.googleapis.com/papyrus_images/f5689b292193632dd4f7f8d93ea653005e64bca2af451a1f6199b22b1fe88ac6.png)

出现这样的画面，代表全部安装完成了。图中的 WARN 不用在意，JS 特色之一

4\. 安装 dotenv 库

可以看下文件夹内的各文件，如果和以下树形图一致，那就表示环境部署没有问题了。

    .
    ├── README.md 
    ├── contracts 
    ├── hardhat.config.js 
    ├── node_modules 
    ├── package-lock.json 
    ├── package.json
    ├── scripts 
    └── test
    

**接下来的step3, step4两步，使用 VScode 打开文件夹进行编辑。**

### step3 编写智能合约

在 ./contracts/ 文件夹下新建 BuyMeACoffee.sol，复制以下代码，然后保存。

    //SPDX-License-Identifier: Unlicense
    pragma solidity ^0.8.0;
    
    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;
        }
    
        // List of all memos received from friends.
        Memo[] memos;
        
        // Address of contract deployer
        address payable owner;
    
        // Deploy logic
        constructor() {
            owner = payable(msg.sender);
        }
    
        /**
         * @dev buy a coffee for contract owner
         * @param _name name of the coffee buyer
         * @param _message a message from the coffee buyer
         */
        function buyCoffee(string memory _name, string memory _message) public payable {
            require(msg.value > 0, "can't buy coffee with 0 eth.");
    
            // add the memo to storage
            memos.push(Memo(
                msg.sender,
                block.timestamp,
                _name,
                _message
            ));
    
            // emit a log event when a new memo is created
            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));
        }
    
        /**
         * @dev retrieve all the memos received and stored on the blockchain
         */
        function getMemos() public view returns(Memo[] memory) {
            return memos;
        }
    }
    

### step4 部署智能合约

在 ./scripts/ 文件夹下新建 buy-coffee.js，复制以下代码，然后保存。

    // We require the Hardhat Runtime Environment explicitly here. This is optional
    // but useful for running the script in a standalone fashion through `node <script>`.
    //
    // When running the script with `npx hardhat run <script>` you'll find the Hardhat
    // Runtime Environment's members available in the global scope.
    const hre = require("hardhat");
    
    // Returns the Ether balance of a given address
    async function getBalance(address) {
      const balanceBigInt = await hre.waffle.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 puchases.
    async function printMemos(memos) {
      for (const memo of memos) {
        const timestamp = memo.timestamp;
        const tipper = memo.name;
        const tipperAddress = memo.address;
        const message = memo.message;
        console.log(`At ${timestamp}, ${tipper} (${tipperAddress}) said: "${message}"`);
      }
    }
    
    async function main() {
      // Get example accounts
      const [owner, tipper, tipper2, tipper3] = await hre.ethers.getSigners();
    
      // Get the contract to deploy & deploy.
      const BuyMeACoffee = await hre.ethers.getContractFactory("BuyMeACoffee");
      const buyMeACoffee = await BuyMeACoffee.deploy();
      await buyMeACoffee.deployed();
      console.log("BuyMeACoffee deployed to ", buyMeACoffee.address);
    
      // Check balances before the coffee purchases.
      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("Angela", "Hello from Angela!", tip);
      await buyMeACoffee.connect(tipper2).buyCoffee("Bernard", "Greeting from Bernard!", tip);
      await buyMeACoffee.connect(tipper3).buyCoffee("Charlie", "Hi from Charlie!", tip);
    
    
      // Check balances after the coffee purchases.
      console.log("== bought coffee ==");
      await printBalances(addresses);
    
      // Withdraw funds
      await buyMeACoffee.connect(owner).withdrawTips();
    
      // Check balances after withdraw.
      console.log("== withdrawTips ==");
      await printBalances(addresses);
    
      // Read all the memos left for the owner.
      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);
      });
    

在 cmd 中运行 `npx hardhat run scripts/buy-coffee.js`，返回

    BuyMeACoffee deployed to  0x5FbDB2315678afecb367f032d93F642f64180aa3
    == start ==
    Address 0 balance:  9999.998770013125
    Address 1 balance:  10000.0
    Address 2 balance:  0.0
    == bought coffee ==
    Address 0 balance:  9999.998770013125
    Address 1 balance:  9998.999752902345533407
    Address 2 balance:  3.0
    == withdrawTips ==
    Address 0 balance:  10002.998724110143833925
    Address 1 balance:  9998.999752902345533407
    Address 2 balance:  0.0
    == memos ==
    At 1660237686, Angela (undefined) said: "Hello from Angela!"
    At 1660237687, Bernard (undefined) said: "Greeting from Bernard!"
    At 1660237688, Charlie (undefined) said: "Hi from Charlie!"
    

这个是运行在本地网络的，简单理解就是没有上链，不消耗任何 gas。跑通没有报错的话，等一会就可以在正式的网络中成功部署了。

### step5 申请 Alchemy 帐号并配置小狐狸钱包

1\. 注册 alchemy 帐号详细步骤可以回看 week1 教程的 step3。

[https://www.alchemy.com/](https://www.alchemy.com/)

2\. 进入 Dashboard，点击 create app。

![](https://storage.googleapis.com/papyrus_images/23e6facd6f57a3d8c3ecb1f6b9be4bd84fb3e7c5d5da656f98f0726e571e8b22.png)

3\. 依次填入 Name, Description，Chain 选择 Ethereum，Network 选择 Goerli，然后点击 CREATE APP。

![](https://storage.googleapis.com/papyrus_images/6dc85929c0406cbf77a6a2571c9be6170761e6828f2f7c99c289da49839f7bd2.png)

4\. 点击 VIEW KEY，复制 HTTPS 和 API KEY 的内容

![](https://storage.googleapis.com/papyrus_images/71aa3ae52fc6a31e70e1a2c021c27e4e5f5d9a46684e8ae75367048c32502880.png)

5\. 查看小狐狸钱包中是否有 Goerli 测试网，如果没有可以去 chainlink.org 上添加一个，并保存私钥，第六步要用到。

6\. 因为交互需要消耗 gas，所以还需通过 chainlink faucet 获取测试代币 ETH。通过 faucet 的链接，进入申请代币即可。

[https://faucets.chain.link/goerli](https://faucets.chain.link/goerli)

### step6 在 Goerli 测试网络部署合约

1\. 在 ./scripts/ 文件夹下新建 deploy.js，复制以下代码，然后保存。

    const hre = require("hardhat");
    
    async function main() {
      // Get the contract to deploy & 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);
      });
    

2\. 编辑 ./hardhat.config.js，复制以下代码，然后保存。

    require("@nomiclabs/hardhat-waffle");
    require("@nomiclabs/hardhat-ethers");
    require("dotenv").config();
    
    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]
        }
      }
    };
    

3\. 新建 ./.env 文件，复制以下代码，然后保存。

    GOERLI_URL=输入goerli的rpc url
    GOERLI_API_KEY=输入goerli的api key
    PRIVATE_KEY=输入钱包私钥
    

4\. 在 cmd 中运行 `npx hardhat run scripts/deploy.js --network goerli`，返回

    BuyMeACoffee deployed to  合约地址
    

至此，合约已经部署到了 goerli 测试网，还可以在浏览器中查看。

![](https://storage.googleapis.com/papyrus_images/b3cc551743da4909684de5721cc96210cce7d2274acbe39814c3265f7d60ffae.png)

### step7 网站搭建

1\. 注册 repl.it 网站的帐号

[https://replit.com/](https://replit.com/)

2\. 教程做好了简单的前端，直接 fork 即可，教程的 [repo 链接](https://replit.com/@thatguyintech/BuyMeACoffee-Solidity-DeFi-Tipping-app)，点击 `Fork Repl` 即可

![](https://storage.googleapis.com/papyrus_images/cc1122fc7c0605fb1abc16968a279fc0934897ed43d701de9b6a604c7e3a16c8.png)

3\. 更换 ./pages/index.jsx 文件第 10 行的 contractAddress，改成刚刚在 goerli 网络部署的合约地址。

![](https://storage.googleapis.com/papyrus_images/d9c1d7381e16036f81c2ef7f2428826b083bf27414ecfd3afb5d5e3f2c25028e.png)

4\. 替换 ./pages/index.jsx 文件中的 Albert，可以改为自己的名字，一共有3处。

![](https://storage.googleapis.com/papyrus_images/94273b93833e2324c3bd6a66a0b481dce82ded6650783034f8003024ae4f8455.png)

5\. 从 VS Code 中复制 ./artifacts/contracts/BuyMeACoffee.sol/BuyMeACoffee.json 的所有内容到 ./utils/BuyMeACoffee.json

6\. 点击右上角 publish，按提示填写

![](https://storage.googleapis.com/papyrus_images/013e078764fcb0614eb5172108fc255b380d57ec5902730045d10614206ad743.png)

7\. 点击右上角 Open in a new tab，会在浏览器新的标签页中打开

![](https://storage.googleapis.com/papyrus_images/f6983623a955df02969200639b7fab0669fb2ddf4c678bd0e85130d5393cf473.png)

8\. 点击 connect your wallet 按钮后，链接小狐狸后，即出现了前端页面

![](https://storage.googleapis.com/papyrus_images/7265fb189c81f807676dec20ce989564430248d3cd3f27ebcb4f62d27e1a91b3.png)

9\. 体验交互一次，用另一个钱包，点击 Send 1 Coffee for 0.001ETH 按钮，小狐狸中点击确认

![](https://storage.googleapis.com/papyrus_images/cbf97b192a0c3313832692bd8a48afef3041c52862fd757fbf8fa64b1751685a.png)

10\. 网页自动更新了 memo。

![](https://storage.googleapis.com/papyrus_images/4272c2a58df5391cbb9f44ef6f37b05f6707b5511994b78d502975e7d697be7b.png)

同时可以在 etherscan 中查看。

![](https://storage.googleapis.com/papyrus_images/1868cfd8b7f246f282b4a402e0c230b8d69d7073ea751419d24aa8e3dcc4988f.png)

### step8 部署提现合约

在 VS Code 中，新建 ./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);
      });
    

见下图，现在合约中有 0.001ETH 余额

![](https://storage.googleapis.com/papyrus_images/a27d6b21b7100065023b729f4249f751303a1e7cfb7d4a7dbb28eae3e85d28b9.png)

运行 `npx hardhat run scripts\withdraw.js --network goerli`，返回

    current balance of owner:  0.999016010494752056 ETH
    current balance of contract:  0.001 ETH
    withdrawing funds..
    current balance of owner:  0.999970651994298471 ETH
    

再到 etherscan 浏览器中查看，多了一笔 Withdraw Tips 的交易，且余额变成了0

![](https://storage.googleapis.com/papyrus_images/05602436a47975f28d17142072b646634cbb03c1487f59f847403fd59fbf2eac.png)

### step9 在官网提交任务并 claim NFT

在官方申请网址中提交对应的信息。

[https://alchemyapi.typeform.com/roadtoweektwo](https://alchemyapi.typeform.com/roadtoweektwo)

在 mintkudos 网址中 claim Alchemy 的 NFT，通常需要一天至一周可以获得 allowlist 资格。

[https://mintkudos.xyz/claim/611](https://mintkudos.xyz/claim/611)

![](https://storage.googleapis.com/papyrus_images/67c0de51dd0b8b1a1797a5ef33485e4a48594e1d7005db6cef1838051f7129df.png)

---

*Originally published on [RogerZ](https://paragraph.com/@rogerz-2/alchemy-week2)*
