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和字节码。

remix.png
remix.png

使用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.jsonscripts中添加了"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

附录

  1. https://docs.web3js.org/
  2. https://learnblockchain.cn/docs/web3.js/getting-started.html
  3. https://solidity.readthedocs.io/