A16z领投1000万刀的Web3构建平台Buildspace第一期NFT获取教程

Buildspace 是一家 Web3 构建者网络服务商,旨在为用户提供探索职业生涯发展的学习平台,现在正专注于 Web3 领域。

11月18号完成 1000 万美元天使轮融资,投资方为「a16z」「Founders Inc」「Weekend Fund」「YC」「Vayner Fund」「Protocol Labs」「Orange DAO」「Solana Ventures」「OpenSea Ventures」「Alchemy Ventures」「Dreamers VC」。

全是熟悉的顶级大投资机构和项目方啊,Buildspace 跟Alchemy很类似,都是web3学习平台,虽然前期写了系列Alchemy教程,但我并没有刷1000个号,都是谣传哈。相对于Alchemy,Buildspace发币的可能性还要大很多,Alchemy有法币付费渠道,Buildspace暂未看到任何利益获取渠道,虽然这种平台确实没有发币的先例,谁知道Buildspace会不会是第一个呢?

手把手第一期教程开始:发布一个以太坊 dApp

step0 准备工作

1.进入项目官网,点击右上角start。

2.下拉到这里,点击进去。

3.点击start this build。

4.用谷歌账号登录。

5.添加日历,链接discord后,点击let’s go。

part1 入门

step1 欢迎,让我们为您详细介绍!

1.把这个看完,点击提交需求。

2.随便写点什么,点击提交。

3,然后点击next。

part2 创建第一个合约

step2 让你的本地以太坊网络运行起来

1.首先要安装环境,环境安装详见我这篇炼金的教程,确保node版本是16。

2.进入控制台,一次性复制以下代码,按回车,稍等几十秒hardhat安装完即可。

mkdir my-wave-portal
cd my-wave-portal
npm init -y
npm install --save-dev hardhat@latest

3.输入npx hardhat,按回车,就会出现下面那个蓝色大字图。

4.连续按三下回车,出现下图。

5.按照提示输入如下代码。

npm install --save-dev "hardhat@^2.12.2" "@nomicfoundation/hardhat-toolbox@^2.0.0"

6.打开vscode,点击file,点击open folder。

7.找到刚才创建的my-wave-portal文件夹,点击选择文件夹。

8.打开后如图所示。

9.打开 hardhat.config.js文件,将以下代码复制进去并保存。(不保存没用啊,要点保存)

require("@nomicfoundation/hardhat-toolbox");

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
    const accounts = await hre.ethers.getSigners();

    for (const account of accounts) {
        console.log(account.address);
    }
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
    solidity: "0.8.17",
};

10.最后,运行 npx hardhat node,应该打印出一堆如下所示的帐户,总共20个。

11.第一步的测试就结束了。清理一下。输入以下代码。

npx hardhat compile
npx hardhat test

12.在VScode中,把 test下面的 Lock.jsscripts.下面的 deploy.js,以及 contracts.下面的 Lock.sol都删除。

13.前往discord的progress频道,发送证明截图。

14.点击submit requirement,上传刚才第十步的图片即可。

step3 用 Solidity 编写您的第一个智能合约

1.回到VScode,在 contracts下面创建一个名为 WavePortal.sol的文件。

2.向 WavePortal.sol中复制粘贴下面代码。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.17;

import "hardhat/console.sol";

contract WavePortal {
    constructor() {
        console.log("Yo yo, I am a contract and I am smart");
    }
}

3.如果2中你的代码是白色的而不是彩色的,需要安装一下这个插件,看起来舒服些,能显示语法还能报错。

4.还是将2中的截图在discord的progress频道上传,并点击这里的submit requirement上传即可。

step4 在本地编译合约并运行

1.在 scripts下面创建一个名为 run.js的文件。

2.在run.js的文件中粘贴如下代码,记得保存,如图所示。

const main = async () => {
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();
  console.log("Contract deployed to:", waveContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0); // exit Node process without error
  } catch (error) {
    console.log(error);
    process.exit(1); // exit Node process while indicating 'Uncaught Fatal Exception' error
  }
  // Read more about Node exit ('process.exit(num)') status codes here: https://stackoverflow.com/a/47163396/7974948
};

runMain();

3.回到控制台,输入npx hardhat run scripts/run.js ,结果如图所示。

4.继续在discord和网页上传刚才3的截屏。

5.这一步他会跳出来个发推。那就发一下呗。

step5 在我们的智能合约上存储数据

1.将以下代码粘贴进waveportal.sol 并保存。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.17;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;

    constructor() {
        console.log("Yo yo, I am a contract and I am smart");
    }

    function wave() public {
        totalWaves += 1;
        console.log("%s has waved!", msg.sender);
    }

    function getTotalWaves() public view returns (uint256) {
        console.log("We have %d total waves!", totalWaves);
        return totalWaves;
    }
}

2.将以下代码粘贴到run.js并保存。

const main = async () => {
  const [owner, randomPerson] = await hre.ethers.getSigners();
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();

  console.log("Contract deployed to:", waveContract.address);
  console.log("Contract deployed by:", owner.address);

  await waveContract.getTotalWaves();

  const waveTxn = await waveContract.wave();
  await waveTxn.wait();

  await waveContract.getTotalWaves();
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

3.回到控制台,输入npx hardhat run scripts/run.js ,得到如下图结果。

4.如果要测试其他用户,还可以添加几行代码。通过VScode在run.js中复制粘贴这个,再回到控制台输入npx hardhat run scripts/run.js试着运行下,就可以得到如图所示结果。

const main = async () => {
  const [owner, randomPerson] = await hre.ethers.getSigners();
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();

  console.log("Contract deployed to:", waveContract.address);
  console.log("Contract deployed by:", owner.address);

  await waveContract.getTotalWaves();

  const firstWaveTxn = await waveContract.wave();
  await firstWaveTxn.wait();

  await waveContract.getTotalWaves();

  const secondWaveTxn = await waveContract.connect(randomPerson).wave();
  await secondWaveTxn.wait();

  await waveContract.getTotalWaves();
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

5.然后继续点击submit requirement,这次要写一个最喜欢的事。

step6 在本地部署,以便我们可以开始构建网站

1.回到控制台,输入npx hardhat node,可以看到刚才的20个地址,每个里面有10000eth。

2.在 scripts下面创建一个名为 deploy.js的文件,并在其中粘贴如下代码。

const main = async () => {
  const [deployer] = await hre.ethers.getSigners();
  const accountBalance = await deployer.getBalance();

  console.log("Deploying contracts with account: ", deployer.address);
  console.log("Account balance: ", accountBalance.toString());

  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();

  console.log("WavePortal address: ", waveContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

3.重新打开一个控制台,输入cd my-wave-portal 进入对应文件夹,再输入以下代码并运行,如图所示就对了。

npx hardhat run scripts/deploy.js --network localhost

4.最后可以到这里校对一下三个文件的代码,不对的话就重新复制粘贴下备用。

5.在dis和网页提交第三步的图片。

part3 连接到钱包

step7 设置一个基本的 React 应用程序,设置 Metamask

1.这里我们选用Replit ,先去注册一个账号。之前跟我做过alchemy的应该都有。

2.官方已经创建了一个React 项目, 只需转到 此处 ,在右侧附近您会看到“Fork Repl”按钮,再点击跳出框的Fork Repl即可。

3.点击顶部的Run,等2分钟就可以了。

4.继续通过dis和网页提交,这一次网页要提交的是网址,就是上一步run之后,右上角那个网址。

step8 将智能合约部署到真实的测试网

1.在 此处 使用 QuickNode 创建一个帐户,邮箱要验证下。

2.点击create an endpoint。

3.选择ETH的Goerli网络。

4.什么都不要选,直接点继续。

5.选免费的,点击create。

6.可以获取两个链接,待会儿有用。

7.领水,以下三个链接都可以用,自取。

8.为了安全起见,回到控制台,输入npm install --save dotenv 。

9.回到VScode,将hardhat.config.js代码更改为下面代码。

require("@nomicfoundation/hardhat-toolbox");
// Import and configure dotenv
require("dotenv").config();

module.exports = {
  solidity: "0.8.17",
  networks: {
    goerli: {
      // This value will be replaced on runtime
      url: process.env.STAGING_QUICKNODE_KEY,
      accounts: [process.env.PRIVATE_KEY],
    },
    mainnet: {
      url: process.env.PROD_QUICKNODE_KEY,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

10.在控制台输入echo test>.env 创建一个.env文件。

11.然后输入下面代码。

STAGING_QUICKNODE_KEY=REPLACE_WITH_ACTUAL_QUICKNODE_URL     // Goerli Quicknode
PROD_QUICKNODE_KEY=BLAHBLAH                                 // Mainnet Quicknode
PRIVATE_KEY=BLAHBLAH

12.接下来,从 QuickNode 仪表板获取您的 API URL 并将其粘贴第9步代码对应位置(url)。然后,将您的 私人 Goerli 密钥(而不是您的公共地址!)也粘贴到对应位置(accounts)。

13.回到控制台运行 npx hardhat run scripts/deploy.js --network goerli 。如图所示即可。复制最后一行已部署合约的地址并将其保存在某处。 别弄丢了! 稍后您将需要它作为前端:)。

14.您可以到这个网址查看刚刚部署的合约。

15.这一步没让去dis提交,就直接在网页提交合约地址即可。然后再分享个推特。

step9 将我们的钱包连接到我们的网络应用程序

1.前往 Replit 并前往 App.jsx下面的 src,这将是我们完成所有工作的地方。

2.将以下代码粘贴进去,替换掉原来的代码。

import React, { useEffect, useState } from "react";
import "./App.css";

const getEthereumObject = () => window.ethereum;

/*
 * This function returns the first linked account found.
 * If there is no account linked, it will return null.
 */
const findMetaMaskAccount = async () => {
  try {
    const ethereum = getEthereumObject();

    /*
     * First make sure we have access to the Ethereum object.
     */
    if (!ethereum) {
      console.error("Make sure you have Metamask!");
      return null;
    }

    console.log("We have the Ethereum object", ethereum);
    const accounts = await ethereum.request({ method: "eth_accounts" });

    if (accounts.length !== 0) {
      const account = accounts[0];
      console.log("Found an authorized account:", account);
      return account;
    } else {
      console.error("No authorized account found");
      return null;
    }
  } catch (error) {
    console.error(error);
    return null;
  }
};

const App = () => {
  const [currentAccount, setCurrentAccount] = useState("");

  const connectWallet = async () => {
    try {
      const ethereum = getEthereumObject();
      if (!ethereum) {
        alert("Get MetaMask!");
        return;
      }

      const accounts = await ethereum.request({
        method: "eth_requestAccounts",
      });

      console.log("Connected", accounts[0]);
      setCurrentAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  /*
   * This runs our function when the page loads.
   * More technically, when the App component "mounts".
   */
  useEffect(async () => {
    const account = await findMetaMaskAccount();
    if (account !== null) {
      setCurrentAccount(account);
    }
  }, []);

  return (
    <div className="mainContainer">
      <div className="dataContainer">
        <div className="header">
          👋 Hey there!
        </div>

        <div className="bio">
          I am Farza and I worked on self-driving cars so that's pretty cool
          right? Connect your Ethereum wallet and wave at me!
        </div>

        <button className="waveButton" onClick={null}>
          Wave at Me
        </button>

        {/*
         * If there is no currentAccount render this button
         */}
        {!currentAccount && (
          <button className="waveButton" onClick={connectWallet}>
            Connect Wallet
          </button>
        )}
      </div>
    </div>
  );
};

export default App;

3.点击Run。

4.把那个网址复制出来打开,你就可以看到这个界面,测试一下链接钱包功能即可。

5.这一次提交他问你和他人一起学习的感觉,回答下即可。

step10 从我们的网络应用程序调用已部署的智能合约

1.将以下代码复制粘贴进App.jsx,const contractAddress那一行要改成你自己之前生成的合约地址(之前跟你说前端有用的那个)。报错不要管,继续往下走,是个文件缺了,下面会补上。

import React, { useEffect, useState } from "react";
import { ethers } from "ethers";
import './App.css';
import abi from './utils/WavePortal.json';

const App = () => {
  const [currentAccount, setCurrentAccount] = useState("");
  /**
   * Create a varaible here that holds the contract address after you deploy!
   */
  const contractAddress = "0xd5f08a0ae197482FA808cE84E00E97d940dBD26E";
  const contractABI = abi.abi;
  
  const checkIfWalletIsConnected = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        console.log("Make sure you have metamask!");
        return;
      } else {
        console.log("We have the ethereum object", ethereum);
      }

      const accounts = await ethereum.request({ method: 'eth_accounts' });

      if (accounts.length !== 0) {
        const account = accounts[0];
        console.log("Found an authorized account:", account);
        setCurrentAccount(account)
      } else {
        console.log("No authorized account found")
      }
    } catch (error) {
      console.log(error);
    }
  }

  const connectWallet = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        alert("Get MetaMask!");
        return;
      }

      const accounts = await ethereum.request({ method: "eth_requestAccounts" });

      console.log("Connected", accounts[0]);
      setCurrentAccount(accounts[0]); 
    } catch (error) {
      console.log(error)
    }
  }

  const wave = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);

        let count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());

        const waveTxn = await wavePortalContract.wave();
        console.log("Mining...", waveTxn.hash);

        await waveTxn.wait();
        console.log("Mined -- ", waveTxn.hash);

        count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());
      } else {
        console.log("Ethereum object doesn't exist!");
      }
    } catch (error) {
      console.log(error)
    }
  }

  useEffect(() => {
    checkIfWalletIsConnected();
  }, [])
  
  return (
    <div className="mainContainer">
      <div className="dataContainer">
        <div className="header">
        👋 Hey there!
        </div>

        <div className="bio">
          I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
        </div>

        <button className="waveButton" onClick={wave}>
          Wave at Me
        </button>

        {!currentAccount && (
          <button className="waveButton" onClick={connectWallet}>
            Connect Wallet
          </button>
        )}
      </div>
    </div>
  );
}

export default App

2.在 src. 下面创建一个名为 utils的文件夹, 在 utils下面再创建一个名为 WavePortal.json.的空文件。

3.再回到VScode,将如下路径中的代码粘贴进步骤2的空文件中。

artifacts/contracts/WavePortal.sol/WavePortal.json

4.再到网页端试一下,链接小狐狸后,这次点击Wave at me有反应了,在小狐狸中确认即可。

5.然后在replit的CSS那里稍微改一下前端即可。估计是官方怕假提交。

6.将网页截图上传即可。

step11 将来自用户的消息存储在区块链上

1.回到VScode,再次修改WavePortal.sol的代码。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.17;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;

    /*
     * A little magic, Google what events are in Solidity!
     */
    event NewWave(address indexed from, uint256 timestamp, string message);

    /*
     * I created a struct here named Wave.
     * A struct is basically a custom datatype where we can customize what we want to hold inside it.
     */
    struct Wave {
        address waver; // The address of the user who waved.
        string message; // The message the user sent.
        uint256 timestamp; // The timestamp when the user waved.
    }

    /*
     * I declare a variable waves that lets me store an array of structs.
     * This is what lets me hold all the waves anyone ever sends to me!
     */
    Wave[] waves;

    constructor() {
        console.log("I AM SMART CONTRACT. POG.");
    }

    /*
     * You'll notice I changed the wave function a little here as well and
     * now it requires a string called _message. This is the message our user
     * sends us from the frontend!
     */
    function wave(string memory _message) public {
        totalWaves += 1;
        console.log("%s waved w/ message %s", msg.sender, _message);

        /*
         * This is where I actually store the wave data in the array.
         */
        waves.push(Wave(msg.sender, _message, block.timestamp));

        /*
         * I added some fanciness here, Google it and try to figure out what it is!
         * Let me know what you learn in #general-chill-chat
         */
        emit NewWave(msg.sender, block.timestamp, _message);
    }

    /*
     * I added a function getAllWaves which will return the struct array, waves, to us.
     * This will make it easy to retrieve the waves from our website!
     */
    function getAllWaves() public view returns (Wave[] memory) {
        return waves;
    }

    function getTotalWaves() public view returns (uint256) {
        // Optional: Add this line if you want to see the contract print the value!
        // We'll also print it over in run.js as well.
        console.log("We have %d total waves!", totalWaves);
        return totalWaves;
    }
}

2.更新 run.js. 代码如下。

const main = async () => {
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy();
  await waveContract.deployed();
  console.log("Contract addy:", waveContract.address);

  let waveCount;
  waveCount = await waveContract.getTotalWaves();
  console.log(waveCount.toNumber());

  /**
   * Let's send a few waves!
   */
  let waveTxn = await waveContract.wave("A message!");
  await waveTxn.wait(); // Wait for the transaction to be mined

  const [_, randomPerson] = await hre.ethers.getSigners();
  waveTxn = await waveContract.connect(randomPerson).wave("Another message!");
  await waveTxn.wait(); // Wait for the transaction to be mined

  let allWaves = await waveContract.getAllWaves();
  console.log(allWaves);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

3.在控制台输入npx hardhat run scripts/run.js,结果如图所示。

4.需要重新部署一下。再次使用这个代码npx hardhat run scripts/deploy.js --network goerli

5.改变 App.js中的 contractAddress地址为新生成的WavePortal地址。

6.重复step10步骤2、3,复制粘贴进去即可。

7.app.jsx中代码换成这个就可以了,合约地址换成你自己之前生成的,感谢AckooLu提供的代码,这一段我实在是晕,不会。。。

import React,{useEffect,useState} from "react";
import { ethers } from "ethers";
import './App.css';
import abi from './utils/WavePortal.json';

export default function App() {
  const [currentAccount,setCurrentAccount]=useState("");
  const [totalWaves,setTotalWaves]=useState();
  const [allwaves,setAllwaves]=useState([]);
  const [inputValue,setInputValue]= useState('');
  const contractAddress = "0xD014C8260bbF382D2ecCf0eb832c20A38c070F3b";
  const contractABI = abi.abi;
  const chainId = `0x5`;
  const rpcURL = 'https://rpc.ankr.com/eth_goerli';
  const networkName = 'Goerli Network';
  const currencyName = 'ETH';
  const currencySymbol = 'ETH';
  const explorerURL = 'https://goerli.etherscan.io/';

const addNetwork = async () => {
  await window.ethereum.request({
    method: 'wallet_addEthereumChain',
    params: [
      {
        chainId: chainId,
        chainName: networkName,
        rpcUrls: [rpcURL],
        blockExplorerUrls: [explorerURL],
        nativeCurrency: {
          name: currencyName,
          symbol: currencySymbol, // 2-6 characters long
          decimals: 18,
        },
      },
    ],
  });
  // refresh
  window.location.reload();
};
  const checkIfWalletIsConnected = async ()=>{
    try {
   
      const {ethereum} = window;
      if(!ethereum){
        console.log("make sure you have metamask");
        return;
      }else{
        console.log("we have the etherum object")
      }
      const accounts = await ethereum.request({method: "eth_accounts"})
      if(accounts.length!==0){
        const account = accounts[0];
        await getAllWaves();
        setCurrentAccount(account);
      }else{
        console.log("No authorized account found")
      }
         
    } catch (error) {
      console.log(error);
    }
  }
  const wave = async () => {
    try {
      const {ethereum} = window;
      if(ethereum){
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
        let count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());

        const waveTxn = await wavePortalContract.wave(inputValue,{gasLimit: 300000});
        console.log("Mining...", waveTxn.hash);

        await waveTxn.wait();
        console.log("Mined -- ", waveTxn.hash);

        count = await wavePortalContract.getTotalWaves();
        // await getAllWaves();
        setTotalWaves(count.toNumber());
        console.log("Retrieved total wave count...", count.toNumber());
      }else{
        console.log("ethereum object doesn't exist")
      }
      
    } catch (error) {
      console.log(error);
    }
  }
  const connectWallet = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        alert("Get MetaMask!");
        return;
      }

      const accounts = await ethereum.request({ method: "eth_requestAccounts" });

      console.log("Connected", accounts[0]);
      setCurrentAccount(accounts[0]); 
    } catch (error) {
      console.log(error)
    }
  }

  useEffect(()=>{
    checkIfWalletIsConnected();
  },[currentAccount])
  const fetchTotal = async ()=>{
     const {ethereum} = window;
      if(ethereum){
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
        let count = await wavePortalContract.getTotalWaves();
        setTotalWaves(count.toNumber());
        console.log("Retrieved total wave count...", count.toNumber());
      }else{
        console.log("ethereum object doesn't exist")
      }
  }
  useEffect(async ()=>{
    await fetchTotal();
  },[totalWaves])
  const getAllWaves=async ()=>{
    try {
      if(ethereum){
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
        const waves = await wavePortalContract.getAllWaves();
        console.log(waves);
        let wavesArr = [];
        waves.forEach(wave => {
          wavesArr.push({
            address: wave.waver,
            timestamp: new Date(wave.timestamp * 1000),
            message: wave.message
          });
        });
        setAllwaves(wavesArr);
      }
    } catch (error) {
      console.log(error);
    }
  }
  useEffect(()=>{
    let wavePortalContract;

  const onNewWave = (from, timestamp, message) => {
    console.log('NewWave', from, timestamp, message);
    setAllwaves(prevState => [
      ...prevState,
      {
        address: from,
        timestamp: new Date(timestamp * 1000),
        message: message,
      },
    ]);
  };

  if (window.ethereum) {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();

    wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);
    wavePortalContract.on('NewWave', onNewWave);
  }

  return () => {
    if (wavePortalContract) {
      wavePortalContract.off('NewWave', onNewWave);
    }
  };
  },[])

  return (
    <div className="mainContainer">
      
      <div className="dataContainer">
        <div className="header">
          <div>👋 Hey there!</div>
          <div className="add" onClick={addNetwork}>Add Goerli to Wallet</div>
        </div>

        <div className="bio">
        I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
        </div>
        <div className="inputWrapper">
          <input className="messageBox" placeholder="Please input wave message" onChange={(e)=>{
      setInputValue(e.target.value);
          }}></input>        
          <button className="waveButton" onClick={wave}>
            Wave at Me
          </button>
        </div>
        {!currentAccount&&(
          <button className="connectButton" onClick={connectWallet}>Connect Wallet</button>
        )}
        <div>
          TotalWaves: {totalWaves}
        </div>
        {allwaves.map((wave,index)=>{
          return (
            <div key={index} className="messageCard">
              <div>Address: {wave.address}</div>
              <div>Timestamp: {wave.timestamp.toString()}</div>
              <div>Message: {wave.message}</div>
            </div>
          )
        })}
      </div>
    </div>
  );
}

8.再到这个界面点击测试一下,能跳出小狐狸,能生成用户信息就可以了,丑就对了,我又不懂这个哈哈,官方看了不是他们自己的截图就行。

9.直接去提交截图就可以了。

step12 基金合约,设置奖品,发送用户以太坊

1.将APP.js换成这段代码。

import React, { useEffect, useState } from "react";
import { ethers } from "ethers";
import './App.css';
import wavePortal from './utils/WavePortal.json';

const App = () => {
  const [currentAccount, setCurrentAccount] = useState("");
  const [allWaves, setAllWaves] = useState([]);
  const contractAddress = "0xd5f08a0ae197482FA808cE84E00E97d940dBD26E";

  const getAllWaves = async () => {
    try {
      if (window.ethereum) {
        const provider = new ethers.providers.Web3Provider
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, wavePortal.abi, signer);

        const waves = await wavePortalContract.getAllWaves();

        let wavesCleaned = [];
        waves.forEach(wave => {
          wavesCleaned.push({
            address: wave.waver,
            timestamp: new Date(wave.timestamp * 1000),
            message: wave.message
          });
        });

        setAllWaves(wavesCleaned);
      } else {
        console.log("Ethereum object doesn't exist!")
      }
    } catch (error) {
      console.log(error);
    }
  }


  const checkIfWalletIsConnected = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        console.log("Make sure you have metamask!");
        return;
      } else {
        console.log("We have the ethereum object", ethereum);
      }

      const accounts = await ethereum.request({ method: 'eth_accounts' });

      if (accounts.length !== 0) {
        const account = accounts[0];
        console.log("Found an authorized account:", account);
        setCurrentAccount(account)
      } else {
        console.log("No authorized account found")
      }
    } catch (error) {
      console.log(error);
    }
  }

  const connectWallet = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        alert("Get MetaMask!");
        return;
      }

      const accounts = await ethereum.request({ method: "eth_requestAccounts" });

      console.log("Connected", accounts[0]);
      setCurrentAccount(accounts[0]);
    } catch (error) {
      console.log(error)
    }
  }

  const wave = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, wavePortal.abi, signer);

        let count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());

        const waveTxn = await wavePortalContract.wave();
        console.log("Mining...", waveTxn.hash);

        await waveTxn.wait();
        console.log("Mined -- ", waveTxn.hash);

        count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());
      } else {
        console.log("Ethereum object doesn't exist!");
      }
    } catch (error) {
      console.log(error)
    }
  }

  useEffect(() => {
    checkIfWalletIsConnected();
  }, [])

  return (
    <div className="mainContainer">
      <div className="dataContainer">
        <div className="header">
          👋 Hey there!
        </div>

        <div className="bio">
          I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
        </div>

        <button className="waveButton" onClick={wave}>
          Wave at Me
        </button>

        {!currentAccount && (
          <button className="waveButton" onClick={connectWallet}>
            Connect Wallet
          </button>
        )}

        {allWaves.map((wave, index) => {
          return (
            <div style={{ backgroundColor: "OldLace", marginTop: "16px", padding: "8px" }}>
              <div>Address: {wave.address}</div>
              <div>Time: {wave.timestamp.toString()}</div>
              <div>Message: {wave.message}</div>
            </div>)
        })}
      </div>
    </div>
  );
}

export default App

2.将deploy.js换成这段代码。

const main = async () => {
  const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
  const waveContract = await waveContractFactory.deploy({
    value: hre.ethers.utils.parseEther('0.001'),
  });

  await waveContract.deployed();

  console.log('WavePortal address: ', waveContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();

3.将run.js换成这段代码。

const main = async () => {
  const waveContractFactory = await hre.ethers.getContractFactory('WavePortal');
  const waveContract = await waveContractFactory.deploy({
    value: hre.ethers.utils.parseEther('0.01'),
  });
  await waveContract.deployed();
  console.log('Contract addy:', waveContract.address);

  let contractBalance = await hre.ethers.provider.getBalance(
    waveContract.address
  );
  console.log(
    'Contract balance:',
    hre.ethers.utils.formatEther(contractBalance)
  );

  let waveTxn = await waveContract.wave('A message!');
  await waveTxn.wait();

  contractBalance = await hre.ethers.provider.getBalance(waveContract.addresss);
  console.log(
    'Contract balance:',
    hre.ethers.utils.formatEther(contractBalance)
  );

  let allWaves = await waveContract.getAllWaves();
  console.log(allWaves);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

4.将WavePortal.sol换成这段代码。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;

    event NewWave(address indexed from, uint256 timestamp, string message);

    struct Wave {
        address waver;
        string message;
        uint256 timestamp;
    }

    Wave[] waves;

    constructor() payable {
        console.log("We have been constructed!");
    }

    function wave(string memory _message) public {
        totalWaves += 1;
        console.log("%s has waved!", msg.sender);

        waves.push(Wave(msg.sender, _message, block.timestamp));

        emit NewWave(msg.sender, block.timestamp, _message);

        uint256 prizeAmount = 0.0001 ether;
        require(
            prizeAmount <= address(this).balance,
            "Trying to withdraw more money than they contract has."
        );
        (bool success, ) = (msg.sender).call{value: prizeAmount}("");
        require(success, "Failed to withdraw money from contract.");
    }

    function getAllWaves() public view returns (Wave[] memory) {
        return waves;
    }

    function getTotalWaves() public view returns (uint256) {
        return totalWaves;
    }
}

5.在控制台输入npx hardhat run scripts/run.js ,如图所示。

6.在控制台输入npx hardhat run scripts/deploy.js --network goerli 在生成一次地址。现在,当您转到 Etherscan 并粘贴您的合约地址时,您会看到您的合约现在有 0.001 ETH 的价值! 成功!

7.将这个地址提交即可。

step13 随机选择获胜者并防止垃圾邮件发送者

1.将Waveportal的代码换成下面地代码。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;

    /*
     * We will be using this below to help generate a random number
     */
    uint256 private seed;

    event NewWave(address indexed from, uint256 timestamp, string message);

    struct Wave {
        address waver;
        string message;
        uint256 timestamp;
    }

    Wave[] waves;

    constructor() payable {
        console.log("We have been constructed!");
        /*
         * Set the initial seed
         */
        seed = (block.timestamp + block.difficulty) % 100;
    }

    function wave(string memory _message) public {
        totalWaves += 1;
        console.log("%s has waved!", msg.sender);

        waves.push(Wave(msg.sender, _message, block.timestamp));

        /*
         * Generate a new seed for the next user that sends a wave
         */
        seed = (block.difficulty + block.timestamp + seed) % 100;

        console.log("Random # generated: %d", seed);

        /*
         * Give a 50% chance that the user wins the prize.
         */
        if (seed < 50) {
            console.log("%s won!", msg.sender);

            /*
             * The same code we had before to send the prize.
             */
            uint256 prizeAmount = 0.0001 ether;
            require(
                prizeAmount <= address(this).balance,
                "Trying to withdraw more money than the contract has."
            );
            (bool success, ) = (msg.sender).call{value: prizeAmount}("");
            require(success, "Failed to withdraw money from contract.");
        }

        emit NewWave(msg.sender, block.timestamp, _message);
    }

    function getAllWaves() public view returns (Wave[] memory) {
        return waves;
    }

    function getTotalWaves() public view returns (uint256) {
        return totalWaves;
    }
}

2.将run.js的代码换成下面的。

const main = async () => {
  const waveContractFactory = await hre.ethers.getContractFactory("WavePortal");
  const waveContract = await waveContractFactory.deploy({
    value: hre.ethers.utils.parseEther("0.1"),
  });
  await waveContract.deployed();
  console.log("Contract addy:", waveContract.address);

  let contractBalance = await hre.ethers.provider.getBalance(
    waveContract.address
  );
  console.log(
    "Contract balance:",
    hre.ethers.utils.formatEther(contractBalance)
  );

  /*
   * Let's try two waves now
   */
  const waveTxn = await waveContract.wave("This is wave #1");
  await waveTxn.wait();

  const waveTxn2 = await waveContract.wave("This is wave #2");
  await waveTxn2.wait();

  contractBalance = await hre.ethers.provider.getBalance(waveContract.address);
  console.log(
    "Contract balance:",
    hre.ethers.utils.formatEther(contractBalance)
  );

  let allWaves = await waveContract.getAllWaves();
  console.log(allWaves);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

3.再回到控制台,运行npx hardhat run scripts/run.js ,可以得到如下结果。数字是随机生成的,不需要一样啊。

4.再将Waveportal.sol的代码全部换成下面的。

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.17;

import "hardhat/console.sol";

contract WavePortal {
    uint256 totalWaves;
    uint256 private seed;

    event NewWave(address indexed from, uint256 timestamp, string message);

    struct Wave {
        address waver;
        string message;
        uint256 timestamp;
    }

    Wave[] waves;

    /*
     * This is an address => uint mapping, meaning I can associate an address with a number!
     * In this case, I'll be storing the address with the last time the user waved at us.
     */
    mapping(address => uint256) public lastWavedAt;

    constructor() payable {
        console.log("We have been constructed!");
        /*
         * Set the initial seed
         */
        seed = (block.timestamp + block.difficulty) % 100;
    }

    function wave(string memory _message) public {
        /*
         * We need to make sure the current timestamp is at least 15-minutes bigger than the last timestamp we stored
         */
        require(
            lastWavedAt[msg.sender] + 15 minutes < block.timestamp,
            "Wait 15m"
        );

        /*
         * Update the current timestamp we have for the user
         */
        lastWavedAt[msg.sender] = block.timestamp;

        totalWaves += 1;
        console.log("%s has waved!", msg.sender);

        waves.push(Wave(msg.sender, _message, block.timestamp));

        /*
         * Generate a new seed for the next user that sends a wave
         */
        seed = (block.difficulty + block.timestamp + seed) % 100;

        if (seed <= 50) {
            console.log("%s won!", msg.sender);

            uint256 prizeAmount = 0.0001 ether;
            require(
                prizeAmount <= address(this).balance,
                "Trying to withdraw more money than they contract has."
            );
            (bool success, ) = (msg.sender).call{value: prizeAmount}("");
            require(success, "Failed to withdraw money from contract.");
        }

        emit NewWave(msg.sender, block.timestamp, _message);
    }

    function getAllWaves() public view returns (Wave[] memory) {
        return waves;
    }

    function getTotalWaves() public view returns (uint256) {
        return totalWaves;
    }
}

5.回到控制台运行npx hardhat run scripts/run.js 。如果跟刚才运行时间不超过15分钟,就会报错,这就对了。

6.然后提交即可。这里还是提交部署合约的以太坊链接。

step14 完成并庆祝!

1.再replit把网站的界面改一下,截图提交即可。

2.推特分享一下即可。

3.点击右下角get nft。

4.点击链接钱包,进去就可以得到了,等邮件通知吧。

Subscribe to 0x1Cd6…29D3
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.