一、什么是web3.js
最好的教程:官方文档
web3.js是开发以太坊去中心化应用(DApp)必备的JavaScript库,提供了用于与geth通讯的JavaScript API,web3.js使用了JSON-RPC协议与geth进行通信。
JSON-RPC是一个无状态,轻量级的远程调用协议(RPC),允许使用http,socket等协议进行通讯,使用JSON作为数据格式。
Web3.js可以与所有支持JSON-RPC的节点进行通信,包括以太坊生态中的其他节点,如Whisper(一个集成以太坊的非实时性消息系统)和Swarm(一个去中心化的存储系统)
Web3.js无法直接连接到以太坊网络, 只能连接到以太坊节点,如:geth
二、为什么要使用web3.js
以太坊只提供JSON-RPC接口,Web.js是JavaScript的一个封装了以太坊JSON-RPC的API的库。
JSON-RPC API
Web.js API
使用原生JSON-RPC API调用不方便,两种方式对比如下
目的:获取当前区块链上的区块数量。
{“id”:1,“jsonrpc”:“2.0”,“result”:[“0xd5957914c31e1d785ccc58237d065dd25c61c4d0”,“0x18cec83129c0a766012a26863419640cd5f89400”,“0xdd2c462a89c8ca5921f2d69d4171acc3384c9d4c”,“0x7ff040debdc47b931fe2b49b5a2be4a6df60e0db”,“0xd2e37f99c1f4034108ac5fce4734dec307a95a2c”,“0x700881f1ee835c9ad319832f473d714168ce1c20”,“0x436ec2513d3ba38ff8d0ee172b6052e035d66616”,“0xea17d75d85300523947dc363376fd04c72e394a4”,“0x93f270195eb32d454f5a08add05b42a32af57420”,“0x4d6a14505808b29e416106cd7f2029463920c8d3”]}
图示:
image-20181024094328652
image-20181024095359726
我们可以看出,使用web3.js库更方便。
三、web3与以太坊关系图示
四、安装web3
旧版本(0.xx,如0.2.1)
官方文档:https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethaccounts
特点:支持同步调用,返回值不是promise,需要使用回调函数方式调用
举例:使用MetaMask查看同步异步
旧版本的返回值不是promise,无法使用.then或者await方式获取,只能使用回调方式。
image-20181024104357602
新版本(1.0,授课版本)
官方文档:https://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#eth-accounts
特点:不支持同步调用,返回值都是promise,也支持回调方式调用
image-20181024114241639
五、we3包含模块
图示
image-20181024102849309
讲授
eth
utils
六、web3工作模式
概述
image-20181127140741893
细节
Web3 大写的Web3是构造函数。(直接看函数原型)
通过构造函数生成web3的实例。
web3的实例需要装入provider电话卡(联通卡,移动卡,电信卡) 才可以去以太坊网络进行交互。
web3实例可以不填写provider,那么只能测试web3的基本信息,如version,以及一些工具函数,但是只有指定了provider(可以通过setProvider方法),才可以和特定的区块链进行交互,这就像是电话服务商一样。
metamask本身就是web3实例,所以直接在浏览器就可以输入web3.version,版本比较老,因为metamask对老版本的修改比较多,依赖较多,不敢轻易修改,通过设置它的url可以连接到不同的网络。
image-20181024134704392
七、eth相关函数
// var web3 = new Web3(new Web3.providers.HttpProvider(“http://localhost:8545”));
var web3 = new Web3()
web3.setProvider(new Web3.providers.HttpProvider(“http://localhost:8545”))
//
let accounts;
web3.eth.getAccounts((err, res) => {
accounts = res
console.log(“方式一: 回调函数”)
console.log(res)
})
web3.eth.getAccounts().then(accounts => {
console.log(“方式二: then”)
console.log(accounts)
})
f1 = async () => {
accounts = await web3.eth.getAccounts()
console.log(“方式三: promise”)
console.log(accounts)
}
f1()
image-20181024155035877
获取账户余额
默认账户(defaultAccount)
见上述案例
这个属性会影响下面两个函数(from字段如果不填写,会使用默认账户)
web3.eth.sendTransaction()
web3.eth.call()
默认区块可以通过下面的属性设置,默认情况下是最新区块,可以设置的几个值:
‘latest’:最新区块
‘pending’:当前挖出的块
‘earliest’:创世区块
自定义数字:指定区块
注意,不要设置上述之外的字符串,否则会卡死。
修改该属性会影响下面几个函数:
web3.eth.getBalance()
web3.eth.getCode()
web3.eth.getTransactionCount()
web3.eth.getStorageAt()
web3.eth.call()
contract.myMethod.call()
contract.myMethod.estimateGas()
这几个函数其实都有默认参数的,拿web3.eth.getBalance()为例,函数原型为
web3.eth.getBalance(addressHexString [, defaultBlock] [, callback])
第一个参数是请求的地址
第二个参数就是默认区块,如果指定为0,那么返回值为该账户在0区块时的余额
回调函数,用于异步调用
//返回指定区块的信息
web3.eth.getBlock(48, function(error, result){
if(!error)
console.log(JSON.stringify(“no error”, result));
else
console.error(“error”, error);
})
执行结果
no error
指定回调函数时,属于异步调用。如不指定,则为同步调用,返回null
回调函数都是这种格式:
function(error, result){
//这里的result是调用函数的返回值
}
3. 发送交易(sendTransaction)
这个很有意思,函数原型如下:
web3.eth.sendTransaction(transactionObject [, callback])
向网络发送一笔交易,包含两种交易:转账交易或发布智能合约。
第一个参数时交易对象,包含如下字段:
from:这个是可选的,未填写使用默认账户(但是要注意,默认账户的默认值是undefined)
to:如果是转账交易,则必须填写。如果是创建合约,则不必填写
value:转账值,单位是wei,创建合约时转移的金额
gas
gasPrice
data: 这个比较重要,只在两个时候使用:
调用合约函数时(eth.call函数会使用这个字段),这个值是函数的参数, 16进制字节码
创建合约时,是合约的二进制码
sendTransaction与send异同
两者都能用来调用合约方法,都能转账(相同)。
sendTransaction是专门用来转账的,无需合约,eth直接能调用,send必须有合约的方法(不同)。
sendTransaction 比send多一个回调函数。
let to = ‘0x18CeC83129C0a766012a26863419640cD5F89400’;
trasferMoney = async () => {
const accounts = await web3.eth.getAccounts();
//部署到特定的网络中时,accounts只返回一个账号,不会返回10个
console.log("====== accounts : “, accounts);
console.log(”====== accounts[0] : “, accounts[0]);
//accounts[1]是undefined,metamask也一样
console.log(”====== accounts[1] : ", accounts[1]);
const res = web3.eth.sendTransaction(
{
from : accounts[0],
to : to,
value: 10 * (10 ** 18),
gas : 1000000
});
console.log("res : ", res);
}
trasferMoney();
在data字段添加欲上链数据的十六进制数据,请部署到ropsten上,ganache有bug,
data生成方式如下:
let data = Buffer.from(data).toString('hex);
4. 综合示例
let Web3 = require(‘web3’)
let web3 = new Web3(‘http://127.0.0.1:7545’)
console.log(‘version :’, web3.version)
//获取账户
let accounts
//error first callback style
//旧版本web3只支持方式一形式,不支持方式二方式三
web3.eth.getAccounts((err, res) => {
console.log(‘获取账户方式一:回调函数形式’)
if (err) {
console.log(‘err:’, err)
}
// console.log(res)
})
web3.eth.getAccounts().then(res => {
console.log(‘获取账户方式二:then形式’)
// console.log(res)
}).catch(e => {
console.log(e)
})
let f = async () => {
try {
let accounts = await web3.eth.getAccounts()
console.log(‘获取账户方式三:async/await形式’)
// console.log(accounts)
let balance1 = await web3.eth.getBalance(accounts[0])
console.log('balance1:', balance1)
//balance1: Promise { }
// let balance1 = web3.eth.getBalance(accounts[0])
let defaultAccount = web3.eth.defaultAccount
console.log('default:', defaultAccount)
web3.eth.defaultAccount = accounts[2]
console.log('new default:', web3.eth.defaultAccount)
let defaultBlock = web3.eth.defaultBlock
console.log('defaultBlock:', defaultBlock)
//由账户0向账户1转10eth
let res = await web3.eth.sendTransaction({
// from: accounts[0], //如果不指定from,那么会使用defaultAccount的值
to: accounts[1],
gas: '6000000',
value: web3.utils.toWei('10', 'ether'),
})
//修改defaultBlock
// web3.eth.defaultBlock = 3
web3.eth.defaultBlock = 'latest'
console.log('defaultBlock:', web3.eth.defaultBlock)
let balance2 = await web3.eth.getBalance(accounts[2])
console.log('balance2:', balance2)
} catch (e) {
console.log(e)
}
}
f()
八、Utils模块
因为Javascript不能正确的处理BigNumber,所以以太坊提供了BigNumber相关的api:如下面的例子:
duke ~/gitee/solidity_syntax/Inbox/web介绍$ node
a = 101010100324325345346456456456456456456
1.0101010032432535e+38
a + 1
1.0101010032432535e+38 //不准确
所以web3.js依赖,BigNmber链接:http://mikemcl.github.io/bignumber.js/
同样是上面的例子,
let BigNumber = require(‘bignumber.js’)
let v1 = 101010100324325345346456456456456456456
let v2 = 10
console.log('v1 + v2 = ', v1 + v2)
console.log('v1 * v2 = ', v1 * v2)
//bignumber.js 库中提供一个 BigNumber 的构造函数,参数必须是长度在15位以内的数字
//数字可以长,但是不准确
//输入字符串可以任意长度
let v3 = new BigNumber(‘101010100324325345346456456456456456456’)
let v4 = 10
console.log('v3 + v4 = ', v3.plus(v4).toString())
console.log('v3 * v4 = ', v3.times(v4).toString())
科学计数法:https://www.cnblogs.com/chaoguo1234/p/6268385.html
1.2345 * 10^(2)
image-20181127071016290
尾数: c
指数: e
符号: s
以太坊BN:
let BigNumber = require(‘bignumber.js’)
let Web3 = require(‘web3’)
let web3 = new Web3()
let v1 = 101010100324325345346456456456456456456
let v2 = 10
console.log(‘v1 + v2:’, v1 + v2)
let v3 = new BigNumber(‘101010100324325345346456456456456456456’)
console.log(‘v3 + v2 :’, v3.plus(10).toString())
//创建BN的时候,输入的值如果是字符,长度任意任意,如果是十进制数字,最多20位,后面的会无效
let v4 = web3.utils.toBN(‘101010100324325345346456456456456456456’)
let v5 = web3.utils.toBN(‘10’)
//运算的时候,只接受BN类型
console.log(‘v4 + v2 :’, v4.add(v5).toString())
console.log(‘相等?’)
x = new BigNumber(123.4567);
y = new BigNumber(123456.7e-3);
z = new BigNumber(x);
console.log(x.eq(y))
console.log(‘加法’)
m = new BigNumber(10101, 2);
n = new BigNumber(“ABCD”, 16);
console.log(m.plus(n))
console.log(m.plus(n).toString())
console.log(‘减法’)
x = new BigNumber(0.5)
y = new BigNumber(0.4)
console.log(0.5 - 0.4)
console.log(x.minus(y).toString())
console.log(‘乘法’)
x = new BigNumber(‘2222222222222222222222222222222222’)
y = new BigNumber(‘7777777777777777777777777777777777’, 16)
console.log(x.times(y).toString())
console.log(‘除法’)
console.log(x.div(y).toString())
console.log(x.div(y).toFixed(6).toString())
console.log(’==== x = -123.456====’)
x = new BigNumber(-123.456)
console.log(x)
console.log(“尾数x.c:”,x.c)
console.log(“指数x.e:”,x.e)
console.log(“符号x.s:”,x.s)
执行结果:
相等?
true
加法
BigNumber { s: 1, e: 4, c: [ 44002 ] }
44002
减法
0.09999999999999998
0.1
乘法
9.0338666892195811337239599484107936881562199669307755164928940478094297346e+73
除法
5.466398580833e-8
0.000000
==== x = -123.456====
BigNumber { s: -1, e: 2, c: [ 123, 45600000000000 ] }
尾数x.c: [ 123, 45600000000000 ]
指数x.e: 2
符号x.s: -1
3.utils相关
console.log(’\n将wei转换为ether, Gwei, Mwei’)
console.log(web3.utils.fromWei(‘12345567890876433’, ‘ether’))
console.log(web3.utils.fromWei(‘12345567890876433’, ‘Gwei’))
console.log(web3.utils.fromWei(‘12345567890876433’, ‘Mwei’))
console.log(’\n转换为Wei’)
console.log(web3.utils.toWei(‘1’, ‘ether’))
console.log(web3.utils.toWei(‘1’, ‘Gwei’))
console.log(web3.utils.toWei(‘1’, ‘Mwei’))
执行结果:
[duke ~/ethStudy/web3/basicApi]$ node weiEtherConvert.js
将wei转换为ether, Gwei, Mwei
0.012345567890876433
12345567.890876433
12345567890.876433
转换为Wei
1000000000000000000
1000000000
1000000
console.log(web3.utils.toHex(‘a’))
console.log(web3.utils.toHex(1234))
console.log(web3.utils.toHex({name:‘Duke’}))
//将所有传入的数据都当做字符串进行处理,然后按照ASCII的16进制返回
//如果内部有单引号,则自动转化成双引号,再在外部用单引号括起来
console.log(JSON.stringify({name:‘Duke’}))
console.log(web3.utils.toHex(’{“name”:“Duke”}’))
console.log(web3.utils.toHex(JSON.stringify({name:‘Duke’})))
console.log(web3.utils.toHex([1,2,3,4]))
console.log(web3.utils.toHex(’[1,2,3,4]’))
执行结果:
[duke ~/ethStudy/web3/basicApi]$ node toHex.js
0x61
0x4d2
0x7b226e616d65223a2244756b65227d
{“name”:“Duke”}
0x7b226e616d65223a2244756b65227d
0x7b226e616d65223a2244756b65227d
0x5b312c322c332c345d
0x5b312c322c332c345d
十六进制与ASCII转换
var Web3 = require(‘Web3’)
var web3 = new Web3()
console.log(“先将字符串’xyz’转换为ascii,然后转化为十六进制”)
var str = web3.utils.fromAscii(‘xyz’)
console.log(str)
console.log(“先将十六进制转换为ascii,然后转化为字符串”)
str = web3.utils.toAscii(‘0x78797a’)
console.log(str)
执行结果:
[duke ~/ethStudy/web3/basicApi]$ node hexAndASCII.js
先将字符串’xyz’转换为ascii,然后转化为十六进制
0x78797a
先将十六进制转换为ascii,然后转化为字符串
xyz
web3.utils.isAddress(add1)
var hash0 = web3.utils.sha3(‘abc’)
console.log(hash0)
//对结果0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45进行hash
var hash1 = (web3.utils.sha3(hash0))
console.log(hash1)
执行结果:
[duke ~/ethStudy/web3/basicApi]$ node toHash.js
0x4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45
0xb8e12eedbb60e5321db47f5a3bfeb8ec0ff6ae9af10020cc61bb8c82ae0b7b66
九、合约模块
image-20181024173112096
BYTECODE
// import Web3 from ‘web3’;
let Web3 = require(‘web3’);
//1. 获取账户
// var web3 = new Web3(new Web3.providers.HttpProvider(“http://localhost:8545”));
var web3 = new Web3()
web3.setProvider(new Web3.providers.HttpProvider(“http://localhost:8545”))
const abi = [{
“constant”: false,
“inputs”: [],
“name”: “getMessage”,
“outputs”: [{“name”: “”, “type”: “string”}],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
}, {
“constant”: false,
“inputs”: [{“name”: “message”, “type”: “string”}],
“name”: “setMessage”,
“outputs”: [],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “function”
}, {
“inputs”: [{“name”: “src”, “type”: “string”}],
“payable”: false,
“stateMutability”: “nonpayable”,
“type”: “constructor”
}];
const bytecode = ‘608060405234801561001057600080fd5b506040516103db3803806103db833981018060405281019080805182019291905050508060009080519060200190610049929190610050565b50506100f5565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009157805160ff19168380011785556100bf565b828001600101855582156100bf579182015b828111156100be5782518255916020019190600101906100a3565b5b5090506100cc91906100d0565b5090565b6100f291905b808211156100ee5760008160009055506001016100d6565b5090565b90565b6102d7806101046000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063368b877214610051578063ce6d41de146100ba575b600080fd5b34801561005d57600080fd5b506100b8600480360381019080803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919291929050505061014a565b005b3480156100c657600080fd5b506100cf610164565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561010f5780820151818401526020810190506100f4565b50505050905090810190601f16801561013c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b8060009080519060200190610160929190610206565b5050565b606060008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101fc5780601f106101d1576101008083540402835291602001916101fc565b820191906000526020600020905b8154815290600101906020018083116101df57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061024757805160ff1916838001178555610275565b82800160010185558215610275579182015b82811115610274578251825591602001919060010190610259565b5b5090506102829190610286565b5090565b6102a891905b808211156102a457600081600090555060010161028c565b5090565b905600a165627a7a72305820f1e87be02dd6e7f710dbf54444a8ae7f74d50b8e48f13501e36f55edb4e630f70029’
deploy = async () => {
try {
accounts = await web3.eth.getAccounts()
let contract = await new web3.eth.Contract(abi)
let mycontract = await contract.deploy({
data: bytecode,
arguments: [‘Hi’],
}).send({
from: accounts[0],
gas: ‘1000000’, //注意要加上单引号,否则可能会报错
})
//res就是合约实例,填充了address字段
console.log("mycontract : ", mycontract.options.address) //这个是填好的合约实例
console.log("contract : ", contract.options.address) //这个是空的
} catch (e) {
console.error(e)
}
}
deploy()
创建完合约以后,所有合约相关的数据在mycontract.option中,方法在mycontract.methods中
代码
let mycontract
deploy = async () => {
try {
accounts = await web3.eth.getAccounts()
let contract = await new web3.eth.Contract(abi)
mycontract = await contract.deploy({
data: bytecode,
arguments: [‘Hi’],
}).send({
from: accounts[0],
gas: ‘1000000’, //注意要加上单引号,否则可能会报错
})
//res就是合约实例,填充了address字段
console.log("mycontract : ", mycontract.options.address)
console.log("contract : ", contract.options.address)
//++++++++++++++++++++++++ 从这里开始 +++++++++++++++++++++++++++++
let getMsg = await mycontract.methods.getMessage().call({
from : accounts[0],
})
console.log('getMessage111 : ', getMsg)
res1 = await mycontract.methods.setMessage('Hello World!').send({
from : accounts[0],
}, console.log)
console.log('res1 : ', res1)
getMsg = await mycontract.methods.getMessage().call({
from : accounts[0],
})
console.log('getMessage222 : ', getMsg)
} catch (e) {
console.error(e)
}
}
deploy()
十、使用的API总结