Truora 开发教程

Truora 预言机服务中有两个角色:

  • Truora 服务运营方

    服务运运营方需要部署 Truora-ServiceTruora-Web 服务,并且部署预言机相关合约到链上,为预言机用户提供服务。

  • 预言机用户

    预言机用户需要根据自身业务,选择一个 Truora 服务运营方,并编写预言机合约(需要从服务运营方处获取预言机相关合约的地址),使用服务运营方提供的预言机服务。

开发流程

预言机服务开发的流程:

  1. 获取 预言机 相关合约地址
    • 选择一个 Truora 服务运营方,并从运营方获取到 预言机 相关合约地址
    • 如果没有运营方,可以参考:安装部署 自行搭建 Truora 服务。部署完成后,可以通过 Truora-Web 获取 预言机 相关合约地址,请参考:查询系统合约地址
  2. 开发合约
    • 编写,调试合约

开发 Truora 合约

获取链下 API 数据

用户可以参考 APISampleOracle.sol 合约实现自己的oracle业务合约。 默认支持solidity0.6版本合约。 solidity0.4solidity0.5均在 Truora-Service 同级目录。合约解析如下:

  • 用户合约需继承FiscoOracleClient合约
  1. contract APISampleOracle is FiscoOracleClient
  • 构造函数需要传入指定的Truora服务的 OracleCore合约 地址。地址可以通过前端界面或者后端接口获取。
  1. constructor(address oracleAddress) public {
  2. oracleCoreAddress = oracleAddress;
  3. }
  • 设定自己要访问的url。修改url变量赋值即可。
  1. function request() public returns (bytes32)
  2. {
  3. // Set your URL
  4. // url = "plain(https://www.random.org/integers/?num=100&min=1&max=100&col=1&base=10&format=plain&rnd=new)";
  5. url = "json(https://api.exchangerate-api.com/v4/latest/CNY).rates.JPY";
  6. bytes32 requestId = oracleQuery(oracleCoreAddress, url, timesAmount);
  7. validIds[requestId] = true;
  8. return requestId;
  9. }
  • 必须实现 __callback(bytes32 _requestId, int256 _result) 方法,用于Truora-Service服务回调获取的结果。
  • get() 方法获取本次请求结果, 可自行修改此函数, 获取结果后进行自己业务逻辑的计算。

URL格式规范

目前支持json和text/plain两种访问格式。并且链下API的url必须支持HTTPS访问(安全因素考虑)。 遵循jsonpath格式,子元素 用 “.” 表示,数组用 “[]”表示,目前只支持单个返回值; text/plain默认取第一行,也可指定数组下标取特定行。 jsonpath规范可以参考 jsonpath

//获取链下随机数API

plain(https://www.random.org/integers/?num=100&min=1&max=100&col=1&base=10&format=plain&rnd=new)

//获取人民币对日元汇率API

json(https://api.exchangerate-api.com/v4/latest/CNY).rates.JPY.rates.JPY)

// 查询某城市某天最高温度

json(https://devapi.qweather.com/v7/weather/3d?location=101280601&key=90d8a8ee98ff495694dce72e96f53a18).daily[1].tempMax.daily[1].tempMax)

业务合约参考

下面以一个简单抽奖合约为例,介绍下一个简单抽奖业务怎么使用 Truora 预言机合约。

抽奖合约LotteryOracle.sol 实现了一个简单的抽奖逻辑, 通过使用上述APISampleOracle.sol 获取随机数结果。请保证 APISampleOracle 合约的url是获取获取随机数的url。 默认支持solidity0.6版本合约。 solidity0.4solidity0.5自行修改合约第一行的编译器版本即可。合约解析如下:

  • 构造函数需要传入获取随机数合约 APISampleOracle 地址。

    1. constructor(address randomOracle) public {
    2. oracle = APISampleOracle(randomOracle);
    3. lotteryId = 0;
    4. lottery_state = LOTTERY_STATE.CLOSED;
    5. }
  • 开始抽奖函数需要传入参与者的地址。简单状态校验后,然后通过调用 APISampleOraclerequest 函数获取随机数。

    1. function start_new_lottery(address[] memory _players) public {
    2. require(lottery_state == LOTTERY_STATE.CLOSED, "can't start a new lottery yet");
    3. lottery_state = LOTTERY_STATE.OPEN;
    4. players = _players;
    5. lotteryId++;
    6. requestId = oracle.request();
    7. }
  • 获取抽奖结果函数回返回中奖者地址。pickWinner 函数获取随机数结果,并对总参与人数取余,得出中奖者地址。

    1. function pickWinner() public returns(address) {
    2. require(oracle.checkIdFulfilled(requestId) == false, " oracle query has not been fulfilled!");
    3. int256 randomness = oracle.getById(requestId);
    4. uint256 index = uint256(randomness) % players.length;
    5. address winner = players[index];
    6. players = new address[](0);
    7. lottery_state = LOTTERY_STATE.CLOSED;
    8. emit Winner(lotteryId, winner, randomness);
    9. return winner;
    10. }

开发示例

部署预言机服务

部署 Truora 服务,示例使用 一键部署,部署整套开发,调试环境,请参考:安装部署

获取链下 API 数据

编写预言机合约

打开一键部署的 WeBASE-Front 页面,默认:http://{IP}:5002/WeBASE-Front/,使用部署主机的 IP 地址替换 {IP}

  • 点击左边 合约管理 –> 测试用户,创建一个调试用户 test

create_user

  • 点击左边 合约管理 –> 合约 IDE,选择 solidity 版本,上传模板合约,包括以下 五个 合约:
  1. FiscoOracleClient.sol
  2. OracleCore.sol
  3. OracleCoreInterface.sol
  4. Ownable.sol
  5. SafeMath.sol

switch_solidity

  • 确认后,选择上传目录,此处选择根目录 /

upload_solidity_files

重要

  • 注意需要上传 五个 合约文件。

  • 合约 IDE 中,创建一个 APISampleOracle 合约,继承 FiscoOracleClient 合约,如下

create_solidity_demo

代码如下:

  1. pragma solidity ^0.6.0;
  2. import "./FiscoOracleClient.sol";
  3. // 继承 FiscoOracleClient 合约
  4. contract APISampleOracle is FiscoOracleClient {
  5. //指定处理的 oracle core 合约
  6. address private oracleCoreAddress;
  7. // 放大倍数
  8. uint256 private timesAmount = 10 ** 18;
  9. // 历史请求结果
  10. mapping(bytes32 => int256) private resultMap;
  11. mapping(bytes32 => bool) private validIds;
  12. // 请求结果
  13. int256 public result;
  14. // 默认的链下 API 地址和解析协议
  15. string private url = "json(https://api.exchangerate-api.com/v4/latest/CNY).rates.JPY";
  16. constructor(address oracleAddress) public {
  17. oracleCoreAddress = oracleAddress;
  18. }
  19. function request() public returns (bytes32){
  20. bytes32 requestId = oracleQuery(oracleCoreAddress, url, timesAmount);
  21. validIds[requestId] = true;
  22. return requestId;
  23. }
  24. /**
  25. * callback from OracleCore
  26. */
  27. function __callback(bytes32 _requestId, int256 _result) public override onlyOracleCoreInvoke(_requestId){
  28. require(validIds[_requestId], "id must be not used!");
  29. resultMap[_requestId] = _result;
  30. delete validIds[_requestId];
  31. result = _result;
  32. }
  33. // 查询请求结果
  34. function get() public view returns (int256){
  35. return result;
  36. }
  37. function getById(bytes32 id) public view returns (int256){
  38. return resultMap[id];
  39. }
  40. function checkIdFulfilled(bytes32 id) public view returns (bool){
  41. return validIds[id];
  42. }
  43. function setUrl(string memory _url) public {
  44. url = _url;
  45. }
  46. function getUrl() public view returns (string memory){
  47. return url;
  48. }
  49. }
获取合约地址

在部署 APISampleOracle 时,需要获取 OracleCore 合约地址,可以通过 Truora-Web 查看。

oracle-core-address

如果需要使用 RESTful 接口获取,请参考:OracleCore 合约地址查询接口

部署合约

选择 APISampleOracle 合约文件,依次点击 保存 –> 编译 编译合约。

save_compile_demo

点击 部署 按钮,部署 APISampleOracle 合约,选择刚刚创建的测试用户 test,输入 OracleCore 合约地址:

deploy_solidity

合约调用

调用 APISampleOracle 合约的 request 方法,触发预言机获取数据

call_request

调用 APISampleOracle 合约的 get 方法,查看预言机返回的结果 call_get

结果显示如下,此处是获取日元到人民币的汇率,放大 10^18 倍的结果:

show_result