Web3.js 以太坊 JavaScript API

发布于 2020-01-21 11:02:52 字数 11528 浏览 1851 评论 0

为了让你的 Ðapp 运行上以太坊,一种选择是使用 Web3.js library 提供的 web3。对象。底层实现上,它通过 RPC 调用 与本地节点通信。Web3.js 可以与任何暴露了 RPC 接口的以太坊节点连接。

web3 中有 eth 对象 - web3.eth 具体来表示与以太坊区块链之间的交互。shh 对象 - web3.shh 表示 Whisper 协议的相关交互。后续我们会继续介绍其它一些 Web3 协议中的对象。可用的example can be found here

如果你想找一些更复杂的示例,可以看看这里 useful Ðapp patterns.

入门

添加web3

首先你需要将web3引入到你的工程中,通过如下步骤:

  • npm: npm install web3
  • bower: bower install web3
  • metor: meteor add ethereum:web3
  • vanilla: dist./web3.min.js

然后你需要创建一个 Web3 的实例,设置一个 provider。为了保证你不会覆盖一个已有的 provider,比如使用Mist时有内置,需要先检查是否web3实例已存在。

if (typeof web3 !== 'undefined') {
  web3 = new Web3(web3.currentProvider);
} else {
  // set the provider you want from Web3.providers
  web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

成功引入后,你现在可以使用 web3 的相关 API 了。

使用 callback

由于这套 API 被设计来与本地的 RPC 结点交互,所有函数默认使用同步的 HTTP 的请求。

如果你想发起一个异步的请求。大多数函数允许传一个跟在参数列表后的可选的回调函数来支持异步。回调函数支持 error first callback 的风格。

web3.eth.getBlock(48, function(error, result){
  if(!error)
    console.log(result)
  else
    console.error(error);
})

批量请求

可以允许将多个请求放入队列,并一次执行。

注意:批量请求并不会更快,在某些情况下,同时发起多个请求,由于是异步的,会变得更快。但这里的批量请求主要目的是用来保证请求的串行执行。

关于 web3.js 中的 Big Number 的说明

数据类型的返回结果,你将始终会得到一个BigNumber对象,因为Javascript不能正确的处理BigNumber,如下面的例子:

"101010100324325345346456456456456456456"
// "101010100324325345346456456456456456456"
101010100324325345346456456456456456456
// 1.0101010032432535e+38

所以 web3.js 依赖 BigNumber Library1,且会自动进行引入。

var balance = new BigNumber('131242344353464564564574574567456');
// or var balance = web3.eth.getBalance(someAddress);

balance.plus(21).toString(10); // toString(10) converts it to a number string
// "131242344353464564564574574567477"

下一个例子中,我们会看到,如果有20位以上的浮点值,仍会导致出错。所以,我们推荐尽量让帐户余额以wei为单位,仅仅在需要向用户展示时,才转换为其它单位。

var balance = new BigNumber('13124.234435346456466666457455567456');

balance.plus(21).toString(10); // toString(10) converts it to a number string, but can only show max 20 floating points 
// "13145.23443534645646666646" // you number would be cut after the 20 floating point

下面教程是打算在尽量牵涉可能少的以太坊的相关工具,主要使用web3.js这个以太坊提供的工具包,来完成合约的编译,发布,合约方法调用的一整个流程。一方面来了解以太坊开发到底需要什么,另一方面来对web3.js的API有个基本的了解。由于所有其它工具都或多或少的是对web3.js的底层函数的包装,所以对web3.js使用流程有个认识之后,也能更好的入门,使用相关的工具。

1. 准备工作

1.1 安装Node.js

由于我们要使用web3.js1。这里使用Node来集成web3.js模块(当然,你还可以使用其它的方式)。你可以通过参考官网文档安装2

1.1.1 Ubuntu

如果你使用ubuntu,可以使用下述命令:

//安装Node
sudo apt-get install nodejs
//安装Node的包管理器
sudo apt-get install npm

1.1.2 MAC

如果你使用Homebrew,可以使用下述命令:

//安装Node
brew install node
//安装Node的包管理器
brew install npm

1.1.3 安装检查

安装成功后,可以查看下当前的版本,确认正常安装:

$ node -v
v7.2.0

1.2 以太坊的节点

由于整个合约代码的执行需要一个虚拟机环境,所以在开始之前,我们不得不安装一个实现了以太坊虚拟机的节点。

可以选择一个轻量级的节点,比如EtherumJS TestRPC,它是一个完整的在内存中的区块链仅仅存在于你开发的设备上。它在执行交易时是实时返回,而不等待默认的出块时间,这样你可以快速验证你新写的代码,当出现错误时,也能即时反馈给你。

npm install -g ethereumjs-testrpc

安装好后,你就可以通过testrpc命令来启动了,启动与大多数以太坊节点一样,运行在localhost:8545

如果你安装geth这样的客户端也是可以的。

1.3 Web3的支持

安装web3的模块[web3]:

npm install web3

2. 合约编译

2.1 一个简单的合约

我们打算用来测试的合约如下:

pragma solidity ^0.4.0;

contract Calc{
  /*区块链存储*/
  uint count;

  /*执行会写入数据,所以需要`transaction`的方式执行。*/
  function add(uint a, uint b) returns(uint){
  count++;
  return a + b;
  }

  /*执行不会写入数据,所以允许`call`的方式执行。*/
  function getCount() constant returns (uint){
  return count;
  }
}

add()方法用来返回输入两个数据的和,并会对add()方法的调用次数进行计数。需要注意的是这个计数是存在区块链上的,对它的调用需要使用transaction

getCount()返回add()函数的调用次数。由于这个函数不会修改区块链的任何状态,对它的调用使用call就可以了。

2.2 编译合约

由于合约是使用Solidity编写,所以我们可以使用web3.eth.compile.solidity来编译合约3

//编译合约
let source = "pragma solidity ^0.4.0;contract Calc{  /*区块链存储*/  uint count;  /*执行会写入数据,所以需要`transaction`的方式执行。*/  function add(uint a, uint b) returns(uint){  count++;  return a + b;  }  /*执行不会写入数据,所以允许`call`的方式执行。*/  function getCount() returns (uint){  return count;  }}";

let calc = web3.eth.compile.solidity(source);

如果编译成功,结果如下:

{
  code: '0x606060405234610000575b607e806100176000396000f3606060405260e060020a6000350463771602f781146026578063a87d942c146048575b6000565b3460005760366004356024356064565b60408051918252519081900360200190f35b3460005760366077565b60408051918252519081900360200190f35b6000805460010190558181015b92915050565b6000545b9056',
  info: {
    source: 'pragma solidity ^0.4.0;contract Calc{  /*区块链存储*/  uint count;  /*执行会写入数据,所以需要`transaction`的方式执行。*/  function add(uint a, uint b) returns(uint){  count++;  return a + b;  }  /*执行不会写入数据,所以允许`call`的方式执行。*/  function getCount() returns (uint){  return count;  }}',
    language: 'Solidity',
    languageVersion: '0.4.6+commit.2dabbdf0.Emscripten.clang',
    compilerVersion: '0.4.6+commit.2dabbdf0.Emscripten.clang',
    abiDefinition: [
      [
        Object
      ],
      [
        Object
      ]
    ],
    userDoc: {
      methods: {
        
      }
    },
    developerDoc: {
      methods: {
        
      }
    }
  }
}

3. 发布合约

web3.js其实也像框架一样对合约的操作进行了封装。发布合约时,可以使用web3.eth.contractnew方法4

let myContractReturned = calcContract.new({
  data: deployCode,
  from: deployeAddr
}, function(err, myContract) {
  if (!err) {
    // 注意:这个回调会触发两次
    //一次是合约的交易哈希属性完成
    //另一次是在某个地址上完成部署

    // 通过判断是否有地址,来确认是第一次调用,还是第二次调用。
    if (!myContract.address) {
      console.log("contract deploy transaction hash: " + myContract.transactionHash) //部署合约的交易哈希值

      // 合约发布成功
    } else {
    }
});

部署过程中需要主要的是,new方法的回调会执行两次,第一次是合约的交易创建完成,第二次是在某个地址上完成部署。需要注意的是只有在部署完成后,才能进行方法该用,否则会报错TypeError: myContractReturned.add is not a function

4. 调用合约

由于web3.js封装了合约调用的方法。我们可以使用可以使用web3.eth.contract的里的sendTransaction来修改区块链数据。在这里有个坑,有可能会出现Error: invalid address,原因是没有传from,交易发起者的地址。在使用web3.js的API都需留意,出现这种找不到地址的,都看看from字段吧。

      //使用transaction方式调用,写入到区块链上
      myContract.add.sendTransaction(1, 2,{
        from: deployeAddr
      });

      console.log("after contract deploy, call:" + myContract.getCount.call());

需要注意的是,如果要修改区块链上的数据,一定要使用sendTransaction,这会消耗gas。如果不修改区块链上的数据,使用call,这样不会消耗gas

5. 编译/发布/调用完整源码

let Web3 = require('web3');
let web3;

if (typeof web3 !== 'undefined') {
  web3 = new Web3(web3.currentProvider);
} else {
  // set the provider you want from Web3.providers
  web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}

let from = web3.eth.accounts[0];

//编译合约
let source = "pragma solidity ^0.4.0;contract Calc{  /*区块链存储*/  uint count;  /*执行会写入数据,所以需要`transaction`的方式执行。*/  function add(uint a, uint b) returns(uint){  count++;  return a + b;  }  /*执行不会写入数据,所以允许`call`的方式执行。*/  function getCount() constant returns (uint){  return count;  }}";
let calcCompiled = web3.eth.compile.solidity(source);

console.log(calcCompiled);
console.log("ABI definition:");
console.log(calcCompiled["info"]["abiDefinition"]);

//得到合约对象
let abiDefinition = calcCompiled["info"]["abiDefinition"];
let calcContract = web3.eth.contract(abiDefinition);

//2. 部署合约

//2.1 获取合约的代码,部署时传递的就是合约编译后的二进制码
let deployCode = calcCompiled["code"];

//2.2 部署者的地址,当前取默认账户的第一个地址。
let deployeAddr = web3.eth.accounts[0];

//2.3 异步方式,部署合约
let myContractReturned = calcContract.new({
  data: deployCode,
  from: deployeAddr
}, function(err, myContract) {
  if (!err) {
    // 注意:这个回调会触发两次
    //一次是合约的交易哈希属性完成
    //另一次是在某个地址上完成部署

    // 通过判断是否有地址,来确认是第一次调用,还是第二次调用。
    if (!myContract.address) {
      console.log("contract deploy transaction hash: " + myContract.transactionHash) //部署合约的交易哈希值

      // 合约发布成功后,才能调用后续的方法
    } else {
      console.log("contract deploy address: " + myContract.address) // 合约的部署地址

      //使用transaction方式调用,写入到区块链上
      myContract.add.sendTransaction(1, 2,{
        from: deployeAddr
      });

      console.log("after contract deploy, call:" + myContract.getCount.call());
    }

    // 函数返回对象`myContractReturned`和回调函数对象`myContract`是 "myContractReturned" === "myContract",
    // 所以最终`myContractReturned`这个对象里面的合约地址属性也会被设置。
    // `myContractReturned`一开始返回的结果是没有设置的。
  }
});

//注意,异步执行,此时还是没有地址的。
console.log("returned deployed didn't have address now: " + myContractReturned.address);

//使用非回调的方式来拿到返回的地址,但你需要等待一段时间,直到有地址,建议使用上面的回调方式。
/*
setTimeout(function(){
  console.log("returned deployed wait to have address: " + myContractReturned.address);
  console.log(myContractReturned.getCount.call());
}, 20000);
*/

//如果你在其它地方已经部署了合约,你可以使用at来拿到合约对象
//calcContract.at(["0x50023f33f3a58adc2469fc46e67966b01d9105c4"]);

相关链接

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

文章 0 评论 0

云雾

文章 0 评论 0

夏尔

文章 0 评论 0

alipaysp_yxYxYl56FW

文章 0 评论 0

涙—继续流

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文