前端调用Solidity智能合约连接MetaMask小狐狸钱包,并在Alchemy测试网发布

前端调用Solidity智能合约连接MetaMask小狐狸钱包,并在Alchemy测试网发布

目录

1.DApp前端代码

2.solidity代码

3.编写部署代码

4.连接小狐狸钱包

5.启动 Webpack 开发服务器

6.部署到Alchemy测试网


在前一往篇文章基础上操作:

https://blog.ZEEKLOG.net/fyihdg/article/details/155675039https://blog.ZEEKLOG.net/fyihdg/article/details/155675039 已经搭建好环境,写一个简单的前端代码,创建一个网页界面,让用户可以通过浏览器与区块链上的 Counter 合约交互,连接用户的MetaMask钱包 ,与一个已部署在以太坊测试网上的 Counter 智能合约交互,显示当前计数值,并提供一个按钮让用户调用 count() 函数来递增它,同时通过事件监听实时更新界面。

1.DApp前端代码

  在vscode右键,新增 src目录,新建index.html,index.ts文件

index.html:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> </body> </html>

index.ts

import { ethers } from "ethers"; import { abi } from '../artifacts/contracts/Counter.sol/Counter.json'; function getEth() { // @ts-ignore const eth = window.ethereum; if (!eth) { throw new Error("No ethereum provider found"); } return eth; } async function requestAccess() { const eth = getEth(); const result = await eth.request({ method: "eth_requestAccounts" }) as string[]; return result && result.length > 0; } async function hasSigners() { const metamask = getEth(); const signers = await metamask.request({ method: "eth_accounts" }) as string[]; return signers.length > 0; } async function getContract() { // 1. 地址 // 2. 方法名 // 3. provider // 4. signer if (!await hasSigners() && !await requestAccess()) { throw new Error("No ethereum provider found"); } const provider = new ethers.BrowserProvider(getEth()); const address = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; const contract = new ethers.Contract( address, abi, await provider.getSigner(), ) const counter = document.createElement("div"); async function getCount() { counter.innerHTML = await contract.getCount(); } getCount(); const btn = document.createElement("button"); btn.innerHTML = "increment"; btn.onclick = async function () { await contract.count(); } contract.on(contract.filters.CounterInc(), async function ({ args }) { counter.innerHTML = args[0].toString() || await contract.getCount(); }) document.body.appendChild(counter); document.body.appendChild(btn); } async function main() { await getContract(); } main();

在package.json文件所在目录下新建,webpack.common.js,webpack.dev.js,webpack.prod.js,.env文件

webpack.common.js

const dotenv = require("dotenv"); dotenv.config(); const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { entry: "./src/index.ts", // bundle"s entry point output: { path: path.resolve(__dirname, "dist"), // output directory filename: "[name].js", // name of the generated bundle }, resolve: { extensions: [".js", ".ts", ".json"], }, module: { rules: [ { test: /\.ts$/, loader: "ts-loader", exclude: /node_modules/, }, { test: /\.css$/i, use: ["style-loader", "css-loader"], }, ], }, plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", inject: "body", }), ], };

webpack.dev.js

 const webpack = require("webpack"); const { merge } = require("webpack-merge"); const baseConfig = require("./webpack.common"); module.exports = merge(baseConfig,{ mode:"development", plugins: [ new webpack.DefinePlugin({ 'process.env.CONTRACT_ADDRESS': JSON.stringify(process.env.CONTRACT_ADDRESS), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), }), ], devServer: { historyApiFallback: true, port:8080, hot:true } });

webpack.prod.js

const webpack = require("webpack"); const baseConfig = require("./webpack.common"); const { merge } = require("webpack-merge"); module.exports = merge(baseConfig,{ mode: "production", plugins: [ new webpack.DefinePlugin({ 'process.env.CONTRACT_ADDRESS': JSON.stringify(process.env.CONTRACT_ADDRESS), 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), }), ], });

 .env文件

# .env 这个文件不能提交到git中 PRIVATE_KEY=私钥地址 ALCHEMY_API_KEY=ALCHEMY的key 

package.json添加配置

 "scripts": { "dev": "webpack serve --config webpack.dev.js", "build": "webpack --config webpack.prod.js", "deploy:local": "npx hardhat run scripts/deploy-counter.ts --network localhost" }

2.solidity代码

在contracts目录下新建Counter.sol文件

pragma solidity ^0.8.24; import "hardhat/console.sol"; contract Counter { uint counter; event CounterInc(uint counter); function count() public { counter++; console.log("Now, counter is: ", counter); emit CounterInc(counter); } function getCount() public view returns (uint) { return counter; } } 


 

3.编写部署代码

在package.json所有目录下新增scripts目录,创建deploy-counter.ts文件

import "@nomicfoundation/hardhat-ethers"; import { ethers } from "hardhat"; async function deploy() { const Counter = await ethers.getContractFactory("Counter"); const counter = await Counter.deploy(); await counter.waitForDeployment(); console.log('counter address is', await counter.getAddress()); return counter; } async function count(counter: any) { await counter.count(); console.log('count is',await counter.getCount()); } deploy().then(count);

4.连接小狐狸钱包

  创建一个钱包,打开谷歌浏览器,输入地址:https://metamask.io/zh-CN/download

我选择添加扩展

然后点击:

就可以找到狐狸钱包了

我们选创建一个钱包

要用 Google/Apple(因为没有备份可恢复),对于大多数加密货币用户:建议只用助记词方式,完全自主控制,不依赖任何第三方服务。Google/Apple 方式适合追求便捷的轻度用户,但要知道其中的信任依赖。

屏幕显示 12 个单词 → 立即手抄!,不要拍照,谁拿到你的助记词,谁就等于拥有了你的全部加密资产!

首先在根目录执行,启动一个 本地以太坊区块链节点(只在你电脑上运行),让前端 DApp 能通过 MetaMask 与本地合约交互,让你能在真实环境中测试 DApp,而无需花费真钱或等待测试网确认。记住这些只是测试工具,不要与真实钱包混淆!

  • 自动生成 20 个测试账户,每个账户包含:
    • 一个 私钥
    • 一个 地址
    • 10,000 个测试 ETH(仅限本地使用,没有真实价值)
npx hardhat node

注意这个地址: http://127.0.0.1:8545/,配置网络:

       

导出测试的私钥

把控制台打印的私钥复制进来

导入后:

  • MetaMask 账户 = Hardhat 账户
  • 有钱 + 有权限 → 开发调试畅通无阻!

5.启动 Webpack 开发服务器

首先部署合约

# 在另一个终端窗口部署合约 npx hardhat run scripts/deploy-counter.ts --network localhost 
D:\ZEEKLOG\Hardhat2.22.17>npx hardhat run scripts/deploy-counter.ts --network localhost counter address is 0x5FbDB2315678afecb367f032d93F642f64180aa3 count is 1n D:\ZEEKLOG\Hardhat2.22.17> 

启动 Webpack 开发服务器

npx webpack serve --config webpack.dev.js
D:\ZEEKLOG\Hardhat2.22.17>npx webpack serve --config webpack.dev.js [[email protected]] injecting env (0) from .env -- tip: 🔐 encrypt with Dotenvx: https://dotenvx.com <i> [webpack-dev-server] Project is running at: <i> [webpack-dev-server] Loopback: http://localhost:8080/, http://[::1]:8080/ <i> [webpack-dev-server] On Your Network (IPv4): http://198.18.0.1:8080/ <i> [webpack-dev-server] On Your Network (IPv6): http://[fdfe:dcba:9876::1]:8080/ <i> [webpack-dev-server] Content not from webpack is served from 'D:\ZEEKLOG\Hardhat2.22.17\public' directory <i> [webpack-dev-server] 404s will fallback to '/index.html' asset main.js 1.49 MiB [emitted] (name: main) asset index.html 242 bytes [emitted] runtime modules 27.9 KiB 12 modules modules by path ./node_modules/ethers/lib.commonjs/ 889 KiB 118 modules modules by path ./node_modules/ethers/node_modules/ 179 KiB 20 modules modules by path ./node_modules/aes-js/lib.commonjs/*.js 66.8 KiB ./node_modules/aes-js/lib.commonjs/index.js 1.6 KiB [built] [code generated] + 8 modules modules by path ./node_modules/webpack-dev-server/client/ 84.8 KiB modules by path ./node_modules/webpack-dev-server/client/*.js 53.3 KiB 4 modules modules by path ./node_modules/webpack-dev-server/client/utils/*.js 980 bytes 2 modules + 2 modules modules by path ./node_modules/webpack/hot/*.js 5.17 KiB ./node_modules/webpack/hot/dev-server.js 1.94 KiB [built] [code generated] ./node_modules/webpack/hot/log.js 1.73 KiB [built] [code generated] + 2 modules + 5 modules webpack 5.103.0 compiled successfully in 3441 ms

在浏览器中输入:http://localhost:8080

点击连接

点击'increment'按钮

点"确认"代表你在授权一个区块链交易!这是 MetaMask 交易确认弹窗

弹窗信息解读

✅ 网络:Hardhat Localhost(本地测试网)
✅ 请求来自:localhost:8080(你的前端DApp)
✅ 交互对象:合约地址(0x5FbDB...80aa3)
✅ 网络费:<US$0.01(测试币,实际$0)

const btn = document.createElement("button");
btn.innerHTML = "increment";
btn.onclick = async function () {
    await contract.count();  // ⬅️ 这里触发的!
}

// 点击确认后:
1. ✅ 发送一个交易到 Counter 合约
2. ✅ 调用 count() 函数
3. ✅ 合约中的计数器 +1
4. ✅ 触发 CounterInc 事件
5. ✅ 前端监听到事件,更新显示

区块链层面的操作:

// Counter.sol 合约中的函数
function count() public {
    count += 1;                // 状态变量+1
    emit CounterInc(count);    // 发出事件
}

// 这个操作会:
1. 🔄 修改区块链状态(count值改变)
2. 📝 产生一个交易记录
3. ⛓️ 被添加到区块中
4. 🔥 消耗少量 Gas(测试币)

至此,前端可以调用到合约了

6.部署到Alchemy测试网

打开网址:https://dashboard.alchemy.com/apps/vatwoz0eoicb1mih/setup

 

需要注意的是,最好弄一个gmail邮箱,qq邮箱容易被拦截。注册成功后

只选这个就行

下一步

去小狐狸复制私钥,选择这三个小点都可以:

.env文件,把私钥填写到:

从Endpoint URL复制:https://eth-sepolia.g.alchemy.com/v2/nr6k9FBd4Rvwi83XSr6xR

ALCHEMY_API_KEY就是:nr6k9FBd4Rvwi83XSr6xR

PRIVATE_KEY=填写刚刚复制的私钥,添加前辍0x
ALCHEMY_API_KEY=nr6k9FBd4Rvwi83XSr6xR

编辑hardhat.config.ts文件:

import "hardhat-gas-reporter" import "@nomicfoundation/hardhat-toolbox"; import "@nomicfoundation/hardhat-ethers"; import "@nomicfoundation/hardhat-verify"; const dotenv = require('dotenv'); dotenv.config(); type Config = import('hardhat/config').HardhatUserConfig const config: Config = { solidity: "0.8.28", networks: { hardhat: { chainId: 31337 }, sepolia_eth: { url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, accounts: [process.env.PRIVATE_KEY] } // 如果没有配置环境变量,这里先注释掉执行 // sepolia_eth: { // url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, // accounts: [process.env.PRIVATE_KEY] // } }, gasReporter: { enabled: true } }; export default config; 

tsconfig.json文件,把这个"strict": true,注释掉

{ "compilerOptions": { "target": "es2020", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, //"strict": true, "skipLibCheck": true, "resolveJsonModule": true } } 

运行命令:

npx hardhat run scripts/deploy-counter.ts --network sepolia_eth

成功了

D:\ZEEKLOG\Hardhat2.22.17>npx hardhat run scripts/deploy-counter.ts --network sepolia_eth [[email protected]] injecting env (2) from .env -- tip: 🔄 add secrets lifecycle management: https://dotenvx.com/ops [[email protected]] injecting env (0) from .env -- tip: 🔑 add access controls to secrets: https://dotenvx.com/ops counter address is 0x920F394f09E6B17133e94c7AF0a86dF5A5cB357B count is 0n D:\ZEEKLOG\Hardhat2.22.17>
输入网址查询:https://sepolia.etherscan.io/

成功上测试网了,注意,不是主网

主网是:https://etherscan.io/
Could not load content