1:Introduction
web3.js 是一个库集合,允许您使用 HTTP、IPC 或 WebSocket 与本地或远程以太坊节点进行交互。
2:Provider
web3.js 提供者是负责以各种方式实现与以太坊网络连接的对象,主要目的是去连接以太坊节点,以实现发送交易、数据请求、以及与合约进行交互等功能。
Provider一共有三种类型:
- HttpProvider
- WebSocketProvider
- IpcProvider
其主要作用可以参考附录。
除了有以上三种不同类型,Provider还支持各种方式去设置Provider。
//set Provider
web3.setProvider(myProvider);
web3.eth.setProvider(myProvider);
web3.Contract.setProvider(myProvider);
contractInstance.setProvider(myProvider);
对于上述的几种不同设置Provider的方式,需要遵循如下两条规则:
- 在较高级别上设置的任何Provider都将应用于所有较低级别,如:任何Provider使用的
web3.setProvider
也可以被应用到web3.eth
对象上。 - 对合约来说,
web3.Contract.setProvider
能为所有通过web3.eth.Contract
创建的合约实例Provider。
以下是一个相对完整的试例:
const { Web3 } = require('web3');
// 1:Http provider
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
// 2:Websocket provider
web3.setProvider(new Web3.providers.WebsocketProvider('ws://localhost:8546'));
// 3:IPC provider
const net = require('net');
const web3 = new Web3(
new Web3.providers.IpcProvider('/Users/myuser/Library/Ethereum/geth.ipc', net),
);
//然后我们就可以使用Provider实例去与链上数据进行交互
3:Deploying and Interacting with Smart Contracts
接下来将尝试部署合约到以太坊网络。
配置环境
在编写和部署合约之前,需要先配置环境,主要是以下三个部分:
- Node.js - Node.js 是一个 JavaScript 运行时环境,允许您在服务器端运行 JavaScript。
- npm - Node包管理器用于向公共 npm 注册表或私有 npm 注册表发布和安装包。(或者,您可以使用yarn而不是npm)
- Ganache - Ganache是用于以太坊开发的个人区块链,可让您查看智能合约在现实场景中的运作方式。
当然,也可以使用geth替换Ganache,但个人建议使用Ganache,geth的命令设置比较麻烦,速度也比较慢,相比之下,Ganache使用非常方便,但Ganache最多只能创建100个账户。
创建一个新的node项目
首先,为项目创建一个新的项目目录并导航到其中:
mkdir smart-contract-tutorial
cd smart-contract-tutorial
然后再初始化这个项目:
npm init -y
这会为项目创建一个新的package.json
文件。
编写solidity智能合约
不清楚solidity语法的话可以参考WTF学院的教程。下面是为项目创建的MyContract.sol
合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
uint256 public myNumber;
constructor(uint256 _myNumber) {
myNumber = _myNumber;
}
function setMyNumber(uint256 _myNumber) public {
myNumber = _myNumber;
}
}
编译solidity合约并获取其ABI和字节码
如果使用的Linux系统,可以通过安装solc包来实现编译合约;如果是Windows用户,使用remix来编译合约是最方便的做法。
在编译成功后,可以从此处获得合约的ABI和字节码。
使用web3js连接以太坊网络
首先,使用npm(或yarn)安装web3js
包:
npm install web3@4.0.1-rc.1
然后,创建一个index.js文件,并添加如下代码:
const { Web3 } = require('web3'); // web3.js has native ESM builds and (`import Web3 from 'web3'`)
// Set up a connection to the Ganache network
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
// Log the current block number to the console
web3.eth
.getBlockNumber()
.then(result => {
console.log('Current block number: ' + result);
})
.catch(error => {
console.error(error);
});
最后通过一下命令来测试连接:
node index.js
当然,如果在package.json
中scripts
中添加了"index": "node index.js"
命令,也可以使用如下命令运行:
npm run index
如果,运行成功,程序将在控制台输出当前区块号。
使用web3js将合约部署到以太坊网络
首先,我们创建一个deploy.js
来部署合约:
// For simplicity we use `web3` package here. However, if you are concerned with the size,
// you may import individual packages like 'web3-eth', 'web3-eth-contract' and 'web3-providers-http'.
const { Web3 } = require('web3'); // web3.js has native ESM builds and (`import Web3 from 'web3'`)
const fs = require('fs');
const path = require('path');
// Set up a connection to the Ethereum network
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
web3.eth.Contract.handleRevert = true;
// Read the bytecode from the file system
const bytecodePath = path.join(__dirname, 'MyContractBytecode.bin');
const bytecode = fs.readFileSync(bytecodePath, 'utf8');
// Create a new contract object using the ABI and bytecode
const abi = require('./MyContractAbi.json');
const MyContract = new web3.eth.Contract(abi);
async function deploy() {
const providersAccounts = await web3.eth.getAccounts();
const defaultAccount = providersAccounts[0];
console.log('deployer account:', defaultAccount);
const myContract = MyContract.deploy({
data: '0x' + bytecode,
arguments: [1],
});
// optionally, estimate the gas that will be used for development and log it
const gas = await myContract.estimateGas({
from: defaultAccount,
});
console.log('estimated gas:', gas);
try {
// Deploy the contract to the Ganache network
const tx = await myContract.send({
from: defaultAccount,
gas,
gasPrice: 10000000000,
});
console.log('Contract deployed at address: ' + tx.options.address);
// Write the Contract address to a new file
const deployedAddressPath = path.join(__dirname, 'MyContractAddress.bin');
fs.writeFileSync(deployedAddressPath, tx.options.address);
} catch (error) {
console.error(error);
}
}
deploy();
此代码从文件中读取字节码MyContractBytecode.bin,并于ABI一起创建一个新的合约对象,任何将它部署到Ganache 网络。然后我们在通过methods.myMethod.send方法来获取合约地址,并将其写入MyContractAddress.bin
文件中,以便后面调用合约时使用。
接下来,使用一下命令运行程序:
node deploy.js
如果成功的话,输出结果应该类似如下内容:
Deployer account: 0xdd5F9948B88608a1458e3a6703b0B2055AC3fF1b
Estimated gas: 142748n
Contract deployed at address: 0x16447837D4A572d0a8b419201bdcD91E6e428Df1
使用web3js与合约进行交互
首先,创建一个名为的interact.js
文件以便与合约进行交互,并为其添加以下代码:
const { Web3 } = require('web3'); // web3.js has native ESM builds and (`import Web3 from 'web3'`)
const fs = require('fs');
const path = require('path');
// Set up a connection to the Ethereum network
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
web3.eth.Contract.handleRevert = true;
// Read the contract address from the file system
const deployedAddressPath = path.join(__dirname, 'MyContractAddress.bin');
const deployedAddress = fs.readFileSync(deployedAddressPath, 'utf8');
// Read the bytecode from the file system
const bytecodePath = path.join(__dirname, 'MyContractBytecode.bin');
const bytecode = fs.readFileSync(bytecodePath, 'utf8');
// Create a new contract object using the ABI and bytecode
const abi = require('./MyContractAbi.json');
const MyContract = new web3.eth.Contract(abi, deployedAddress);
async function interact() {
const providersAccounts = await web3.eth.getAccounts();
const defaultAccount = providersAccounts[0];
try {
// Get the current value of my number
const myNumber = await MyContract.methods.myNumber().call();
console.log('my number value: ' + myNumber);
// Increment my number
const receipt = await MyContract.methods.setMyNumber(myNumber + 1n).send({
from: defaultAccount,
gas: 1000000,
gasPrice: 10000000000,
});
console.log('Transaction Hash: ' + receipt.transactionHash);
// Get the updated value of my number
const myNumberUpdated = await MyContract.methods.myNumber().call();
console.log('my number updated value: ' + myNumberUpdated);
} catch (error) {
console.error(error);
}
}
interact();
此代码使用该MyContract
对象与智能合约进行交互。并通过它获取myNumber
的当前值,递增并更新它,然后获取其更新后的值,并将其值与交易hash打印到控制台。
在代码中我们使用了两种方法,一种是methods.myMethod.call用来获取myNumber
的值,它的特点是仅从合约上获取数据,也可以通过它来调用合约函数并获取其返回值,并且,它的任何操作不会改变合约的任何状态;另一种是methods.myMethod.send来递增myNumber
的值,它与上一种方法最大的区别就是它可以改变合约的状态,包括但不限于发送交易、改变合约变量。
最后,运行以下代码与智能合约交互:
node interact.js
如过一切正常,将看见控制台输出类似如下内容:
my number value: 1
Transaction Hash: 0x9825e2a2115896728d0c9c04c2deaf08dfe1f1ff634c4b0e6eeb2f504372f927
my number updated value: 2