Web3.js 开发 SHIB 合约:从零开始到部署
准备工作
在深入区块链应用开发之前,环境配置是关键的第一步。我们需要确保系统已经安装了Node.js和npm(Node Package Manager),这是后续操作的基础。Node.js是一个基于Chrome V8引擎的JavaScript运行环境,它允许我们在服务器端运行JavaScript代码。npm则是Node.js的包管理器,类似于Python的pip或Java的Maven,它负责管理项目依赖,包括安装、更新和卸载软件包。我们将利用npm来安装Web3.js库和其他必要的依赖项,这些依赖项将帮助我们与以太坊区块链进行交互。
安装Node.js和npm: 可以从Node.js官网下载并安装最新版本的Node.js。安装过程中会同时安装npm。shib-contract
的目录。
bash mkdir shib-contract cd shib-contract
bash npm init -y
这会创建一个package.
文件,用于管理项目的依赖和脚本。
bash npm install web3
这会将Web3.js添加到你的项目的node_modules
目录,并在package.
文件中添加一个依赖项。
合约编写
我们将使用Solidity编程语言编写SHIB(柴犬币)合约的一个简化版本。这个简化版本将专注于实现ERC-20代币标准中的核心功能,包括追踪总发行量(
totalSupply
)、查询账户余额(
balanceOf
)、进行代币转移(
transfer
)以及授权他人代为转移代币(
approve
和
transferFrom
)。该合约将提供一个理解SHIB代币基本运作方式的实践案例。
创建一个名为
SHIB.sol
的Solidity文件。随后,将提供的代码复制并粘贴到该文件中。该文件将包含SHIB代币合约的Solidity源代码。
solidity pragma solidity ^0.8.0;
contract SHIB { string public name = "Shiba Inu"; string public symbol = "SHIB"; uint8 public decimals = 18; uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 _initialSupply) {
totalSupply = _initialSupply * 10**decimals;
balanceOf[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(balanceOf[msg.sender] >= _value, "Insufficient balance.");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(allowance[_from][msg.sender] >= _value, "Allowance insufficient.");
require(balanceOf[_from] >= _value, "Insufficient balance.");
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
}
这段Solidity代码定义了一个名为
SHIB
的智能合约,它实现了简化的ERC-20代币功能。该合约的核心组成部分包括:
-
name
,symbol
,decimals
: 这些状态变量定义了代币的名称(例如 "Shiba Inu")、符号(例如 "SHIB")以及小数位数(decimals
,通常为18)。decimals
决定了代币可以被分割的最小单位,对于处理代币的精度至关重要。 -
totalSupply
: 这是一个uint256
类型的状态变量,用于存储代币的总供应量。该值在合约部署时被初始化,并代表该代币存在的总量。 -
balanceOf
: 这是一个mapping
(映射),将每个以太坊地址(address
)映射到其拥有的代币余额(uint256
)。通过查询balanceOf
映射,可以确定任何给定地址持有的SHIB代币数量。 -
allowance
: 这是一个嵌套的mapping
,用于实现代币授权机制。它允许一个地址(owner
)授权另一个地址(spender
)代表其花费一定数量的代币。allowance[owner][spender]
存储了spender
被允许从owner
的账户中花费的代币数量。 -
Transfer
,Approval
: 这些是合约中定义的事件(event
)。Transfer
事件在代币从一个地址转移到另一个地址时触发,记录了转移的发送者(from
)、接收者(to
)和转移的代币数量(value
)。Approval
事件在代币所有者授权一个代理(spender
)花费其代币时触发,记录了所有者(owner
)、代理(spender
)和授权的代币数量(value
)。事件对于跟踪合约的活动和历史记录至关重要。 -
constructor
: 构造函数是一个特殊的方法,在合约部署到区块链时自动执行。在这个合约中,构造函数接收一个名为_initialSupply
的参数,表示初始发行的代币总量。它将totalSupply
设置为_initialSupply * 10**decimals
,并将所有这些代币分配给部署合约的地址(msg.sender
)。10**decimals
用于正确处理代币的小数位数。构造函数还会触发一个Transfer
事件,表明所有代币已从地址0x0
(通常表示销毁地址或系统地址)转移到合约部署者的地址。 -
transfer
:transfer
函数允许代币持有者将代币转移到另一个地址。它接收目标地址(_to
)和转移的代币数量(_value
)作为参数。该函数首先检查发送者的余额是否足够(require(balanceOf[msg.sender] >= _value, "Insufficient balance.")
)。如果余额足够,则从发送者的余额中减去_value
,并将_value
添加到接收者的余额中。它会触发一个Transfer
事件,并返回true
表示交易成功。 -
approve
:approve
函数允许代币持有者授权另一个地址(_spender
)代表其花费一定数量的代币(_value
)。该函数将allowance[msg.sender][_spender]
设置为_value
,并触发一个Approval
事件。这使得_spender
可以在以后的交易中使用transferFrom
函数代表msg.sender
转移代币。 -
transferFrom
:transferFrom
函数允许一个被授权的地址(即spender
)代表另一个地址(_from
)转移代币到目标地址(_to
)。它接收_from
、_to
和_value
作为参数。该函数首先检查spender
是否已被授权花费足够的代币(require(allowance[_from][msg.sender] >= _value, "Allowance insufficient.")
)以及_from
的余额是否足够(require(balanceOf[_from] >= _value, "Insufficient balance.")
)。如果两个条件都满足,则从_from
的余额中减去_value
,并将_value
添加到_to
的余额中。同时,它会减少allowance[_from][msg.sender]
的值,并触发一个Transfer
事件。返回true
表示交易成功。
编译合约
为了将Solidity代码部署到区块链上,必须先将其编译成字节码。我们将使用Solc,即Solidity编译器,来完成这一步骤。Solc负责将人类可读的Solidity代码转换成区块链虚拟机(如EVM)可以理解和执行的字节码。可以使用命令行工具或Web3.js等库来编译合约。这里重点介绍使用Web3.js的方式,这需要在项目中安装
solc
这个npm包。通过Web3.js集成Solc,可以在JavaScript环境中使用Solidity编译器,方便自动化编译流程,特别是在构建开发工具和自动化部署脚本时。
bash npm install solc
创建一个名为
compile.js
的文件,并将以下代码复制到其中。此文件将包含编译Solidity合约的JavaScript逻辑。
javascript const path = require('path'); const fs = require('fs'); const solc = require('solc');
const shibPath = path.resolve(__dirname, 'SHIB.sol'); const source = fs.readFileSync(shibPath, 'utf8');
var input = { language: 'Solidity', sources: { 'SHIB.sol': { content: source } }, settings: { outputSelection: { '*': { '*': [ '*' ] } } } };
const compiledContract = JSON.parse(solc.compile(JSON.stringify(input))).contracts['SHIB.sol'].SHIB; 这行代码首先将input对象转换为JSON字符串,然后使用solc.compile()方法进行编译。编译结果是一个包含所有合约信息的JSON对象。接着,我们解析这个JSON对象,并从中提取出'SHIB.sol'合约的'SHIB'部分,即编译后的合约对象,包含了ABI(Application Binary Interface)和字节码。ABI是合约接口的描述,允许外部应用与合约进行交互。字节码是合约在区块链上实际执行的代码。
module.exports = compiledContract;
运行以下命令来编译合约:
bash node compile.js
这个脚本会读取当前目录下名为
SHIB.sol
的Solidity合约文件,使用Solc编译器编译该文件,并将编译后的合约对象导出。导出的合约对象包含了合约的ABI(Application Binary Interface)和字节码,这些信息对于部署和与合约交互至关重要。ABI定义了合约的函数签名,允许外部应用知道如何调用合约的函数。字节码是将在区块链上执行的实际代码。
部署智能合约
在成功编译智能合约之后,下一步关键操作便是将其部署到区块链网络中。部署过程需要与以太坊节点进行交互,该节点作为连接区块链网络的桥梁。您可以通过多种方式搭建或连接以太坊节点,常见的选择包括使用本地模拟环境Ganache,或者连接到公开的以太坊测试网络,例如Goerli、Sepolia等。选择合适的网络取决于您的测试需求和开发阶段。
- 为了保证测试环境的安全性,通常建议开发者首先在测试网络上部署和测试合约,然后再考虑部署到主网络。
创建一个名为deploy.js
的文件,并将以下代码复制到其中。
javascript const Web3 = require('web3'); const compiledContract = require('./compile');
// Replace with your Ganache provider URL const providerUrl = 'http://127.0.0.1:7545'; const web3 = new Web3(providerUrl);
const deploy = async () => { const accounts = await web3.eth.getAccounts(); const deployerAccount = accounts[0]; // Use the first account as the deployer
console.log('Attempting to deploy from account:', deployerAccount);
const initialSupply = '1000000000'; // 1 billion SHIB
const contract = new web3.eth.Contract(compiledContract.abi);
const deploymentTx = contract.deploy({ data: compiledContract.evm.bytecode.object, arguments: [initialSupply] });
const gas = await deploymentTx.estimateGas({from: deployerAccount});
const deployedContract = await deploymentTx.send({ from: deployerAccount, gas: gas });
console.log('Contract deployed to:', deployedContract.options.address); };
deploy();
确保将providerUrl
替换为你的Ganache提供者URL。运行以下命令来部署合约:
bash node deploy.js
这个脚本会连接到Ganache,获取第一个账户作为部署者,使用编译后的合约ABI和字节码部署合约,并将合约地址打印到控制台。
与合约交互
合约成功部署后,下一步是与该合约进行交互。Web3.js是一个强大的JavaScript库,它使得我们能够在以太坊区块链上与智能合约进行交互。我们可以利用Web3.js执行诸如查询账户余额、调用合约函数(例如代币转移)等操作。以下将详细介绍如何使用Web3.js与已部署的合约进行交互。
创建一个名为
interact.js
的JavaScript文件,用于编写与合约交互的代码。将以下代码片段复制到该文件中。请务必根据你的实际环境修改代码中的参数,特别是provider URL和合约地址。
javascript const Web3 = require('web3'); const compiledContract = require('./compile'); // 替换为你的 Ganache provider URL 和已部署的合约地址 const providerUrl = 'http://127.0.0.1:7545'; const contractAddress = 'YOUR_CONTRACT_ADDRESS'; // 替换为你的已部署合约地址 const web3 = new Web3(providerUrl); const interact = async () => { // 获取以太坊账户 const accounts = await web3.eth.getAccounts(); // 使用第二个账户作为用户账户 (索引为1) const userAccount = accounts[1]; // 使用第一个账户作为部署者账户 (索引为0) const deployerAccount = accounts[0]; // 创建合约实例 const contract = new web3.eth.Contract(compiledContract.abi, contractAddress); // 获取用户账户的余额 const balance = await contract.methods.balanceOf(userAccount).call(); console.log(`Balance of ${userAccount}: ${balance}`); // 从部署者账户向用户账户转移代币 const amountToTransfer = web3.utils.toWei('1000', 'ether'); // 转移 1000 个代币 const gasEstimate = await contract.methods.transfer(userAccount, amountToTransfer).estimateGas({ from: deployerAccount }); // 发起交易 await contract.methods.transfer(userAccount, amountToTransfer).send({ from: deployerAccount, gas: Math.min(gasEstimate * 2, 1000000) // 设定gas limit,防止OutOfGas错误。通常估算的gas乘以一个系数(例如2)是一个较好的实践 }); console.log(`Transferred ${amountToTransfer} tokens from ${deployerAccount} to ${userAccount}`); // 获取更新后的用户账户余额 const updatedBalance = await contract.methods.balanceOf(userAccount).call(); console.log(`Updated balance of ${userAccount}: ${updatedBalance}`); }; interact();
请务必将代码中的
contractAddress
替换为你实际部署的合约地址。在执行代币转移操作时,考虑使用
estimateGas
来估算交易所需的gas量,并设置合理的gas limit,防止交易失败。上述代码展示了如何利用
estimateGas
来预估所需的gas,并设定最大gas limit,从而避免"out of gas"错误。
完成代码编写后,使用以下命令在终端中运行脚本,与部署的合约进行交互:
bash node interact.js
该脚本首先连接到Ganache区块链,然后获取部署者账户和用户账户的地址。接着,它会查询用户账户的初始余额,并将一定数量的代币从部署者账户转移到用户账户。脚本会打印出更新后的用户账户余额,验证代币转移是否成功。通过观察控制台输出,你可以确认与智能合约的交互是否按照预期进行。
常见问题
-
Gas不足:
在执行以太坊交易时,Gas 是衡量执行交易所需计算量的单位。如果 Gas Limit 设置过低,交易在执行过程中会因为 Gas 用尽而失败,并产生“Out of Gas”错误。为了解决这个问题,可以尝试手动增加 Gas Limit。更稳妥的方法是使用 Web3.js 提供的
estimateGas
方法预估执行交易所需的 Gas 量。这个方法会模拟交易的执行,并返回一个建议的 Gas Limit 值,确保交易能够顺利完成。请注意,Gas Price 也影响交易成本,需要根据网络拥堵情况进行调整,但 Gas Price 不会直接导致 “Gas 不足” 的错误。 - 网络连接问题: Web3.js 需要连接到以太坊节点才能与区块链进行交互。网络连接问题通常表现为无法发送交易、无法读取合约状态等。要确保 Web3.js 配置的提供者 URL (Provider URL) 指向一个可用的以太坊节点,例如 Infura、Alchemy 或者本地运行的 Ganache 节点。检查以太坊节点是否正在运行且同步状态良好。如果使用 Infura 或 Alchemy 等服务,要确认 API 密钥是否有效且账户余额充足。防火墙设置或网络代理也可能导致连接问题,需要进行相应的配置调整。可以使用 `web3.eth.net.isListening()` 方法来测试 Web3.js 是否成功连接到以太坊网络。
- 合约地址错误: 在与智能合约交互时,使用正确的合约地址至关重要。错误的合约地址会导致交易发送到错误的合约,从而导致交易失败、数据读取错误或者不可预测的行为。务必仔细核对合约地址,确保其与部署到区块链上的合约地址完全一致。可以从合约部署的交易回执、区块浏览器或者合约部署脚本中获取正确的合约地址。一些开发工具,如 Truffle 或 Hardhat,可以方便地管理和跟踪合约地址。另外,确保网络 ID 与合约部署的网络 ID 相匹配,否则即使合约地址正确,也可能因为网络不匹配而无法交互。
这个教程提供了一个基本的 Web3.js 开发 SHIB 合约的示例。你可以根据自己的需求扩展合约的功能,例如添加铸造 (Minting)、销毁 (Burning)、转账 (Transfer) 等功能,并实现更复杂的逻辑。同时,可以利用 Web3.js 提供的各种 API 与合约进行更高级的交互,例如监听合约事件、调用合约的只读函数 (View/Pure functions)、使用合约过滤器等。更进一步,可以结合前端框架 (如 React、Vue.js) 构建用户界面,方便用户与 SHIB 合约进行交互。在开发过程中,务必注意合约的安全性和代码的健壮性,避免出现漏洞导致资产损失。