Cover photo

手把手教程 ——Alchemy Week2

Alchemy 教程目录

Week1: How to Develop an NFT Smart Contract (ERC721) with Alchemy 教程

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

官方教程在此,也可以看 youtube 视频链接,不想看我啰嗦的可以移步。

Play Video

step0 环境配置

  • 系统环境:

    Window10 X64

  • 编程环境:

    nodeJS v16.15.0

    NPM v8.5.5

    nodeJS 安装回头写一篇教程吧,最简单的方法可以 google nvm,比去官网下载 nodeJS 的安装包好用多了。另外 windows 系统下需要配置环境变量,不明白的可以先百度。

  • 编程IDE:

    VS Code

    VS code 下载官网,exe 的安装程序,按照提示即可

step1 部署 web3 环境

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

2. 安装 hardhat,在 cmd 中输入

看到上图代表安装完成
看到上图代表安装完成

根据提示依次

选择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 特色之一
出现这样的画面,代表全部安装完成了。图中的 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/

2. 进入 Dashboard,点击 create app。

post image

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

post image

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

post image

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

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

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 测试网,还可以在浏览器中查看。

post image

step7 网站搭建

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

https://replit.com/

2. 教程做好了简单的前端,直接 fork 即可,教程的 repo 链接,点击 Fork Repl 即可

post image

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

post image

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

post image

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

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

post image

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

post image

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

post image

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

post image

10. 网页自动更新了 memo。

post image

同时可以在 etherscan 中查看。

post image

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 余额

post image

运行 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

post image

step9 在官网提交任务并 claim NFT

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

https://alchemyapi.typeform.com/roadtoweektwo

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

https://mintkudos.xyz/claim/611

post image