赞
踩
web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:
第一步:编写合约。
第二步:编译合约(之前我们设置了自动编译)。
第三步:部署合约,部署成功后返回合约地址。
第四步:调用合约。
remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!
首先,使用web3编译合约之后,会生成bytecode和abi接口。从上图可以看出,部署合约的时候需要用到这两个东西。
如果要调用合约中的方法,那么先要找到合约实例。通过abi接口和合约地址可以找到合约实例。
所以,无论部署合约还是查找合约实例都需要使用abi。
(1)创建项目,配置nodejs环境。
参考“Goland工具开发NodeJs”的Node环境配置。
(2)安装编译器
在命令窗口中进入项目所在路径,然后执行下面命令安装solc和web3编译器。
- // 初始化项目
- npm init -y
- // 安装solc编译工具
- npm install --save solc
- // 安装web3
- npm install --save web3
(3)创建项目目录结构
创建一个contract文件夹,专门保存sol合约文件。另外再创建四个js文件。分别如下所示:
01_compile.js 编译合约
02_deploy.js 部署合约
03_instance.js 获取合约实例
04_interact.js 调用合约
创建后的目录结构如下图所示:
(4)创建合约
- pragma solidity >=0.4.22 <0.9.0;
-
- contract SimpleStorage {
- string str;
-
- constructor(string _str) public {
- str = _str;
- }
-
- function setValue(string _str) payable {
- str = _str;
- }
-
- function getValue() public view returns (string) {
- return str;
- }
- }

为了防止编写合约时候出现的人为错误,建议先在remix上创建合约并测试通过后,再拷贝到项目的contracts目录下。
solc官方提供了编译智能合约的示例,具体可以参考:https://www.npmjs.com/package/solc。
(1)打开01_compile.js文件,导入solc和fs
- let solc = require('solc')
- let fs = require('fs')
(2)读取合约代码
let contractCode = fs.readFileSync('./contracts/SimpleStorage.sol', 'utf-8')
(3)编译合约代码
let output = solc.compile(contractCode, 1)
output是一个json对象,其格式如下所示:
- { contracts:
- { ':SimpleStorage':
- { assembly: [Object],
- bytecode:
- '608060405234801561001057600080fd5b5060405161039238038061039283398101604052805101805161003a906000906020840190610041565b50506100dc565b82805460018160011
- 6156101000203166002900490600052602060002090601f016020900481019282601f1061008257805160ff19168380011785556100af565b828001600101855582156100af579182015b8281111561
- 00af578251825591602001919060010190610094565b506100bb9291506100bf565b5090565b6100d991905b808211156100bb57600081556001016100c5565b90565b6102a7806100eb6000396000f
- 30060806040526004361061004b5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166320965255811461005057806393a09352146100da575b6000
- 80fd5b34801561005c57600080fd5b50610065610135565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009f57818101518382015260200161008
- 7565b50505050905090810190601f1680156100cc5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100e657600080fd5b506040805160
- 206004803580820135601f81018490048402850184019095528484526101339436949293602493928401919081908401838280828437509497506101cc9650505050505050565b005b6000805460408
- 0516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101c15780601f1061019657610100808354040283
- 5291602001916101c1565b820191906000526020600020905b8154815290600101906020018083116101a457829003601f168201915b505050505090505b90565b80516101df9060009060208401906
- 101e3565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061022457805160ff1916838001178555610251565b828001600101
- 85558215610251579182015b82811115610251578251825591602001919060010190610236565b5061025d929150610261565b5090565b6101c991905b8082111561025d57600081556001016102675
- 600a165627a7a723058209a431495f0223bccfd15255ce1400f96f38db198124404ee19016ee697bf93d90029',
- functionHashes: [Object],
- gasEstimates: [Object],
- interface:
- '[{"constant":true,"inputs":[],"name":"getValue","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{
- "constant":false,"inputs":[{"name":"_str","type":"string"}],"name":"setValue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"
- inputs":[{"name":"_str","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]',
- ...
- } } }

从上面可以看到,output中包含了bytecode和interface(abi)的信息。
(4)执行导出
- module.exports = output['contracts'][':SimpleStorage']
- console.log('bytecode : ', output['contracts'][':SimpleStorage']['bytecode'])
- console.log('abi : ', output['contracts'][':SimpleStorage']['interface'])
(1)从“01_compile”中导入bytecode和interface
let {bytecode, interface} = require('./01_compile')
(2)创建Web3实例
- // 1.引入web3
- let Web3 = require('web3')
- // 2.创建web3实例
- let web3 = new Web3()
- // 3.设置网络
- web3.setProvider('http://localhost:7545')
http://localhost:7545是Ganache-cli客户端配置的地址。当部署合约后,可以在客户端上查询到具体的交易。
(3)创建Contract对象
let contract = new web3.eth.Contract(JSON.parse(interface))
因为interface是一个字符串,所以需要通过JSON.parse(interface)把它转换成JSON对象。
(4)部署合约
- // 合约拥有者的帐号
- const account = '0x4B249c138A04FaE2b48441cAAbde22bF32cB9613'
-
- // 部署合约
- contract.deploy({
- data : bytecode,
- arguments : ['helloworld']
- }).send({
- from : account,
- gas : '3000000',
- }).then(instance => {
- console.log("contract address : ", instance.options.address)
- })
使用deploy方法部署合约的时候需要指定两个参数。data表示bytecode,arguments是合约构造函数的参数,它是一个数组。
部署完成后,记得调用send方法发起一个创建合约的交易。同样它也指定了两个参数。from代表创建合约的帐号,gas是油的数量。因为默认的gas比较少,为了让交易创建成功,建议设置gas的值高一点。
部署成功后,可以在Ganache-cli客户端查看到新创建的合约交易。
(1)创建Web3实例
- // 引入web3
- let Web3 = require('web3')
- // 创建web3实例
- let web3 = new Web3()
- // 设置网络
- web3.setProvider('http://localhost:7545')
(2)指定abi和合约地址
- // 从01_compile.js中复制过来
- let abi = [{"constant":true,"inputs":[],"name":"getValue","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_str","type":"string"}],"name":"setValue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_str","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
- // 在02_deploy.js中生成的合约地址
- let address = '0xfC005a1aA55432E72A427383d3F5405A6D04287b'
abi和address可以查询之前步骤获取到。
(3)获取合约实例
let contractInstance = new web3.eth.Contract(abi, address)
(4)导出合约实例
- // 导出合约实例
- module.exports.contractInstance = contractInstance
- module.exports.web3 = web3
导出合约实例是为了后面调用合约时候使用。
(1)导入合约实例
let {contractInstance, web3} = require('./03_instance')
(2)调用合约方法
调用合约需要使用到两个方法:call()和send()。它们都可以发送一个交易并调用方法,但是call不会改变合约状态,而send会改变合约状态。因此,调用getValue方法时候使用call,调用setValue时候,由于会改变合约数据,所以使用send。
- let test = async() => {
- let accounts = await web3.eth.getAccounts()
- try {
- // 调用合约的get方法
- let v1 = await contractInstance.methods.getValue().call()
- console.log('v1 : ', v1)
- // 调用合约的set方法
- let res = contractInstance.methods.setValue("aaaaaaaaa").send({
- from: accounts[1],
- value: 100,
- })
- console.log('res : ', res)
- } catch (e) {
- console.log(e)
- }
- }
-
- test()

上图描述了如何通过Web3去连接以太坊的测试网络。从图上可以看出,如果要部署到测试网络,需要提供一个服务商(Provider)和账户助记词(Account Mnemonic)。
Infura是一个托管的以太坊节点集群,可以将你开发的以太坊智能合约部署到infura提供的节点上,而无需搭建自己的以太坊节点。
其实MetaMask背后就是使用了Infura作为以太坊供应商。
(1)注册供应商帐号
第一步:访问infura网站(https://infura.io),先注册一个帐号。注册完成后登录网站。
第二步:登录成功后,创建一个Project。
第三步:点击View Project按钮,选择网络为Ropsten,然后复制服务商的地址,后面需要用到。
(2)在命令窗口切换到项目路径下,然后执行下面命令安装truffle-hdwallet-provider包。
>> npm install truffle-hdwallet-provider@0.0.3 --save
(3)修改部署合约代码
第一步:打开02_deploy.js文件,引入infura包。
let hdWalletProvider = require('truffle-hdwallet-provider')
第二步:创建HDWalletProvider对象,传入两个参数:助记词和Provider的地址。
- // 助记词,从metamask中复制过来
- let mnemonic = 'letter debris ready dog window mountain front truth project bottom merit valley'
- // 提供商地址,从infura.io网站复制过来
- let providerUrl = 'https://ropsten.infura.io/v3/3d30b778a38b41df8f502d8b8e3ee37b'
- // 创建HDWalletProvider对象
- let provider = new hdWalletProvider(mnemonic, providerUrl)
第三步:设置Provider。
- // web3.setProvider('http://localhost:7545')
- web3.setProvider(provider)
第四步:修改帐号地址,这里改为动态获取帐号地址。
web3提供getAccounts方法获取帐号的api。由于该方法是一个异步方法,所以最好把它放入Promise中进行同步。
- // 5.部署合约
- /*contract.deploy({
- data : bytecode,
- arguments : ['helloworld'] // 传入合约构造函数参数
- }).send({
- from : account,
- gas : '3000000',
- }).then(instance => {
- console.log("contract address : ", instance.options.address)
- })*/
-
- let deploy = async () => {
- // 获取账户,调用该方法每次只会返回一个帐号
- let accounts = await web3.eth.getAccounts()
- // 创建合约实例
- let contractInstance = await contract.deploy({
- data : bytecode,
- arguments : ['helloworld']
- }).send({
- from : accounts[0],
- gas : '3000000',
- })
- console.log("contract instance address : ", contractInstance.options.address)
- }
-
- deploy()

(4)测试部署
首先在浏览器打开Metamask,并切换到Ropsten测试网络。
接着,找到View on Etherscan按钮,点击后在https://ropsten.etherscan.io中查看当前帐号详情。
找到最后一笔交易,点击交易ID查看交易详情。
至此部署完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。