使用 Hardhat 构建全链上 web3 站点

  • xing.sir
  • 更新于 2024-04-13 20:27
  • 阅读 1152

Hardhat是个solidity开发工具,他提供了一个内部的EVM节点运行环境。可以让你离线本地编写测试solidity代码。同时他还提供了Nodejs的编程环境,方便开发者封装合约交互工具。

Hardhat 是个 solidity 开发工具,他提供了一个内部的 EVM 节点运行环境。可以让你离线本地编写测试solidity 代码。同时他还提供了 Nodejs 的编程环境,方便开发者封装合约交互工具。

具体参看 : https://hardhat.org/hardhat-runner/docs/getting-started

本文通过实例介绍如何通过hardhat 发布一个全链web3站点,部署完成后,你就可以通过web3url 访问了。

部署hardhat 项目

npm install --save-dev hardhat

这里选择了 Create a TypeScript project 环境。

项目说明

部署完成后,你的项目代码里会包含几个目录 。

  1. contracts 这里放你编写的 solidity 代码
  2. scripts 目录,这里放你编写的部署测试脚本等
  3. tests 目录,这里放合约的测试工具
  4. hardhat.config.ts 这个文件放当前项目相关的节点信息 (很关键)

networks 中添加 mumbai 网络配置

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
require("dotenv").config();

const config: HardhatUserConfig = {
  solidity: "0.8.19",
  networks: {
    mumbai: {
      url: `https://polygon-mumbai.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
      // url: "https://rpc-mumbai.maticvigil.com/",
      accounts: [`0x${process.env.PRIVATE_KEY}`],
    },
  },
};

export default config;

需要提前准备好部署账号的私钥,其中包含一些测试 Token .

编写合约

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

contract Page {
    bytes32 public constant resolveMode = "manual";
    mapping(bytes => string) public uriContents;
    address payable public owner;

    constructor() payable {
        owner = payable(msg.sender);
    }

    function setMapping(bytes calldata pathinfo, string memory content) public {
        uriContents[pathinfo] = content;
    }

    fallback(bytes calldata pathinfo) external returns (bytes memory) {
        if (bytes(uriContents[pathinfo]).length != 0) {
            return abi.encode(uriContents[pathinfo]);
        }

        return abi.encode("<script>location.href='/index.html';</script>");
    }
}
  1. 合约使用 bytes32 public constant resolveMode = "manual"; 声明为 manual 模式。
  2. 使用一个 简单的mapping 来存取根据url 输出的资源。

注: 只有在 manual 模式中才可以使用mime 输出指定格式的内容,否则,content-type 均为空,浏览器会默认理解为 html

部署,测试

pnpm hardhat compile 
pnpm hardhat test  
hardhat run --network mumbai scripts/deploy.ts

部署的时候,需要指定网络。

随意部署一个 页面站点,用来生成网页内容

pnpm create vite@latest --template react-ts

由于站点本身生成的 index bundle 过大一般 1M 左右,在上传过程中消耗大量的 gas ,所以,建议使用cdn 资源替代。

安装 vite-plugin-cdn2 和 vite-plugin-inspect , 在vite.config.ts 中添加 cdn 配置:

import { defineConfig } from "vite";
import React from "@vitejs/plugin-react";
import Inspect from "vite-plugin-inspect";
import { cdn } from "vite-plugin-cdn2";
import { chunkSplitPlugin } from "vite-plugin-chunk-split";

export default defineConfig(({ command }) => {
  return {
    plugins: [
      chunkSplitPlugin({
        strategy: "single-vendor",
      }),
      React(),
      cdn({
        modules: [
          { name: "react", relativeModule: "./umd/react.production.min.js" },
          {
            name: "react-dom",
            relativeModule: "./umd/react-dom.production.min.js",
            aliases: ["client"],
          },
        ],
        apply: command,
      }),
      Inspect(),
    ]
  };
});

pnpm build 生成 dist 对应文件。

部署并上传到链上

以上过程,封装成了一个简单的 ts 脚本,将 部署合约,上传资源合并到了一期。

脚本如下:

import { ethers } from "hardhat";
import * as fs from "fs/promises";
import path from "path";

async function main() {
  const deployPath = "vite-project/dist";
  const provider = ethers.provider;
  const [deployer] = await ethers.getSigners();
  console.log("Deploying contracts with the account:", deployer.address);

  const balance = await provider.getBalance(deployer.address);
  console.log(`deploy balance is : ${balance}`);

  const lockedAmount = ethers.parseEther("0.001");
  if (balance.valueOf() > lockedAmount.valueOf()) {
    const page = await ethers.deployContract("Page", [], {
      value: lockedAmount,
    });
    await page.waitForDeployment();
    console.log(
      `Lock with ${ethers.formatEther(lockedAmount)} ETH and  deployed to ${
        page.target
      }`
    );

    const processFile = async (filePath: string) => {
      try {
        const content = await fs.readFile(filePath, "utf8");
        const uri = filePath.replace(deployPath, "");
        console.log(
          `Processing file: ${filePath} len: ${content.length} to URI: ${uri}`
        );

        const tx = await page.setMapping(ethers.toUtf8Bytes(uri), content);
        console.log(
          `Transaction for ${uri} sent, waiting for confirmation ${tx.hash}`
        );
        await tx.wait();
        console.log(`Transaction for ${uri} completed`);
        console.log(
          `web3 url is : https://${page.target}.80001.w3link.io${uri}. sleep 3 seconds ....`
        );
        await new Promise((resolve, reject) => {
          setTimeout(() => {
            console.log("do next Transaction!");
            resolve(true);
          }, 3000);
        });
      } catch (error) {
        console.error(`Error processing file ${filePath}:`, error);
      }
    };

    const traverseDirectory = async (directoryPath: string) => {
      try {
        const files = await fs.readdir(directoryPath);

        for (const file of files) {
          const fullPath = path.join(directoryPath, file);
          const stats = await fs.stat(fullPath);

          if (stats.isDirectory()) {
            await traverseDirectory(fullPath);
          } else if (stats.isFile()) {
            await processFile(fullPath);
          }
        }
      } catch (error) {
        console.error(`Error reading directory ${directoryPath}:`, error);
      }
    };

    await traverseDirectory(deployPath);
  } else {
    console.log("Insufficient funds");
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

参考项目: https://github.com/v1xingyue/hardhat-tutorial

部署完成后访问

部署完成后,有两种方式可以访问你的站点:

web3://$address.networkid/ 或者: 网关模式:

https://$address.$networkid.w3link.io

更复杂的方式,可参看 web3url 的设置。 https://w3url.w3eth.io/#/

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
xing.sir
xing.sir
0x427f...39a8
web3 让世界更美好。