web3-solidity hardhat ether.js笔记
1.问:solidity里mapping如何进行key空判断
在 Solidity 中,mapping 是一种键值对数据结构,用于存储和快速访问数据。然而,mapping 的特点是默认情况下所有键都会映射到其值类型的默认值(例如 uint 的默认值是 0,bool 的默认值是 false)。这意味着即使访问一个不存在的键,mapping 也会返回默认值,而不是像其他编程语言中的 null 或 undefined。 因此,要判断某个键是否存在,通常需要引入额外的机制,例如通过布尔值进行标记。以下是一个实现示例: 示例代码 pragma solidity ^0.8.0; contract MappingKeyCheck { // 定义一个 mapping mapping(address => uint256) public balances; // 定义一个额外的布尔 mapping 来标记键是否存在 mapping(address => bool) public isKeyExists; // 添加键值对 function setBalance(address _key, uint256 _value) public { balances[_key] = _value; isKeyExists[_key] = true; // 标记此键已存在 } // 检查键是否存在 function checkKeyExists(address _key) public view returns (bool) { return isKeyExists[_key]; } // 删除键值对 function deleteKey(address _key) public { balances[_key] = 0; // 将值重置为默认值 isKeyExists[_key] = false; // 标记此键不存在 } } 关键点: 默认值问题: mapping 的默认行为是访问一个不存在的键时会返回默认值。 所以单靠 mapping 无法直接判断键是否存在。 解决方案: 使用额外的布尔型 mapping(如 isKeyExists)来标记键是否存在。 删除键: 在 Solidity 中不能直接从 mapping 中删除键,但可以通过设置值为默认值,以及将对应的布尔标记设置为 false 来模拟删除操作。 使用说明 当你向 mapping 中添加键值对时,同时更新 isKeyExists 显示键的存在状态。 当你需要判断某个键是否存在时,通过 isKeyExists 获取状态。 如果删除键,确保同时更新 isKeyExists。
2.问:Solidity 存储和内存管理:深入理解与高效优化
答:
1. Solidity 中的存储模型概述 Solidity 的存储模型主要由三个关键概念组成:存储(storage)、内存(memory) 和 数据传递(calldata)。这三者负责智能合约中的数据存储与管理,它们有不同的用途和特性,对 gas 的消耗也不同。 1.1 存储(storage) storage 是 Solidity 中持久化的数据存储位置。所有在合约中定义的状态变量(即合约的成员变量)都存储在 storage 中。这意味着即使合约执行结束或区块链状态发生变化,storage 中的数据依然保持不变,直到合约显式修改它。 永久存储:状态变量存储在 storage 中,数据不会在函数执行完毕后丢失。 较高的 gas 消耗:因为存储在区块链的永久存储中,读写操作会消耗较多的 gas,特别是写操作。 示例: contract StorageExample { uint256 public data; // 存储在 storage 中的状态变量 function updateData(uint256 _data) public { data = _data; // 修改 storage 中的数据,消耗较多 gas } } 1.2 内存(memory) memory 是用于临时存储数据的非持久化存储区域。函数调用时,局部变量、函数参数等可以存储在 memory 中。memory 中的数据只在函数执行期间存在,函数返回后数据会被清除。 临时存储:memory 中的数据不会在函数执行结束后保留。 相对较低的 gas 消耗:相较于 storage,memory 的读写操作消耗较少的 gas。 示例: contract MemoryExample { function process(uint256 _input) public pure returns (uint256) { uint256 temp = _input * 2; // 临时存储在 memory 中 return temp; // 函数执行完毕后,temp 将被清除 } } 1.3 数据传递(calldata) calldata 是一个特殊的存储区域,用于存储函数的外部调用参数。calldata 是不可修改的(只读),而且 gas 消耗更低,因此常用于处理外部输入的数据。 只读存储:calldata 中的数据不能被修改,通常用于传递外部函数调用参数。 最低的 gas 消耗:由于它的只读属性,calldata 的读写操作 gas 消耗最低。 示例: contract CalldataExample { function processCalldata(uint256[] calldata data) public pure returns (uint256) { return data[0] * 2; // 只读访问 calldata 中的数据 } } 2. 存储、内存和数据传递的区别 2.1 生命周期 存储(storage):与合约的生命周期一致,数据在合约的整个生命周期内都保留,直到显式修改或删除。 内存(memory):仅在函数调用期间存在,函数结束后内存会自动释放,数据不再保留。 数据传递(calldata):函数调用期间的只读数据存储,用于外部合约调用参数传递,函数执行完毕后数据消失。 2.2 可读写性 存储(storage):可读可写,适用于需要长期存储和操作的数据。 内存(memory):可读可写,适用于临时数据处理,但不能用于永久存储。 数据传递(calldata):只读,适用于只需要读取外部传递的数据场景。 2.3 gas 消耗 存储(storage):写操作消耗最高,读操作次之,主要用于需要长期保存数据的场景。 内存(memory):读写操作的 gas 消耗比 storage 低,适合函数内部临时处理数据。 数据传递(calldata):消耗最少,特别适合只需要传递和读取外部数据的场景。 3. 如何高效管理数据? 3.1 优化存储访问 减少 storage 写操作:由于写入 storage 的操作非常昂贵,应该尽可能减少不必要的 storage 写入。可以通过局部变量临时保存值,并在所有计算完成后再更新 storage。 示例: contract OptimizedStorage { uint256 public data; function updateData(uint256 _input) public { uint256 temp = data; // 读取 storage 到局部变量 temp += _input; // 在内存中处理 data = temp; // 完成处理后再更新 storage } } 在上面的代码中,我们将 storage 中的 data 读取到 memory 中,并在所有处理完成后再写回 storage。这样减少了多次 storage 写入,从而节省 gas。 3.2 使用 calldata 传递数据 如果函数参数是外部传入的数组或字符串,尽量使用 calldata,因为它的 gas 消耗最少。如果数据只用于读取,而不需要修改,calldata 是最佳选择。 示例: contract UseCalldata { function sumArray(uint256[] calldata data) public pure returns (uint256) { uint256 sum = 0; for (uint256 i = 0; i < data.length; i++) { sum += data[i]; // 只读访问 calldata 数据 } return sum; } } 3.3 合适的数据类型选择 Solidity 中不同的数据类型占用的存储空间不同,选择合适的数据类型可以节省存储空间。例如,尽量使用 uint8、uint16 等小类型代替 uint256,如果数据范围允许的话。 3.4 减少复杂数据结构的存储 复杂的数据结构(如数组、映射等)在 storage 中占用更多的存储空间并且消耗更多的 gas。在设计合约时,应尽量减少复杂数据结构的使用,或者将其临时保存在 memory 中处理。 4. 存储、内存和数据传递的常见误区 4.1 将数组保存在 storage 中 将数组保存在 storage 中并进行频繁操作是一个常见的低效操作。数组的长度会影响读取、修改等操作的 gas 消耗,尤其是对于大数组,频繁操作会显著增加成本。因此,建议将数组数据尽量在 memory 中处理,并在必要时再将结果写回 storage。 4.2 不当的 calldata 使用 虽然 calldata 消耗最低,但它只能用于外部调用的参数。如果尝试在函数内部创建或修改 calldata,编译器会报错。因此,calldata 只能用于只读场景,开发者需要清楚它的限制。 5. 总结 理解 Solidity 中的存储模型和数据管理对于优化合约性能和降低 gas 成本至关重要。存储(storage)用于持久化数据,操作消耗较高;内存(memory)适用于临时数据处理,消耗较低;而数据传递(calldata)是用于函数参数的高效只读存储。为了编写高效的合约,开发者应根据具体需求合理选择存储区域,并尽量减少不必要的 storage 写操作。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_73901614/article/details/142684463
问: 在hardhat框架中,有哪些变量在哪些环境是被Hardhat 自动注入的
答:
Hardhat 自动注入的变量 1. 核心注入变量 📦 ethers // 自动可用,无需引入const contractFactory = await ethers.getContractFactory("MyContract");const provider = ethers.provider;const signers = await ethers.getSigners(); 🌐 hre (Hardhat Runtime Environment) // 包含所有Hardhat功能的主对象console.log(hre.network.name);console.log(hre.config);await hre.run("compile"); 🔧 artifacts // 访问编译后的合约artifactsconst artifact = await artifacts.readArtifact("MyContract"); 📋 config // Hardhat配置对象console.log(config.solidity.version);console.log(config.networks); 🌍 network // 当前网络信息console.log(network.name);console.log(network.config); 🔨 run // 执行Hardhat任务await run("compile");await run("test");
2. 不同环境中的可用性 ✅ Hardhat Scripts (npx hardhat run scripts/xxx.js) // scripts/deploy.js// 以下变量自动可用:console.log(ethers); // ✅console.log(hre); // ✅console.log(artifacts); // ✅console.log(config); // ✅console.log(network); // ✅console.log(run); // ✅
✅ Hardhat Console (npx hardhat console) $ npx hardhat console > ethers.provider.getBlockNumber() // ✅ 可用 > network.name // ✅ 可用 > await artifacts.readArtifact("MyContract") // ✅ 可用
✅ Hardhat Tasks task("my-task", "description").setAction(async (taskArgs, hre) => { // 通过hre参数访问: console.log(hre.ethers); // ✅ console.log(hre.network); // ✅ console.log(hre.artifacts); // ✅ // 也可能全局可用(但建议用hre): console.log(ethers); // ✅ (但建议用hre.ethers) });
✅ 测试文件 (使用 npx hardhat test) // test/MyContract.test.jsdescribe("MyContract", function () { it("should work", async function () { // 以下变量通常可用: const signers = await ethers.getSigners(); // ✅ const Contract = await ethers.getContractFactory("MyContract"); // ✅ console.log(network.name); // ✅ }); });
❌ 普通Node.js执行 (node scripts/xxx.js) // 直接用node执行时,需要手动引入:const hre = require("hardhat");const { ethers } = require("hardhat");
3. 完整的注入变量列表 // 在Hardhat环境中自动可用的变量: // 🔹 核心工具 ethers // Ethers.js库实例 hre // Hardhat Runtime Environment artifacts // 合约artifacts访问器 config // Hardhat配置对象 network // 当前网络信息 run // 任务执行器 // 🔹 网络相关 web3 // Web3.js (如果安装了@nomiclabs/hardhat-web3) waffle // Waffle测试工具 (如果安装了相关插件) // 🔹 测试相关 (在测试环境中) expect // Chai的expect (如果配置了) assert // 断言库
4. 检测注入变量的方法 // 检测脚本 - 放在scripts/check-globals.jsconsole.log("=== Hardhat Global Variables ==="); const globals = [ 'ethers', 'hre', 'artifacts', 'config', 'network', 'run', 'web3', 'waffle' ]; globals.forEach(varName => { try { const value = eval(varName); console.log(`✅ ${varName}:`, typeof value); } catch (e) { console.log(`❌ ${varName}: not available`); } });
5. 环境对比表 变量 Scripts Console Tasks Tests Node.js ethers ✅ ✅ ✅ ✅ ❌ hre ✅ ✅ ✅(参数) ✅ ❌ artifacts ✅ ✅ ✅ ✅ ❌ config ✅ ✅ ✅ ✅ ❌ network ✅ ✅ ✅ ✅ ❌ run ✅ ✅ ✅ ✅ ❌
问:ether.js与hardhat的关系,以及他们是如何配合工作的
答:
Ether.js与Hardhat的关系 Ether.js简介 定义:Ether.js是一个轻量级的JavaScript库,用于与以太坊区块链进行交互 主要功能: 连接以太坊网络 管理钱包和账户 发送交易 与智能合约交互 查询区块链数据 Hardhat简介 定义:Hardhat是一个专业的以太坊开发环境和框架 主要功能: 智能合约编译 本地区块链网络模拟 自动化测试 部署脚本管理 调试工具 它们如何配合工作 1. 开发环境集成 // hardhat.config.js require("@nomiclabs/hardhat-ethers"); module.exports = { solidity: "0.8.19", networks: { hardhat: { // 本地测试网络配置 } } }; 2. 智能合约部署 // deploy.js const { ethers } = require("hardhat"); async function main() { // 使用Hardhat提供的ethers实例 const Contract = await ethers.getContractFactory("MyContract"); const contract = await Contract.deploy(); await contract.deployed(); console.log("Contract deployed to:", contract.address); } 3. 测试脚本编写 // test/contract.test.js const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MyContract", function () { it("Should return the right value", async function () { const Contract = await ethers.getContractFactory("MyContract"); const contract = await Contract.deploy(); expect(await contract.getValue()).to.equal("Hello World"); }); }); 4. 与合约交互 // interact.js const { ethers } = require("hardhat"); async function interact() { const contractAddress = "0x..."; const contract = await ethers.getContractAt("MyContract", contractAddress); // 调用合约方法 const result = await contract.someFunction(); console.log("Result:", result); } 配合工作的优势 🔧 开发效率 Hardhat提供开发环境,Ether.js处理区块链交互 无需手动配置复杂的连接设置 内置的测试网络和账户管理 🧪 测试便利 自动重置的本地区块链 快速的交易确认 丰富的调试信息 📦 部署简化 统一的部署脚本 多网络配置管理 自动化的验证流程 🔍 调试支持 详细的错误信息 交易追踪 Gas使用分析 典型工作流程 graph TD A[编写智能合约] --> B[使用Hardhat编译] B --> C[本地测试网络启动] C --> D[使用Ether.js编写测试] D --> E[运行测试] E --> F[部署到测试网] F --> G[使用Ether.js与合约交互] G --> H[部署到主网] 最佳实践建议 1. 项目结构 project/ ├── contracts/ # 智能合约 ├── scripts/ # 部署脚本 ├── test/ # 测试文件 ├── hardhat.config.js # Hardhat配置 └── package.json 2. 环境配置 使用环境变量管理私钥 配置多个网络环境 设置适当的Gas限制 3. 安全考虑 永远不要在代码中硬编码私钥 使用Hardhat的内置账户进行测试 在主网部署前充分测试 这两个工具的结合为以太坊开发提供了完整的解决方案,让开发者能够高效地构建、测试和部署去中心化应用!
问:ethers.js的作用以及有哪些内置变量
答:
你好!这是一个很棒的问题!😊 让我详细介绍一下ethers.js的作用和内置变量: 🎯 ethers.js 的作用 1. 核心功能 ethers.js 是一个完整的以太坊JavaScript库,用于: 🔗 连接区块链:与以太坊网络交互 📝 智能合约:部署和调用智能合约 💰 钱包管理:创建和管理钱包 🔐 交易处理:发送和接收交易 📊 数据查询:查询区块链数据 2. 主要优势 📦 模块化设计:功能分离,按需使用 🛡️ 类型安全:完整的TypeScript支持 🚀 性能优化:比web3.js更轻量 📚 文档完善:详细的API文档 🔧 ethers.js 内置变量和模块 1. 核心模块结构 const { ethers } = require("ethers"); // ethers 对象包含以下主要模块: console.log(Object.keys(ethers)); // 输出类似:['providers', 'Wallet', 'Contract', 'utils', 'BigNumber', ...] 2. 主要内置变量/模块 🌐 providers(提供者) // 连接到区块链网络 ethers.providers.JsonRpcProvider ethers.providers.InfuraProvider ethers.providers.AlchemyProvider ethers.providers.EtherscanProvider ethers.providers.Web3Provider // 使用示例 const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545"); const infuraProvider = new ethers.providers.InfuraProvider("mainnet", "YOUR_API_KEY"); 💼 Wallet(钱包) // 钱包管理 ethers.Wallet ethers.Signer // 使用示例 const wallet = new ethers.Wallet("0x私钥"); const randomWallet = ethers.Wallet.createRandom(); const mnemonicWallet = ethers.Wallet.fromMnemonic("助记词"); 📋 Contract(合约) // 智能合约交互 ethers.Contract ethers.ContractFactory // 使用示例 const contract = new ethers.Contract(address, abi, provider); const factory = new ethers.ContractFactory(abi, bytecode, signer); 🔧 utils(工具函数) // 常用工具函数 ethers.utils.parseEther("1.0") // 转换ETH到wei ethers.utils.formatEther(bigNumber) // 转换wei到ETH ethers.utils.parseUnits("100", 6) // 解析单位(如USDC) ethers.utils.formatUnits(amount, 6) // 格式化单位 ethers.utils.keccak256("0x1234") // 哈希函数 ethers.utils.solidityKeccak256(...) // Solidity兼容哈希 ethers.utils.getAddress(address) // 地址校验和格式化 ethers.utils.isAddress(address) // 验证地址格式 ethers.utils.hexlify(data) // 转换为十六进制 ethers.utils.arrayify(hexString) // 转换为字节数组 🔢 BigNumber(大数处理) // 处理大整数 ethers.BigNumber.from("1000000000000000000") // 1 ETH in wei ethers.constants.Zero // 0 ethers.constants.One // 1 ethers.constants.MaxUint256 // 最大uint256值 📊 constants(常量) // 常用常量 ethers.constants.AddressZero // "0x0000000000000000000000000000000000000000" ethers.constants.HashZero // "0x0000000000000000000000000000000000000000000000000000000000000000" ethers.constants.Zero // BigNumber(0) ethers.constants.One // BigNumber(1) ethers.constants.Two // BigNumber(2) ethers.constants.MaxUint256 // 最大uint256值 ethers.constants.MaxInt256 // 最大int256值 ethers.constants.MinInt256 // 最小int256值 ethers.constants.EtherSymbol // "Ξ" 3. 在Hardhat中的特殊变量 当在Hardhat环境中使用时,ethers对象还包含额外的功能: // Hardhat特有的ethers功能 ethers.provider // 当前网络的provider ethers.getSigners() // 获取可用的签名者 ethers.getContractFactory(name) // 获取合约工厂 ethers.getContractAt(name, addr) // 获取已部署的合约实例 🚀 实际使用示例 1. 完整的部署脚本 const { ethers } = require("hardhat"); async function main() { // 获取签名者 const [deployer] = await ethers.getSigners(); console.log("Deployer address:", deployer.address); // 获取余额 const balance = await deployer.getBalance(); console.log("Deployer balance:", ethers.utils.formatEther(balance), "ETH"); // 部署合约 const ContractFactory = await ethers.getContractFactory("MyContract"); const contract = await ContractFactory.deploy( ethers.utils.parseEther("1.0"), // 构造函数参数 ethers.constants.AddressZero ); await contract.waitForDeployment(); console.log("Contract deployed to:", contract.target); // 调用合约函数 const tx = await contract.someFunction({ value: ethers.utils.parseEther("0.1") }); await tx.wait(); console.log("Transaction hash:", tx.hash); } 2. 工具函数使用 // 地址和数值处理 const address = "0x742d35Cc6634C0532925a3b8D39c9d0a3d7b3e5B"; console.log("Is valid address:", ethers.utils.isAddress(address)); console.log("Checksum address:", ethers.utils.getAddress(address.toLowerCase())); // 单位转换 const oneEther = ethers.utils.parseEther("1.0"); console.log("1 ETH in wei:", oneEther.toString()); console.log("Back to ETH:", ethers.utils.formatEther(oneEther)); // USDC (6 decimals) 处理 const usdcAmount = ethers.utils.parseUnits("100", 6); // 100 USDC console.log("100 USDC:", usdcAmount.toString()); console.log("Back to USDC:", ethers.utils.formatUnits(usdcAmount, 6)); // 哈希和编码 const hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("Hello World")); console.log("Hash:", hash); 3. Provider和网络交互 async function networkInfo() { const provider = ethers.provider; // Hardhat provider // 网络信息 const network = await provider.getNetwork(); console.log("Network:", network.name, "Chain ID:", network.chainId); // 最新区块 const blockNumber = await provider.getBlockNumber(); console.log("Latest block:", blockNumber); // Gas价格 const gasPrice = await provider.getGasPrice(); console.log("Gas price:", ethers.utils.formatUnits(gasPrice, "gwei"), "gwei"); } 📝 总结 ethers.js 提供了丰富的内置变量和模块: 🌐 providers: 网络连接 💼 Wallet: 钱包管理 📋 Contract: 合约交互 🔧 utils: 实用工具 🔢 BigNumber: 大数处理 📊 constants: 常用常量
问:node.js如何管理和引用其它javaScript文件的?
答:
你好!这是一个很实用的问题!😊 在Node.js中引用其他JavaScript文件有多种方式,让我详细介绍一下: 🔗 CommonJS 模块系统(Node.js默认) 1. 基本的 require() 引用 📁 文件结构示例 project/ ├── utils/ │ ├── math.js │ ├── string.js │ └── index.js ├── config/ │ └── database.js ├── models/ │ └── User.js └── app.js 📝 导出和引用示例 // utils/math.js - 被引用的文件 function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } const PI = 3.14159; // 导出方式1:exports exports.add = add; exports.subtract = subtract; exports.PI = PI; // 导出方式2:module.exports // module.exports = { add, subtract, PI }; // app.js - 引用文件 const math = require('./utils/math'); console.log(math.add(5, 3)); // 8 console.log(math.subtract(10, 4)); // 6 console.log(math.PI); // 3.14159 2. 不同的引用方式 ✅ 完整对象引用 // utils/helpers.js exports.formatDate = (date) => date.toISOString(); exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); exports.isEmail = (email) => /\S+@\S+\.\S+/.test(email); // app.js const helpers = require('./utils/helpers'); console.log(helpers.formatDate(new Date())); console.log(helpers.capitalize('hello')); console.log(helpers.isEmail('test@example.com')); ✅ 解构引用 // app.js const { formatDate, capitalize, isEmail } = require('./utils/helpers'); console.log(formatDate(new Date())); console.log(capitalize('hello')); console.log(isEmail('test@example.com')); ✅ 混合引用 // app.js const helpers = require('./utils/helpers'); const { add, subtract } = require('./utils/math'); console.log(helpers.formatDate(new Date())); console.log(add(5, 3)); 🎯 不同导出类型的引用 1. 引用导出对象 // config/database.js module.exports = { host: 'localhost', port: 5432, database: 'myapp', user: 'admin', password: 'secret', options: { ssl: false, timeout: 5000 } }; // app.js const dbConfig = require('./config/database'); console.log(dbConfig.host); // 'localhost' console.log(dbConfig.options.ssl); // false // 或者解构 const { host, port, database } = require('./config/database'); console.log(`${host}:${port}/${database}`); 2. 引用导出函数 // utils/logger.js module.exports = function(level, message) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`); }; // app.js const log = require('./utils/logger'); log('info', 'Application started'); log('error', 'Something went wrong'); 3. 引用导出类 // models/User.js class User { constructor(name, email) { this.name = name; this.email = email; this.createdAt = new Date(); } greet() { return `Hello, I'm ${this.name}`; } getInfo() { return { name: this.name, email: this.email, createdAt: this.createdAt }; } } module.exports = User; // app.js const User = require('./models/User'); const user = new User('John Doe', 'john@example.com'); console.log(user.greet()); // "Hello, I'm John Doe" console.log(user.getInfo()); 📂 不同路径的引用方式 1. 相对路径引用 // 当前目录下的文件 const math = require('./math'); const math = require('./math.js'); // .js 可以省略 // 子目录中的文件 const helpers = require('./utils/helpers'); const dbConfig = require('./config/database'); // 上级目录中的文件 const config = require('../config/app'); // 多级目录 const validator = require('../../shared/validation'); 2. 目录引用(index.js) // utils/index.js - 目录的入口文件 const math = require('./math'); const string = require('./string'); const validation = require('./validation'); module.exports = { math, string, validation }; // app.js const utils = require('./utils'); // 自动加载 utils/index.js console.log(utils.math.add(5, 3)); console.log(utils.string.capitalize('hello')); 3. 绝对路径引用(不推荐) const path = require('path'); // 使用 __dirname 构建绝对路径 const mathPath = path.join(__dirname, 'utils', 'math'); const math = require(mathPath); // 或者直接使用绝对路径(不推荐) const math = require('/full/path/to/utils/math'); 🔄 ES6 模块系统(ESM) 1. 启用 ES6 模块 方式1:package.json 中设置 { "type": "module", "name": "my-app", "version": "1.0.0" } 方式2:使用 .mjs 扩展名 // math.mjs export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; } export const PI = 3.14159; 2. ES6 导出和引用 // utils/math.js (在 type: "module" 项目中) export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; } export const PI = 3.14159; // 默认导出 export default class Calculator { add(a, b) { return a + b; } subtract(a, b) { return a - b; } } // app.js import { add, subtract, PI } from './utils/math.js'; import Calculator from './utils/math.js'; console.log(add(5, 3)); console.log(PI); const calc = new Calculator(); console.log(calc.add(10, 5)); 🎯 实际项目示例 1. Express 应用结构 // 📁 项目结构 project/ ├── controllers/ │ ├── userController.js │ └── productController.js ├── models/ │ ├── User.js │ └── Product.js ├── middleware/ │ ├── auth.js │ └── validation.js ├── utils/ │ ├── database.js │ └── helpers.js ├── config/ │ └── app.js └── server.js // models/User.js class User { constructor(data) { this.id = data.id; this.name = data.name; this.email = data.email; } static async findById(id) { // 数据库查询逻辑 return new User({ id, name: 'John', email: 'john@example.com' }); } async save() { // 保存到数据库的逻辑 console.log('User saved:', this.name); } } module.exports = User; // controllers/userController.js const User = require('../models/User'); const { validateEmail } = require('../utils/helpers'); const userController = { async getUser(req, res) { try { const userId = req.params.id; const user = await User.findById(userId); res.json(user); } catch (error) { res.status(500).json({ error: error.message }); } }, async createUser(req, res) { try { const { name, email } = req.body; if (!validateEmail(email)) { return res.status(400).json({ error: 'Invalid email' }); } const user = new User({ name, email }); await user.save(); res.status(201).json(user); } catch (error) { res.status(500).json({ error: error.message }); } } }; module.exports = userController; // server.js const express = require('express'); const userController = require('./controllers/userController'); const productController = require('./controllers/productController'); const authMiddleware = require('./middleware/auth'); const config = require('./config/app'); const app = express(); // 中间件 app.use(express.json()); app.use(authMiddleware); // 路由 app.get('/users/:id', userController.getUser); app.post('/users', userController.createUser); app.listen(config.port, () => { console.log(`Server running on port ${config.port}`); }); 2. 工具模块的组织 // utils/index.js - 统一导出 const database = require('./database'); const validation = require('./validation'); const helpers = require('./helpers'); const logger = require('./logger'); module.exports = { database, validation, helpers, logger }; // app.js - 统一引用 const utils = require('./utils'); // 使用 utils.logger.info('Application started'); utils.database.connect(); const isValid = utils.validation.isEmail('test@example.com'); 🔄 动态引用 1. 条件引用 // 根据环境加载不同配置 const env = process.env.NODE_ENV || 'development'; const config = require(`./config/${env}`); // 根据条件加载不同模块 let database; if (process.env.DB_TYPE === 'mongodb') { database = require('./database/mongodb'); } else { database = require('./database/mysql'); } 2. 运行时动态引用 // 动态加载插件 const pluginName = 'emailPlugin'; const plugin = require(`./plugins/${pluginName}`); // 批量引用 const fs = require('fs'); const path = require('path'); const modelsDir = path.join(__dirname, 'models'); const models = {}; fs.readdirSync(modelsDir).forEach(file => { if (file.endsWith('.js')) { const modelName = path.basename(file, '.js'); models[modelName] = require(path.join(modelsDir, file)); } }); console.log(models); // { User: [Class], Product: [Class], ... } ⚠️ 注意事项和最佳实践 1. 循环依赖问题 // ❌ 避免循环依赖 // a.js const b = require('./b'); exports.name = 'A'; // b.js const a = require('./a'); // 可能导致问题 exports.name = 'B'; // ✅ 解决方案:重构代码结构 // shared.js exports.sharedFunction = function() { return 'shared'; }; // a.js const shared = require('./shared'); exports.name = 'A'; // b.js const shared = require('./shared'); exports.name = 'B'; 2. 文件扩展名处理 // 这些引用方式是等价的 const math = require('./math'); const math = require('./math.js'); // JSON 文件可以直接引用 const config = require('./config.json'); // 目录引用会查找 index.js const utils = require('./utils'); // 等同于 ./utils/index.js 3. 缓存机制 // 模块会被缓存,多次 require 返回同一个实例 const math1 = require('./math'); const math2 = require('./math'); console.log(math1 === math2); // true // 如果需要每次都获取新实例,可以清除缓存 delete require.cache[require.resolve('./math')]; const freshMath = require('./math'); 📝 总结 Node.js 中引用其他 JavaScript 文件的主要方式: require() - CommonJS 标准,Node.js 默认 import/export - ES6 模块,需要配置 相对路径 - ./ 或 ../ 开头 目录引用 - 自动查找 index.js 解构引用 - 只引用需要的部分
问:在macos上使用solidity通过hardhat框架开发一个智能合约,使用node.js,git和vscode工具,并且这些工具都未安装,请根据如上情况,修改开发一个完整智能合约的详情步骤,包括环境安装等,环境管理使用hardhat插件env-enc。
答:
macOS智能合约开发完整方案 第一步:环境准备 1.1 安装Homebrew(macOS包管理器) /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 1.2 更新系统 brew update 第二步:工具安装 2.1 安装Git # 使用Homebrew安装Git brew install git # 验证安装 git --version # 配置Git(替换为你的信息) git config --global user.name "你的姓名" git config --global user.email "你的邮箱" 2.2 安装Node.js # 方法1:直接安装Node.js brew install node # 验证安装 node --version npm --version # 方法2:使用nvm管理Node.js版本(推荐) brew install nvm echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.zshrc echo '[ -s "/opt/homebrew/share/nvm/nvm.sh" ] && \. "/opt/homebrew/share/nvm/nvm.sh"' >> ~/.zshrc source ~/.zshrc # 安装最新LTS版本 nvm install --lts nvm use --lts 2.3 安装Visual Studio Code # 使用Homebrew安装VSCode brew install --cask visual-studio-code 2.4 配置VSCode插件 打开VSCode后安装以下插件: Solidity(Juan Blanco) Hardhat for Visual Studio Code GitLens Prettier - Code formatter ES6 String HTML 第三步:项目初始化 3.1 创建项目目录 mkdir my-smart-contract cd my-smart-contract 3.2 初始化Git仓库 git init 3.3 初始化Node.js项目 npm init -y 3.4 安装Hardhat和相关依赖 # 安装Hardhat核心 npm install --save-dev hardhat # 安装Hardhat工具包 npm install --save-dev @nomicfoundation/hardhat-toolbox npm install --save-dev @nomicfoundation/hardhat-verify npm install --save-dev hardhat-gas-reporter npm install --save-dev solidity-coverage # 安装环境变量加密插件 npm install --save-dev @hardhat-env-enc/hardhat # 安装OpenZeppelin合约库 npm install @openzeppelin/contracts # 安装其他依赖 npm install --save-dev chai ethers npm install dotenv 3.5 初始化Hardhat项目 npx hardhat init 选择"Create a JavaScript project",按提示完成初始化。 3.6 配置环境变量加密 # 设置加密密码 npx hardhat env-enc set-pw # 添加环境变量 npx hardhat env-enc set PRIVATE_KEY npx hardhat env-enc set INFURA_PROJECT_ID npx hardhat env-enc set ETHERSCAN_API_KEY 第四步:配置Hardhat 4.1 更新hardhat.config.js require("@nomicfoundation/hardhat-toolbox"); require("@hardhat-env-enc/hardhat"); require("hardhat-gas-reporter"); require("solidity-coverage"); module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200, }, }, }, networks: { hardhat: { chainId: 1337, }, sepolia: { url: `https://sepolia.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, accounts: [process.env.PRIVATE_KEY], }, mainnet: { url: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`, accounts: [process.env.PRIVATE_KEY], }, }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, gasReporter: { enabled: true, currency: "USD", }, }; 第五步:智能合约开发 5.1 创建智能合约 (contracts/MyToken.sol) // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyToken is ERC20, ERC20Burnable, Ownable { uint256 private constant INITIAL_SUPPLY = 1000000 * 10**18; event TokensMinted(address indexed to, uint256 amount); event TokensBurned(address indexed from, uint256 amount); constructor( string memory name, string memory symbol, address initialOwner ) ERC20(name, symbol) Ownable(initialOwner) { _mint(initialOwner, INITIAL_SUPPLY); emit TokensMinted(initialOwner, INITIAL_SUPPLY); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); emit TokensMinted(to, amount); } function burnFrom(address account, uint256 amount) public override { super.burnFrom(account, amount); emit TokensBurned(account, amount); } function burn(uint256 amount) public override { super.burn(amount); emit TokensBurned(msg.sender, amount); } } 5.2 创建部署脚本 (scripts/deploy.js) const { ethers } = require("hardhat"); async function main() { const [deployer] = await ethers.getSigners(); console.log("Deploying contracts with account:", deployer.address); console.log("Account balance:", (await deployer.provider.getBalance(deployer.address)).toString()); const MyToken = await ethers.getContractFactory("MyToken"); const myToken = await MyToken.deploy("MyToken", "MTK", deployer.address); await myToken.waitForDeployment(); const contractAddress = await myToken.getAddress(); console.log("MyToken deployed to:", contractAddress); const totalSupply = await myToken.totalSupply(); console.log("Total supply:", ethers.formatEther(totalSupply)); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); 5.3 创建测试文件 (test/MyToken.test.js) const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MyToken", function () { let MyToken, myToken, owner, addr1, addr2; beforeEach(async function () { MyToken = await ethers.getContractFactory("MyToken"); [owner, addr1, addr2] = await ethers.getSigners(); myToken = await MyToken.deploy("MyToken", "MTK", owner.address); await myToken.waitForDeployment(); }); describe("Deployment", function () { it("Should set the right owner", async function () { expect(await myToken.owner()).to.equal(owner.address); }); it("Should assign total supply to owner", async function () { const ownerBalance = await myToken.balanceOf(owner.address); expect(await myToken.totalSupply()).to.equal(ownerBalance); }); }); describe("Transactions", function () { it("Should transfer tokens between accounts", async function () { await myToken.transfer(addr1.address, ethers.parseEther("50")); const addr1Balance = await myToken.balanceOf(addr1.address); expect(addr1Balance).to.equal(ethers.parseEther("50")); }); it("Should fail if sender doesn't have enough tokens", async function () { await expect( myToken.connect(addr1).transfer(owner.address, ethers.parseEther("1")) ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); }); }); describe("Minting", function () { it("Should allow owner to mint tokens", async function () { await myToken.mint(addr1.address, ethers.parseEther("100")); expect(await myToken.balanceOf(addr1.address)).to.equal(ethers.parseEther("100")); }); it("Should not allow non-owner to mint", async function () { await expect( myToken.connect(addr1).mint(addr2.address, ethers.parseEther("100")) ).to.be.revertedWith("Ownable: caller is not the owner"); }); }); }); 第六步:测试与部署 6.1 编译合约 npx hardhat compile 6.2 运行测试 # 运行所有测试 npx hardhat test # 运行特定测试 npx hardhat test test/MyToken.test.js # 生成覆盖率报告 npx hardhat coverage 6.3 本地部署测试 # 启动本地网络 npx hardhat node # 新终端部署到本地 npx hardhat run scripts/deploy.js --network localhost 6.4 部署到测试网络 # 检查环境变量 npx hardhat env-enc status # 部署到Sepolia测试网 npx hardhat run scripts/deploy.js --network sepolia 6.5 验证合约 npx hardhat verify --network sepolia <合约地址> "MyToken" "MTK" <所有者地址> 第七步:项目结构 my-smart-contract/ ├── contracts/ │ └── MyToken.sol ├── scripts/ │ └── deploy.js ├── test/ │ └── MyToken.test.js ├── .env.enc ├── .gitignore ├── hardhat.config.js ├── package.json └── README.md 创建.gitignore文件 node_modules/ .env .env.local .env.production coverage/ coverage.json typechain/ typechain-types/ cache/ artifacts/ .DS_Store 常见问题解决 Node.js版本问题 nvm install 18 nvm use 18 npm权限问题 sudo chown -R $(whoami) ~/.npm 网络连接问题 检查Infura项目ID 确认私钥正确设置 验证网络配置 Gas费用问题 获取测试ETH:https://sepoliafaucet.com/ 下一步建议 学习更多Solidity特性:事件、修饰符、继承 集成前端:React + ethers.js创建DApp 安全审计:使用Slither、MythX工具 部署主网:充分测试后部署到以太坊主网 监控维护:设置合约监控和升级策略 有用资源 Hardhat官方文档(https://hardhat.org/docs) Solidity官方文档(https://docs.soliditylang.org/en/v0.8.30/) OpenZeppelin合约库(https://docs.openzeppelin.com/contracts/5.x/) 以太坊开发者资源(https://ethereum.org/en/developers/) 这个完整方案涵盖了从零开始在macOS上搭建Solidity开发环境到完成智能合约开发的全过程。按照这些步骤,你将能够成功创建、测试和部署你的第一个智能合约!
问:使用 Hardhat 和 Ethers.js 在 Sepolia 测试网开发智能合约的完整流程
答:
好的!以下是完整的文档,包括从项目初始化、配置到部署的完整操作流程,以及最新详尽的疑问解答部分,所有内容均整合为一体,方便你从头到尾完整参考。 使用 Hardhat 和 Ethers.js 在 Sepolia 测试网开发和部署智能合约 目录 项目环境搭建 配置 Sepolia 测试网环境 配置 Hardhat 编写智能合约 测试智能合约 部署合约到 Sepolia 测试网 验证合约 疑问解答 1. 项目环境搭建 1.1 初始化项目 创建一个新的项目目录: mkdir my-dapp-project cd my-dapp-project 初始化 package.json 文件: npm init -y 安装 Hardhat: npm install --save-dev hardhat 初始化 Hardhat 项目: npx hardhat init 选择 Create a JavaScript project 或 Create a TypeScript project。 1.2 安装依赖 安装必要的依赖库: npm install --save-dev @nomicfoundation/hardhat-toolbox npm install ethers dotenv npm install --save-dev @openzeppelin/contracts npm install --save-dev chai @nomicfoundation/hardhat-chai-matchers 2. 配置 Sepolia 测试网环境 2.1 获取必要的资源 获取 Sepolia 测试网的 RPC URL: 注册 Alchemy、Infura 等服务,创建一个项目并选择 Sepolia 测试网。 获取 RPC URL,例如: https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY 获取测试网 ETH: 访问 Sepolia 测试网水龙头领取测试 ETH: https://sepoliafaucet.com/ https://faucet.sepolia.dev/ 导出钱包私钥: 在 MetaMask 中创建账户并导出私钥: 打开 MetaMask,选择账户。 点击右上角头像 -> 账户详情 (Account Details)。 点击 导出私钥 (Export Private Key),输入密码并复制私钥。 获取 Etherscan 的 API Key: 注册 Etherscan 账户。 点击右上角用户名 -> API Keys -> 创建一个新的 API Key。 2.2 创建 .env 文件 在项目根目录下创建 .env 文件,存储敏感信息: SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY PRIVATE_KEY=your_private_key_here ETHERSCAN_API_KEY=your_etherscan_api_key 将 .env 文件添加到 .gitignore,避免敏感信息泄露: .env 3. 配置 Hardhat 编辑 hardhat.config.js 文件,添加网络和插件的配置: require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200, }, }, }, networks: { hardhat: { chainId: 31337, }, sepolia: { url: process.env.SEPOLIA_RPC_URL, accounts: [process.env.PRIVATE_KEY], chainId: 11155111, }, }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, }; 4. 编写智能合约 创建一个简单的 ERC20 代币合约文件 contracts/MyToken.sol: // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyToken is ERC20, Ownable { constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) { _mint(msg.sender, initialSupply); } } 5. 测试智能合约 创建测试文件 test/MyToken.test.js: const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("MyToken", function () { let MyToken, myToken, owner, addr1; beforeEach(async function () { [owner, addr1] = await ethers.getSigners(); MyToken = await ethers.getContractFactory("MyToken"); myToken = await MyToken.deploy("MyToken", "MTK", ethers.parseEther("1000000")); // 1M tokens }); it("Should assign the total supply to the owner", async function () { const ownerBalance = await myToken.balanceOf(owner.address); expect(await myToken.totalSupply()).to.equal(ownerBalance); }); it("Should allow transfer of tokens", async function () { await myToken.transfer(addr1.address, ethers.parseEther("100")); expect(await myToken.balanceOf(addr1.address)).to.equal(ethers.parseEther("100")); }); }); 运行测试: npx hardhat test 6. 部署合约到 Sepolia 测试网 创建部署脚本 scripts/deploy.js: const { ethers } = require("hardhat"); async function main() { const [deployer] = await ethers.getSigners(); console.log("Deploying contract with account:", deployer.address); const MyToken = await ethers.getContractFactory("MyToken"); const myToken = await MyToken.deploy("MyToken", "MTK", ethers.parseEther("1000000")); await myToken.waitForDeployment(); console.log("Contract deployed to:", await myToken.getAddress()); } main().catch((error) => { console.error(error); process.exitCode = 1; }); 运行部署脚本: npx hardhat run scripts/deploy.js --network sepolia 7. 验证合约 在部署合约后,可以通过 Hardhat 的 verify 插件验证合约: npx hardhat verify --network sepolia <CONTRACT_ADDRESS> "MyToken" "MTK" "1000000000000000000000000" 8. 疑问解答 疑问 1:sepolia.etherscan.io 和 etherscan.io 的关系是什么?为什么在 etherscan.io 创建的 API Key 可以在 sepolia.etherscan.io 使用? 回答 etherscan.io 是以太坊主网的区块链浏览器,用于查看主网的区块、交易和账户数据。 sepolia.etherscan.io 是以太坊 Sepolia 测试网的区块链浏览器,用于查看测试网的数据。 两者的关系: 主网和测试网是两个独立的区块链网络,它们的数据(如余额、交易记录等)互不相关。 但它们都使用了 Etherscan 提供的服务,因此共享同一个 API 服务平台。 为什么 API Key 通用? Etherscan 的 API Key 是全局通用的,绑定到你的 Etherscan 账户,而不是绑定到某个特定网络。 当你在 etherscan.io 创建 API Key 时,Etherscan 会允许你通过该 Key 访问主网和所有测试网的服务。 举例说明 创建 API Key 登录 Etherscan。 点击右上角用户名 -> API Keys -> 点击 Add 创建一个新的 API Key,例如 abc123xyz。 使用 API Key 查询主网和测试网数据 查询主网账户余额: https://api.etherscan.io/api ?module=account &action=balance &address=0x1234...abcd &apikey=abc123xyz 返回结果:主网账户余额。 查询 Sepolia 测试网账户余额: https://api-sepolia.etherscan.io/api ?module=account &action=balance &address=0x1234...abcd &apikey=abc123xyz 返回结果:Sepolia 测试网账户余额。 总结 etherscan.io 和 sepolia.etherscan.io 是 Etherscan 的不同实例,分别对应主网和测试网。 API Key 是全局的,可以在任何支持的网络中使用。 疑问 2:为什么在 Hardhat 中不需要填写 Etherscan 的地址?Hardhat 是否会根据网络选择对应的 Etherscan 服务? 回答 Hardhat 的 etherscan 插件已经内置了网络与 Etherscan 地址的映射。 你只需要在 hardhat.config.js 中配置网络名称(如 mainnet 或 sepolia)和 API Key,Hardhat 会根据网络名称自动选择正确的 Etherscan 服务地址。 内置地址映射 以下是 hardhat-etherscan 插件内置的地址映射: 网络名称 Etherscan 地址 API 地址 mainnet https://etherscan.io/ https://api.etherscan.io/ sepolia https://sepolia.etherscan.io/ https://api-sepolia.etherscan.io/ goerli https://goerli.etherscan.io/ https://api-goerli.etherscan.io/ Hardhat 的工作原理 当你运行以下命令验证合约时: npx hardhat verify --network sepolia <CONTRACT_ADDRESS> Hardhat 会: 从 hardhat.config.js 中读取 sepolia 网络配置。 根据网络名称匹配内置的 Etherscan 地址映射: 服务端点:https://api-sepolia.etherscan.io/ 使用 ETHERSCAN_API_KEY 调用验证 API 上传合约源代码。 举例说明 配置 hardhat.config.js module.exports = { networks: { sepolia: { url: process.env.SEPOLIA_RPC_URL, accounts: [process.env.PRIVATE_KEY], }, }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, }; 验证合约 部署合约到 Sepolia 测试网后,运行以下命令: npx hardhat verify --network sepolia 0xYourContractAddress Hardhat 会自动选择 https://api-sepolia.etherscan.io/ 地址,完成验证。 自定义 Etherscan 地址(可选) 如果你使用的是私有链或其他 Etherscan 实例,可以手动指定 API 地址: module.exports = { etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, customChains: [ { network: "custom", chainId: 12345, urls: { apiURL: "https://custom-blockchain.io/api", browserURL: "https://custom-blockchain.io", }, }, ], }, }; 疑问 3:Sepolia 测试网和主网的账户是否需要重新创建? 回答 不需要重新创建账户。 一个以太坊账户(地址 + 私钥)可以在所有以太坊兼容网络中使用。 地址和私钥是通用的,但余额和交易记录在不同网络中是完全独立的。 MetaMask 创建账户的本质 在 MetaMask 中创建账户时,会生成一个私钥,并基于该私钥计算出一个公钥和地址。 地址的生成过程与网络无关,因此账户地址可以在所有网络中使用。 账户在主网和测试网中的表现 主网:记录主网的余额和交易记录。 测试网(如 Sepolia):记录测试网的余额和交易记录。 举例说明 创建账户 在 MetaMask 中创建一个账户,地址为 0x1234...abcd。 在主网中 切换到以太坊主网,余额显示为 10 ETH,有主网的交易记录。 在 Sepolia 测试网中 切换到 Sepolia 测试网,初始余额为 0 ETH,没有交易记录。 使用测试网水龙头(如 https://sepoliafaucet.com/)领取测试 ETH 后,余额更新为 2 Sepolia ETH。 账户表现总结 网络 地址 余额 交易记录 主网(Ethereum) 0x1234...abcd 10 ETH 主网的交易记录 Sepolia 测试网 0x1234...abcd 2 Sepolia ETH 测试网的交易记录 账户地址与余额独立 地址相同,但余额和交易记录是独立的。 Sepolia 测试网的余额和交易不会影响主网。 使用账户的步骤 在 MetaMask 中创建或导入账户。 切换到 Sepolia 测试网。 复制地址并领取测试网 ETH。 使用 Hardhat 部署或交互智能合约。
疑问4:配置SEPOLIA_RPC_URL的作用是什么?SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY里YOUR_API_KEY在哪获取?
4.1. 什么是 SEPOLIA_RPC_URL?
SEPOLIA_RPC_URL 是你连接 Sepolia 测试网的入口,它是一个 RPC(Remote Procedure Call)端点,用于与以太坊区块链节点通信。
通过这个 URL,你可以在本地调用区块链节点提供的功能,例如:
查询账户余额。
查看交易状态。
部署智能合约。
调用智能合约中的函数。
4.2. 为什么需要 SEPOLIA_RPC_URL?
以太坊网络是一个去中心化的区块链,由许多节点组成。你的本地开发环境(如 Hardhat)需要通过某个节点才能与区块链网络交互。
而 SEPOLIA_RPC_URL 提供了一个访问节点的接口,你无需自己运行一个完整的以太坊节点。
4.3. 示例:SEPOLIA_RPC_URL 的作用
场景 1:查询账户余额
如果你想查询某个账户在 Sepolia 测试网中的余额:
你需要通过 SEPOLIA_RPC_URL 连接到测试网节点,并发送 RPC 调用请求。
示例请求:
POST https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": ["0x1234...abcd", "latest"],
"id": 1
}
返回结果:
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0de0b6b3a7640000" // 1 ETH
}
场景 2:部署智能合约
当你在 Hardhat 中部署智能合约时,Hardhat 会通过 SEPOLIA_RPC_URL 将部署交易发送到 Sepolia 测试网的节点。
疑问:SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY 中 YOUR_API_KEY 是什么?
1. 什么是 YOUR_API_KEY?
YOUR_API_KEY 是你从 RPC 服务提供商(如 Alchemy、Infura 等)获取的 API 密钥。
它的作用是:
验证你是否有权限访问节点。
限制你的访问频率(例如,每分钟可以发起的请求数)。
记录你的 API 使用情况(例如请求次数、错误情况)。
2. 如何获取 YOUR_API_KEY?
方法 1:从 Alchemy 获取
注册 Alchemy
访问 Alchemy 官网(https://www.alchemy.com)。
注册一个账户并登录。
创建项目
在 Alchemy 的仪表盘中点击 Create App。
填写以下信息:
App Name: 你的项目名称(如 MySepoliaApp)。
Chain: 选择 Ethereum。
Network: 选择 Sepolia。
点击 Create App。
获取 API Key
创建项目后,进入项目的详情页面。
复制显示的 HTTP 或 WebSocket URL,例如:
https://eth-sepolia.g.alchemy.com/v2/abc123xyz
其中 abc123xyz 就是你的 API Key。
方法 2:从 Infura 获取
注册 Infura
访问 Infura 官网(https://www.infura.io/zh)。
注册一个账户并登录。
创建项目
在 Infura 的仪表盘中点击 Create New Project。
填写项目名称(如 MySepoliaApp),并选择 Ethereum。
获取 API Key
在项目详情页中找到 Endpoints。
选择 Sepolia 网络。
复制 HTTP URL,例如:
https://sepolia.infura.io/v3/abc123xyz
其中 abc123xyz 就是你的 API Key。
方法 3:从其他提供商获取
其他提供商,例如:
QuickNode
Ankr
注册并创建项目后,你可以获取类似的 RPC URL 和 API Key。
3. 示例配置
将获取到的 API URL 填入 .env 文件中:
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/abc123xyz
完整的配置和使用流程
1. 获取 API Key
选择一个 RPC 提供商(如 Alchemy、Infura)。
注册并创建项目,获取 SEPOLIA_RPC_URL。
2. 配置 .env 文件
将 SEPOLIA_RPC_URL 配置到项目的 .env 文件中:
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/abc123xyz
PRIVATE_KEY=your_private_key_here
ETHERSCAN_API_KEY=your_etherscan_api_key
3. 配置 hardhat.config.js
在 hardhat.config.js 中添加 Sepolia 网络的配置:
module.exports = {
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 11155111
}
}
};
问:智能合约入门:搞懂以太坊账户类型、交易模式和运行机制
答:
智能合约入门:搞懂以太坊账户类型、交易模式和运行机制 智能合约开发核心概念 (详细版) 一、账户类型 外部账户 (EOA - Externally Owned Account) 定义: 由私钥控制的账户,是用户与以太坊网络交互的主要方式。可以把它想象成你的银行账户,只有知道密码(私钥)的人才能控制它。 特点: 私钥控制: 拥有私钥(通常是 64 位的十六进制字符串)的人可以完全控制该账户的所有交易和操作。私钥必须妥善保管,丢失则无法找回,泄露则可能导致资产被盗。 主动性: 可以发起交易,包括: 转账 ETH 或代币给其他账户。 部署新的智能合约到区块链上。 与已经部署的智能合约进行交互,调用合约中的函数。 无需代码: 外部账户本身不存储任何代码,它的行为完全由其拥有的私钥控制。 作用: 用户入口: 用户通过外部账户与区块链应用交互,例如: 使用钱包应用(如 MetaMask、Trust Wallet)管理 ETH 和其他代币资产。 参与 DeFi 活动,如借贷、交易、质押等。 购买和交易 NFT。 合约部署者: 开发者使用外部账户部署智能合约到区块链上。 交易发起者: 用户使用外部账户发起各种类型的交易,实现不同的目的。 合约账户 (Contract Account) 定义: 由智能合约代码控制的账户,是区块链应用的核心组成部分。可以把它想象成一个自动执行的程序,它的行为完全由预先编写好的代码逻辑决定。 特点: 代码控制: 合约账户的行为由预先编写好的智能合约代码逻辑决定,不能直接通过私钥控制。 存储代码: 合约账户会存储智能合约的代码,这些代码定义了合约的行为和逻辑。代码一旦部署到区块链上,就无法修改(除非合约设计了升级机制)。 被动性: 合约账户不能主动发起交易,只能被动地响应外部账户或其他合约账户发起的交易或消息调用。 自动执行: 一旦部署到区块链上,合约代码就会自动执行,无需人工干预。但执行需要 Gas,需要由发起调用的账户支付。 作用: 实现业务逻辑: 智能合约代码定义了区块链应用的业务逻辑,例如: DeFi 应用中的借贷、交易、清算等逻辑。 NFT 应用中的铸造、转让、拍卖等逻辑。 供应链管理应用中的货物追踪、状态更新等逻辑。 自动化执行: 智能合约可以自动执行,无需人工干预,提高了效率和可靠性。
去中心化: 智能合约的执行是去中心化的,不受任何单一实体控制,保证了公平性和透明度。 总结 特征外部账户(EOA)合约账户控制者私钥智能合约代码主动性可以发起交易不能主动发起交易代码无有存储无有交易发起多种类型被动接收余额可以有余额可以有余额地址私钥生成自动生成交互方式发起交易接收交易并执行代码 二、账户交互 外部账户与合约账户交互 流程: 外部账户发起交易: 用户使用钱包应用创建一个交易,指定要调用的合约地址和函数,以及传递给函数的参数。 交易广播至以太坊网络: 钱包应用将交易签名后广播到以太坊网络中,等待矿工或验证者打包。 节点接收交易,EVM 执行对应智能合约的代码: 以太坊网络中的节点收到交易后,会使用 EVM 执行对应的智能合约代码。 合约更新状态、触发事件等: 智能合约代码执行完成后,会更新合约的状态,例如修改变量的值、存储数据等。合约还可以触发事件,将关键信息记录到区块链上。 方式: 直接调用: 外部账户直接调用合约的公共 (public) 函数。这是最常见的交互方式,例如调用 ERC-20 代币合约的 transfer 函数进行转账。 间接调用: 外部账户通过调用其他合约的函数,间接与目标合约交互。这种方式通常用于实现更复杂的逻辑,例如 DeFi 应用中的多步交易。 代理合约: 使用代理合约可以实现合约升级、访问控制等功能,外部账户通过代理合约间接调用目标合约的函数。 合约账户之间的交互 (消息调用) 定义: 合约账户可以调用其他合约账户的函数,进行交互。 这种调用也被称为内部交易或消息调用 (Message Call) 。 示例: DeFi 协议中,一个借贷合约可以调用一个预言机合约获取最新的资产价格。 一个 NFT 市场合约可以调用 NFT 合约的 transferFrom 函数进行 NFT 的转让。 合约账户转账 注意: 合约账户不能主动发起转账,只能被动地执行转账操作。
方式: 接收外部账户的转账指令(交易调用): 外部账户调用合约的函数,该函数内部执行转账操作。 接收其他合约账户的转账指令(合约交互): 一个合约调用另一个合约的函数,被调用合约执行转账操作。 根据预设条件自动触发转账(合约逻辑、定时任务、事件触发): 合约逻辑: 当满足特定条件时,合约自动执行转账操作,例如: 众筹合约达到目标金额后,自动将资金转给项目方。 DAO 合约根据投票结果自动执行资金分配。 定时任务: 使用 Chainlink Keepers 或 Gelato 等服务,定时触发合约执行转账操作。 事件触发: 监听特定事件,当事件发生时,合约自动执行转账操作。 通过预言机获取链下信息并触发转账: 合约使用 Chainlink 或其他预言机服务获取链下信息,例如价格、天气等。 根据预言机提供的信息,合约判断是否需要触发转账操作。 示例: 众筹合约:接收用户捐款,达到目标后自动将 ETH 转移到项目发起人的地址。 保险合约:当预言机报告发生特定事件(例如航班延误)时,自动向投保人支付赔偿金。 自动做市商 (AMM):根据交易对的价格变化,自动调整流动性池中的资产比例。 三、交易类型 普通交易 (Regular Transaction) 定义: 外部账户之间进行的 ETH 或代币转账。这是最简单的交易类型,用于实现价值的转移。 字段: to:接收方地址(另一个外部账户的地址)。 value:转账金额,以 Wei 为单位 (1 ETH = 10^18 Wei)。 data:可选字段,可以包含一些额外的数据,例如备注信息。 场景: 朋友间转账: 你想转给朋友一些 ETH。 支付商品/服务: 你想使用 ETH 在线上商店购买商品或服务。 交易所交易: 你想在加密货币交易所买入或卖出 ETH。
创建合约交易 (Contract Creation Transaction) 定义: 将智能合约部署到以太坊区块链上。这种交易会将智能合约的代码上传到区块链,并创建一个新的合约账户。 字段: to:为空,表示创建合约。 value:可选字段,可以包含 ETH,用于支付合约部署费用和初始化合约状态。例如,如果合约的构造函数需要接收 ETH,则需要在此字段中指定金额。 data:编译后的智能合约代码,通常是字节码 (bytecode)。 场景: 部署新合约: 你开发了一个新的智能合约,需要将其部署到以太坊上。 发行代币 (Token): 你想发行一种新的代币,例如 ERC-20 代币,需要创建一个 ERC-20 代币合约。 创建去中心化应用 (DApp): 你想开发一个 DApp,需要部署相关的智能合约,例如智能合约游戏,智能合约钱包等等。 调用合约交易 (Contract Call Transaction) 定义: 外部账户调用智能合约中的函数。这种交易会触发智能合约的执行,并根据合约代码的逻辑进行相应的操作。 字段: to:智能合约的地址。 value:可选字段,可以包含 ETH,用于支付合约调用费用(Gas),也可以用于向合约转账。例如,如果合约的某个函数需要接收 ETH,则需要在此字段中指定金额。 data:要调用的函数签名和参数。函数签名是一个哈希值,用于标识要调用的函数。参数是传递给函数的输入值。 场景: 使用 DApp: 你想使用一个 DApp,例如 Uniswap 进行代币兑换,需要调用相关的智能合约函数。 参与 DeFi: 你想参与 DeFi 借贷或质押活动,需要与智能合约进行交互。 购买 NFT: 你想购买一个 NFT,需要调用 NFT 智能合约的购买函数。 铸造NFT: 你需要调用 NFT 智能合约的mint函数去铸造NFT。 四、以太坊虚拟机 (EVM) 定义: 以太坊虚拟机(Ethereum Virtual Machine,简称 EVM)是一个 图灵完备的 虚拟计算机, 是以太坊的核心组成部分。它负责执行智能合约代码,处理交易,并维护区块链的状态。
作用: 执行智能合约代码: EVM 负责解释和执行智能合约的字节码指令。 计算状态变更: 根据智能合约的执行结果,EVM 会计算区块链的状态变更,例如账户余额的变化、合约存储的更新等。 提供安全环境: EVM 提供了一个隔离的安全环境,确保智能合约的执行不会影响到以太坊网络的其他部分。 确定性执行: EVM 执行合约必须确保执行结果的确定性,同样的输入一定会得到同样的输出。 独立于主机,确保所有节点计算结果一致: EVM 的设计使其能够在不同的操作系统和硬件平台上运行,保证了以太坊网络的一致性。 工作原理: 每个节点运行 EVM: 以太坊网络中的每个节点都运行着一个 EVM 实例。 交易消耗 Gas: 智能合约的执行需要消耗 Gas,Gas 是一种用于衡量计算资源消耗的单位。 EVM 具有规则和限制: 为了防止恶意代码和资源滥用,EVM 对智能合约的执行设置了 Gas 上限和其他限制。 更新区块链状态,生成新区块: 当 EVM 成功执行完智能合约后,会将状态变更提交到区块链,并由矿工或验证者打包成新的区块。 EVM的编译运行:首先,使用Solidity等高级语言编写智能合约;然后,使用Solidity编译器将智能合约编译成EVM字节码;最后,将编译后的字节码部署到以太坊网络,EVM负责解释和执行这些字节码。 五、以太坊客户端 定义: 以太坊客户端是实现了以太坊协议的软件,允许用户连接到以太坊网络,并与网络进行交互。简单来说,以太坊客户端就是你访问以太坊网络的入口。 作用: 同步区块链数据: 客户端会与其他节点同步区块链数据,包括区块头、交易信息等,确保本地存储的区块链数据与网络保持一致。 广播交易和区块: 客户端允许用户发起交易,并将交易广播到以太坊网络中,等待矿工或验证者打包。同时,客户端也会接收来自网络的区块信息,并将新的区块添加到本地的区块链数据中。 执行 EVM: 客户端内部集成了 EVM,用于执行智能合约代码。 遵循共识机制: 客户端会遵循以太坊的共识机制(例如 PoW 或 PoS)来验证区块的有效性,并参与区块链的维护。 常见客户端: Geth(Go-Ethereum): 使用 Go 语言编写,是以太坊官方提供的客户端,也是最常用的客户端之一。 OpenEthereum(原 Parity): 使用 Rust 语言编写,以性能和安全性著称,适合对资源占用有要求的场景。 Besu: 使用 Java 语言编写,由 Hyperledger 项目支持,面向企业级应用。 功能: 节点类型: 全节点 (Full Node): 存储完整的区块链数据,并验证所有交易和区块。全节点需要消耗大量的存储空间和计算资源,但可以提供最高的安全性和可信度。 轻节点 (Light Node): 只存储区块头信息,不存储完整的交易数据。轻节点需要依赖全节点来获取交易信息,因此对资源的要求较低,但安全性也相对较低。 归档节点 (Archive Node): 存储以太坊区块链的所有历史状态,包括每个账户在每个区块的状态。归档节点用于数据分析、审计等需要访问历史数据的场景。
钱包功能: 客户端通常会提供钱包功能,用于管理用户的 ETH 和其他代币资产,生成和管理密钥对,以及发起交易。 智能合约部署与交互: 客户端允许用户部署智能合约到以太坊网络,并与已经部署的智能合约进行交互,调用合约中的函数。 开发者工具: 有些客户端会提供开发者工具,例如命令行界面 (CLI)、API 接口等,方便开发者进行智能合约开发和调试。
六、以太坊架构分层 执行层 (Execution Layer) 定义: 以太坊网络中负责处理交易、运行智能合约和管理以太坊状态的部分。 核心功能: 交易处理 (Transaction Processing): 负责接收和验证交易,并将交易打包到区块中。 智能合约执行 (Smart Contract Execution): 负责执行智能合约的字节码指令,并根据执行结果修改以太坊的状态。 状态管理 (State Management): 负责管理以太坊的状态,包括账户余额、合约存储、代码等。 技术实现: 以太坊虚拟机 (EVM)。 区块的执行:执行层负责读取区块中的交易数据,并按照顺序执行这些交易。 共识层 (Consensus Layer) 定义: 以太坊网络中负责确定区块的有效性和维护区块链一致性的部分。
核心功能: 验证区块 (Block Validation): 负责验证区块的有效性,包括验证区块中的交易是否合法、区块头是否符合共识规则等。 维护区块链一致性 (Maintaining Blockchain Consistency): 负责维护区块链的一致性,确保所有节点都拥有相同的区块链数据。 共识机制: 权益证明 (PoS)。 技术实现: 验证者 (Validators) 和信标链 (Beacon Chain)。 验证者 (Validators): 在 PoS 机制中,验证者负责验证区块的有效性,并通过投票来决定哪个区块可以添加到区块链中。 信标链 (Beacon Chain): 以太坊 2.0 的核心组件,负责管理验证者集合,并运行 PoS 共识机制。
协作 数据传递:共识层将区块提议传递给执行层,执行层处理这些交易,并将执行后的状态根返回给共识层。 职责分离:共识层专注于区块链的共识和验证,执行层则负责智能合约的执行和状态更新。 共识层确定区块提议者,执行层负责执行交易和更新状态。 提议区块: 共识层通过PoS机制选择一个验证者来提议新的区块。 执行交易: 执行层接收到共识层传递过来的区块提议后,会执行区块中的交易,并更新以太坊的状态。 达成共识: 共识层会验证执行层执行后的状态,并与其他验证者达成共识,最终将区块添加到区块链中。 七、共识机制 工作量证明 (PoW) 概念: 通过让矿工竞争解决一个复杂的数学难题来产生新的区块,解决难题的过程需要消耗大量的计算资源,因此被称为工作量证明。 核心原理: 矿工通过不断尝试不同的随机数,计算区块头的哈希值,直到找到一个满足特定条件的哈希值,该区块才能被添加到区块链中。 优点: 简单易懂: PoW 的原理比较简单,容易实现。 抗攻击性强: 攻击者需要控制大量的计算资源才能篡改区块链数据。 缺点: 能源消耗巨大: PoW 需要消耗大量的电力,造成能源浪费。 中心化风险: 算力集中在少数矿池手中,存在中心化风险。 效率低下: PoW 的交易确认速度较慢,难以满足大规模应用的需求。 权益证明 (PoS) 概念: 通过让验证者质押一定数量的加密货币来获得验证区块的权利。 核心原理: 网络会根据验证者质押的代币数量、质押时间等因素,选择一个验证者来创建新的区块。其他验证者会对新区块进行验证,如果超过一定数量的验证者同意,该区块就会被添加到区块链中。 优点: 节能环保: PoS 不需要消耗大量的电力,更加节能环保。 去中心化: PoS 可以鼓励更多的参与者成为验证者,提高网络的去中心化程度。 效率更高: PoS 的交易确认速度更快,可以满足大规模应用的需求。 实现步骤: 质押(Staking) :用户将一定数量的以太币(ETH)存入一个特殊的合约中,成为验证者。 选举:系统会根据一定的规则(比如质押数量、在线时间等)选择一部分验证者参与区块的创建和验证。 创建区块:被选中的验证者负责创建新的区块,并将其广播到网络中。 验证区块:其他验证者会对新区块进行验证,如果验证通过,则签名确认。 达成共识:当一个区块获得足够多的签名后,就被认为是有效的,并添加到区块链中。
八、以太坊 RPC 概念: 以太坊 RPC (Remote Procedure Call) 是一种允许外部应用程序与以太坊节点进行通信的协议。通过 RPC,应用程序可以向以太坊节点发送请求,并获取区块链上的数据,或者执行智能合约。 类似于 API:可以把以太坊 RPC 理解为一种 API,它提供了一组标准的接口,应用程序可以通过这些接口与以太坊网络进行交互。 使用 JSON-RPC 协议:以太坊 RPC 使用 JSON-RPC 协议进行数据交换,JSON-RPC 是一种轻量级、基于 JSON 格式的远程过程调用协议。 工作流程: 客户端发送请求: 客户端(例如 DApp、钱包等)通过 HTTP 或 WebSocket 连接向以太坊节点发送 JSON-RPC 请求。 请求格式:JSON-RPC 请求通常包含以下字段: jsonrpc:指定 JSON-RPC 协议的版本,通常为 "2.0"。 method:指定要调用的以太坊 RPC 方法的名称,例如 eth_getBlockByNumber、eth_sendTransaction 等。 params:指定调用 RPC 方法所需的参数,参数可以是数组或对象。 id:指定请求的 ID,用于标识请求,方便客户端异步处理响应。 节点处理请求: 以太坊节点接收到客户端的请求后,会根据请求的内容进行相应的处理,例如查询区块链数据、执行智能合约等。 返回响应结果: 节点处理完请求后,会将结果封装成 JSON-RPC 响应,并返回给客户端。 响应格式:JSON-RPC 响应通常包含以下字段: jsonrpc:指定 JSON-RPC 协议的版本,通常为 "2.0"。 result:指定请求的结果,如果请求成功,则该字段包含返回的数据;如果请求失败,则该字段为空。 error:指定请求的错误信息,如果请求成功,则该字段为空;如果请求失败,则该字段包含错误代码和错误信息。 id:指定请求的 ID,与请求中的 ID 对应。 同步和异步调用:RPC 可以通过同步或异步的方式调用。在同步调用中,客户端等待服务器返回响应后才能继续执行;在异步调用中,客户端发送请求后可以继续执行其他任务,当服务器返回响应时,客户端会收到通知。 常用方法: eth_blockNumber: 获取最新区块号。 eth_getBlockByNumber: 根据区块号获取区块信息。 eth_getBlockByHash: 根据区块哈希获取区块信息。 eth_getTransactionByHash: 根据交易哈希获取交易详情。 eth_getTransactionReceipt: 根据交易哈希获取交易收据。交易收据包含交易的状态(成功或失败)、Gas 消耗量、日志信息等。 eth_getBalance: 获取指定账户的 ETH 余额。 eth_getCode: 获取指定地址的合约代码。 eth_call: 调用智能合约的只读函数(不修改状态),并返回结果。 eth_sendTransaction: 发送交易,用于转账 ETH 或调用智能合约的函数(需要消耗 Gas)。 eth_sendRawTransaction: 发送已经签名过的交易,允许离线签名交易。 eth_estimateGas: 估算执行特定交易所需的 Gas 数量。 eth_getLogs: 查询符合特定条件的事件日志。 web3_clientVersion: 获取客户端的版本信息。 连接方式: 本地节点 (Local Node): 在本地运行一个以太坊节点,例如 Geth 或 OpenEthereum,并通过 RPC 接口与节点进行通信。本地节点可以提供最高的安全性和隐私性,但需要消耗大量的计算和存储资源。 公共 RPC 服务 (Public RPC Service): 使用第三方提供的公共 RPC 服务,例如 Infura、Alchemy、QuickNode 等。公共 RPC 服务可以方便地访问以太坊网络,无需自己维护节点,但可能会存在安全和隐私风险。 安全与限制: 权限控制 (Access Control): RPC 接口可以用于执行敏感操作,例如发送交易、访问账户等,因此需要进行权限控制,防止未经授权的访问。 请求频率限制 (Rate Limiting): 为了防止滥用和 DDoS 攻击,RPC 服务通常会设置请求频率限制。如果你的应用程序需要频繁访问 RPC 服务,可能需要购买更高容量的付费服务。 CORS 策略:通常需要配置跨域资源共享(CORS)策略,以允许来自不同域的客户端访问 RPC 服务。 九、智能合约,EVM,区块链节点关系 智能合约 (Smart Contract): 是部署在区块链上的可执行代码,定义了状态和函数,用于实现特定的业务逻辑。智能合约通常使用 Solidity 等高级编程语言编写,然后编译成字节码 (bytecode),存储在区块链上。 EVM (Ethereum Virtual Machine): 是以太坊虚拟机,是执行智能合约代码的运行环境。EVM 负责解释和执行智能合约的字节码指令,并管理以太坊的状态。 区块链节点 (Blockchain Node): 是参与以太坊网络的计算机,存储着区块链的完整或部分数据,并负责验证交易、执行智能合约、参与共识机制。每个区块链节点都运行着一个以太坊客户端,例如 Geth 或 OpenEthereum。 三者关系: 智能合约运行在 EVM 中: 当用户调用智能合约的函数时,交易会被发送到以太坊网络,并由区块链节点上的 EVM 执行。 EVM 运行在区块链节点上: 每个区块链节点都运行着一个 EVM 实例,用于执行智能合约代码。 区块链节点共同维护着以太坊网络: 区块链节点之间相互通信,同步区块链数据,并共同参与共识机制,维护以太坊网络的正常运行。 总结 理解以太坊的核心概念,如外部账户、合约账户、不同类型的交易模式、EVM、区块链节点以及 RPC 通信,是进入 Web3 和区块链开发的关键。掌握这些知识,你就能构建去中心化应用,参与 DeFi 生态,探索 NFT 世界。希望本文能帮助你扫清入门障碍,踏上区块链开发的征程。请记住,理论学习与实践操作相结合,才能更好地掌握这些概念,创造出更多有价值的应用。 作者:用户749213471597 链接:https://juejin.cn/post/7472261998648098850 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
问:
你好!你的理解是完全正确的!让我为你详细解释这个架构的使用方式!😊 🌐 Alchemy RPC 服务支持的网络 ✅ 完全支持测试网和主网 Alchemy 提供了全面的网络支持: 主网 (Mainnet): Ethereum Mainnet Polygon Mainnet Arbitrum One Optimism Mainnet 测试网 (Testnets): Goerli (推荐) Sepolia (新推荐) Mumbai (Polygon 测试网) Arbitrum Goerli Optimism Goerli 🔑 Alchemy App 创建和 API Key 使用 1. 创建 App 的网络选择 // 在 Alchemy Dashboard 创建 App 时 App Name: MyDApp Description: My decentralized application Chain: Ethereum // 选择区块链 Network: Goerli // 选择具体网络 (测试网/主网) 2. API Key 对应关系 // 每个 App 的 API Key 只能用于创建时选择的网络 // 例如:为 Goerli 创建的 App,API Key 只能访问 Goerli // Goerli 测试网 App const goerliApp = { name: "MyApp-Goerli", network: "Goerli", apiKey: "your-goerli-api-key", url: "https://eth-goerli.g.alchemy.com/v2/your-goerli-api-key" }; // Mainnet 主网 App const mainnetApp = { name: "MyApp-Mainnet", network: "Mainnet", apiKey: "your-mainnet-api-key", url: "https://eth-mainnet.g.alchemy.com/v2/your-mainnet-api-key" }; ⚙️ Hardhat 配置多网络 完整的 hardhat.config.js 配置 require("@nomicfoundation/hardhat-toolbox"); require('dotenv').config(); module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200 } } }, networks: { // 本地开发网络 hardhat: { chainId: 31337 }, // Goerli 测试网 goerli: { url: `https://eth-goerli.g.alchemy.com/v2/${process.env.ALCHEMY_GOERLI_API_KEY}`, accounts: [process.env.PRIVATE_KEY], chainId: 5, gasPrice: 20000000000, // 20 gwei }, // Sepolia 测试网 sepolia: { url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_SEPOLIA_API_KEY}`, accounts: [process.env.PRIVATE_KEY], chainId: 11155111, }, // Ethereum 主网 mainnet: { url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_MAINNET_API_KEY}`, accounts: [process.env.PRIVATE_KEY], chainId: 1, gasPrice: 'auto', }, // Polygon Mumbai 测试网 mumbai: { url: `https://polygon-mumbai.g.alchemy.com/v2/${process.env.ALCHEMY_MUMBAI_API_KEY}`, accounts: [process.env.PRIVATE_KEY], chainId: 80001, }, // Polygon 主网 polygon: { url: `https://polygon-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_POLYGON_API_KEY}`, accounts: [process.env.PRIVATE_KEY], chainId: 137, } }, etherscan: { apiKey: { goerli: process.env.ETHERSCAN_API_KEY, mainnet: process.env.ETHERSCAN_API_KEY, polygon: process.env.POLYGONSCAN_API_KEY, polygonMumbai: process.env.POLYGONSCAN_API_KEY, } } }; 📝 环境变量配置 (.env) # .env 文件 # Alchemy API Keys (每个网络需要单独的 App 和 API Key) ALCHEMY_GOERLI_API_KEY=your-goerli-api-key ALCHEMY_SEPOLIA_API_KEY=your-sepolia-api-key ALCHEMY_MAINNET_API_KEY=your-mainnet-api-key ALCHEMY_MUMBAI_API_KEY=your-mumbai-api-key ALCHEMY_POLYGON_API_KEY=your-polygon-api-key # 私钥 (测试网用测试私钥,主网用真实私钥) PRIVATE_KEY=your-private-key # 区块链浏览器 API Keys ETHERSCAN_API_KEY=your-etherscan-api-key POLYGONSCAN_API_KEY=your-polygonscan-api-key 🚀 部署到不同网络 部署命令 # 部署到 Goerli 测试网 npx hardhat run scripts/deploy.js --network goerli # 部署到 Sepolia 测试网 npx hardhat run scripts/deploy.js --network sepolia # 部署到 Ethereum 主网 npx hardhat run scripts/deploy.js --network mainnet # 部署到 Polygon Mumbai 测试网 npx hardhat run scripts/deploy.js --network mumbai 智能部署脚本 // scripts/deploy.js async function main() { const [deployer] = await ethers.getSigners(); const network = hre.network.name; console.log(`Deploying to ${network} network`); console.log("Deploying contracts with account:", deployer.address); // 根据网络调整 gas 设置 let gasPrice; if (network === "mainnet") { gasPrice = ethers.utils.parseUnits("30", "gwei"); // 主网使用更高 gas } else { gasPrice = ethers.utils.parseUnits("10", "gwei"); // 测试网使用较低 gas } const MyContract = await ethers.getContractFactory("MyContract"); const myContract = await MyContract.deploy({ gasPrice: gasPrice }); await myContract.deployed(); console.log(`Contract deployed to: ${myContract.address}`); // 只在非本地网络验证合约 if (network !== "hardhat" && network !== "localhost") { console.log("Waiting for block confirmations..."); await myContract.deployTransaction.wait(6); await verify(myContract.address, []); } } 🔍 最佳实践建议 1. 开发流程 # 1. 本地开发和测试 npx hardhat test # 2. 部署到测试网验证 npx hardhat run scripts/deploy.js --network goerli # 3. 在测试网充分测试后部署到主网 npx hardhat run scripts/deploy.js --network mainnet 2. 网络切换策略 // 根据环境自动选择网络 const getNetworkConfig = () => { const env = process.env.NODE_ENV; if (env === "development") return "goerli"; if (env === "staging") return "sepolia"; if (env === "production") return "mainnet"; return "hardhat"; // 默认本地网络 }; ✅ 总结回答你的问题: ✅ 可以用于测试网和主网 - Solidity + Hardhat + Alchemy 完全支持 ✅ 网络对应关系 - 创建 App 时选择的网络,对应的 API Key 只能用于该网络 ✅ 需要为每个网络创建单独的 Alchemy App - 获得对应的 API Key 这个架构非常成熟和可靠,是目前主流的智能合约开发方案!
问:
etherscan的apiKey不是通用的吗?这里为什么会配置这么多的。
etherscan的apiKey不是通用的吗?这里为什么会配置这么多的。
etherscan: {
apiKey: {
goerli: process.env.ETHERSCAN_API_KEY,
mainnet: process.env.ETHERSCAN_API_KEY,
polygon: process.env.POLYGONSCAN_API_KEY,
polygonMumbai: process.env.POLYGONSCAN_API_KEY,
}
}
答:
你的观察很敏锐!让我为你详细解释这个配置的原因!😊 🔍 你的理解是对的! 确实,在这个配置中有一些可以简化的地方: Etherscan API Key 的通用性 // ✅ Etherscan API Key 在以下网络是通用的: etherscan: { apiKey: { // 这些都可以使用同一个 Etherscan API Key goerli: process.env.ETHERSCAN_API_KEY, // 可以用同一个 sepolia: process.env.ETHERSCAN_API_KEY, // 可以用同一个 mainnet: process.env.ETHERSCAN_API_KEY, // 可以用同一个 // 但这些需要不同的 API Key polygon: process.env.POLYGONSCAN_API_KEY, // 必须不同 polygonMumbai: process.env.POLYGONSCAN_API_KEY, // 必须不同 } } 🌐 区块链浏览器分类 1. Etherscan 系列(同一个 API Key) // 这些网络都属于 Ethereum 生态,使用 Etherscan const ethereumNetworks = { mainnet: "https://etherscan.io/", goerli: "https://goerli.etherscan.io/", sepolia: "https://sepolia.etherscan.io/", // 都可以使用同一个 ETHERSCAN_API_KEY }; 2. 其他区块链浏览器(需要不同 API Key) const otherExplorers = { // Polygon 网络使用 PolygonScan polygon: "https://polygonscan.com/", mumbai: "https://mumbai.polygonscan.com/", // BSC 网络使用 BscScan bsc: "https://bscscan.com/", bscTestnet: "https://testnet.bscscan.com/", // Arbitrum 使用 Arbiscan arbitrum: "https://arbiscan.io/", // Optimism 使用 Optimistic Etherscan optimism: "https://optimistic.etherscan.io/" }; ⚙️ 优化后的配置 简化版配置 // hardhat.config.js - 优化版本 module.exports = { etherscan: { apiKey: { // Ethereum 系列网络 - 使用同一个 API Key mainnet: process.env.ETHERSCAN_API_KEY, goerli: process.env.ETHERSCAN_API_KEY, sepolia: process.env.ETHERSCAN_API_KEY, // Polygon 系列 - 需要单独的 API Key polygon: process.env.POLYGONSCAN_API_KEY, polygonMumbai: process.env.POLYGONSCAN_API_KEY, // 其他链的示例 bsc: process.env.BSCSCAN_API_KEY, bscTestnet: process.env.BSCSCAN_API_KEY, arbitrumOne: process.env.ARBISCAN_API_KEY, optimisticEthereum: process.env.OPTIMISM_API_KEY, } } }; 更简洁的写法 // 如果只用 Ethereum 系列网络,可以这样写: module.exports = { etherscan: { // 直接指定一个 API Key,适用于所有 Ethereum 网络 apiKey: process.env.ETHERSCAN_API_KEY } }; // 或者使用对象形式但复用变量 const ETHERSCAN_KEY = process.env.ETHERSCAN_API_KEY; const POLYGONSCAN_KEY = process.env.POLYGONSCAN_API_KEY; module.exports = { etherscan: { apiKey: { mainnet: ETHERSCAN_KEY, goerli: ETHERSCAN_KEY, sepolia: ETHERSCAN_KEY, polygon: POLYGONSCAN_KEY, polygonMumbai: POLYGONSCAN_KEY, } } }; 📝 环境变量优化 .env 文件简化 # .env - 简化版本 # Ethereum 系列网络通用 ETHERSCAN_API_KEY=your-etherscan-api-key # Polygon 系列网络 POLYGONSCAN_API_KEY=your-polygonscan-api-key # 其他链(按需添加) BSCSCAN_API_KEY=your-bscscan-api-key ARBISCAN_API_KEY=your-arbiscan-api-key 🎯 实际使用建议 1. 最小化配置(只用 Ethereum) module.exports = { networks: { goerli: { /* config */ }, mainnet: { /* config */ } }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY // 简单直接 } }; 2. 多链项目配置 module.exports = { etherscan: { apiKey: { // Ethereum 生态 ...["mainnet", "goerli", "sepolia"].reduce((acc, network) => { acc[network] = process.env.ETHERSCAN_API_KEY; return acc; }, {}), // Polygon 生态 ...["polygon", "polygonMumbai"].reduce((acc, network) => { acc[network] = process.env.POLYGONSCAN_API_KEY; return acc; }, {}), } } }; 3. 动态配置 // 根据需要的网络动态生成配置 const networks = ["mainnet", "goerli", "sepolia"]; const etherscanConfig = networks.reduce((config, network) => { config[network] = process.env.ETHERSCAN_API_KEY; return config; }, {}); module.exports = { etherscan: { apiKey: etherscanConfig } }; ✅ 总结 你的观察是正确的! Etherscan API Key 确实在所有 Ethereum 网络(mainnet, goerli, sepolia)通用 PolygonScan API Key 在 Polygon 网络(polygon, mumbai)通用 配置重复 主要是为了明确性和扩展性,但确实可以简化 推荐做法: 如果只用 Ethereum 网络:直接用 apiKey: process.env.ETHERSCAN_API_KEY 如果用多链:按区块链浏览器分组配置 这样既清晰又避免了不必要的重复!

浙公网安备 33010602011771号