合约开放平台开发指南

1. 文档说明

1.1 阅读对象

本文档适用于基于迅雷区块链技术,进行合约应用开发的企业或者个人开发者。
指导用户进行注册、企业和个人资质认证,合约提交、测试和发布。基于智能合约技术进行应用开发。

2. 专业术语

2.1 区块链(Blockchain)

区块链是一种去中心化的分布式计算系统,主要的特点是数据永久不可篡改性、不可伪造、公开透明信任度高。核心的技术包括拜占庭容错的共识算法(PBFT)、加密技术、P2P技术等。

2.2 迅雷链(Thunderchain)

迅雷链(ThunderChain)是迅雷旗下网心科技创新打造了具备百万tps高并发、秒级确认能力的高性能区块链,并在此基础上,搭建了迅雷链开放平台,助力开发者快速开发、部署智能合约,企业或个人可以轻松将自己的产品和服务上链,更加便捷地开发区块链应用。

2.3 交易(Transaction)

区块链可以理解为一个全局共享的交易数据库系统。任何有权限的软件都可以读取区块链网络中的数据。当需要改变区块网络中的数据时,就必须发起一个被所有区块节点接受的请求,这个请求在系统中统称为交易(Transaction)。
交易具有事务性,提交到区块链,要么全部不执行,要么全部执行。一个交易执行完成后永久保存到区块链,不能再做修改和再次执行。
交易由系统中的账户(Account)发起并且签名,通过加密技术,交易只能由私钥持有人发起,其他人不能修改和伪造。这样保证了交易的真实和安全。

2.4 账户(Account)

在区块链系统中有两类账户,一个是外部账户,一个是合约账户。外部账户拥有自己独有的公私密钥,账户由这个密钥对控制。合约账户有自己的代码,账户由自己的代码控制。
账户由一个地址标识,地址长度是一样的,两类账户无差别。外部账户的地址由其公钥产生,合约地址使用创建合约账户的地址以及创建合约账户的交易数(nonce)产生。迅雷链开放平台的合约由官方地址统一部署,普通账户在迅雷区块链不能发布合约。用户的合约必须经过官方评审,由迅雷统一发布。
在系统内部,对待这两类账户是无差别的。每个账户在系统内部有一个256bits到256bits的key-value存储结构,叫做storage。每个账户有个余额叫做balance,单位是wei,可以通过发送带数值的交易到账户进行修改。

2.5 合约(Contract)

合约就是存储了代码的区块链账户,通过给这个账户发送交易实现合约调用。当前比较流行的合约编程语言是Solidity。当前开放平台支持基于EVM的Solidity语言和基于WASM的C/C++语言开发的合约。
合约内容为两个部分,数据储存和函数,数据存储着合约的状态,函数是合约对外的接口,通过调用函数实现数据查询和状态修改。
通过Solidity编写合约,编译后得到EVM字节码。通过给合约账户发送交易,实现合约调用。

2.6 Gas

Gas是区块链的付费单位,一个交易创建的时候,会指定支付一定数量的Gas。主要是为了约束交易的运算量,以及为交易执行支付费用。交易执行过程中,Gas会以一个EVM设定的规则消耗。
Gas价格(Gas price)是由交易创建者指定的一个值,交易执行需要支付的费用数量为Gas_Price*Gas。交易结束如果Gas有剩余,剩余部分会返回给创建交易的用户。如果Gas不足,交易执行会失败,为了系统安全防止泛洪攻击,交易失败的手续费不返回。Gas价格的最小单位是wei,10^18 wei = 1 迅雷链token。Gas_Price当前固定取值为10^11,暂时没有开放自由设定。

2.7 Solidity

Solidity是针对智能合约设计的一门高级编程语言,运行环境是EVM(Ethereum Virtual Machine)。语言设计实现中受到了C++/Python/JavaScript的影响。Solidity是强类型语言,支持继承、多态、接口、抽象、库、自定义数据类型等特性。Solidity支持汇编指令编程,代码编译为字节码后运行在EVM上。Solidity是当下最流行的智能合约开发语言,也是迅雷合约平台推荐和支持的语言。

3. 新手指南

3.1 迅雷链介绍

3.1.1 什么是迅雷链?

迅雷链(ThunderChain)是迅雷旗下网心科技创新打造了具备百万tps高并发、秒级确认能力的高性能区块链,并在此基础上,搭建了迅雷链开放平台,助力开发者快速开发、部署智能合约,企业或个人可以轻松将自己的产品和服务上链,更加便捷地开发区块链应用。

3.1.2 什么是迅雷链开放平台?

迅雷链开放平台是网心科技基于“迅雷链”倾力打造的区块链服务开放平台。开放迅雷十余年的分布式技术沉淀和上亿用户基础,共享迅雷链生态数百万活跃人群,以高起点领跑行业,共同构建全球领先的区块链生态。

3.1.3 什么是合约开放平台?

基于迅雷链,提供稳定、快速、低成本的智能合约区块链服务,支持金融、电商、游戏、社交等各种行业应用场景。助力开发者快速部署智能合约,企业可以轻松将自己的产品和服务上链,更加便捷地开发区块链应用。

3.2 如何注册账号及认证

3.2.1 开发者注册登录

个人或者企业开发者要使用智能合约功能前,必须先注册为平台的用户。手机号码是用户唯一标识。注册成功后,可以绑定邮箱。登录

注册流程如下:

用户注册流程

3.2.2 邮箱绑定

绑定邮箱后,方便接收消息通知,方便与官方联系。在忘记密码的情况下,可以通过邮箱重置密码。立即绑定

3.2.3 忘记密码

用户忘记密码后,无法登陆系统。可以通过手机号或者邮箱地址重置密码。

通过手机号验证重置密码

通过邮箱验证重置密码

3.2.4 个人认证

个人开发者要提交使用合约功能,必须先进行个人信息认证。在进行个人认证前,请先注册为平台用户。请准备好身份证,以及身份证正反面照片(必须包含本人头像,照片清晰)。

立即认证

3.2.5 企业认证

企业开发者需要进行企业认证,认证信息包括企业名称、营业执照扫描件、法人代表身份证扫描件或者授权书扫描件。企业认证通过后,可以进行智能合约功能使用。

立即认证

3.3 如何创建应用

智能合约是和应用关联的,在提交和使用合约之前,必须创建一个应用,否则不能使用这些功能。用户需要将应用信息提交开放平台审核。 应用分为两种类型,移动应用和web应用。移动应用需要提供可下载或验证的软件包。web应用必须提供可访问的网址以及网站注册信息。

创建移动应用

创建web应用

3.4 如何创建合约

使用开放平台创建合约之前,请先确认合约已提交测试环境并经过功能测试。

创建合约

4. 合约开发指南

4.1 平台使用约束

  1. 出于安全和法律合规,合约由官方统一审核和发布,普通账户不能发布合约。这个约束对合约的函数实现有较大影响。
  2. 合约必须满足当地法律和规范要求,官方会对提交的合约进行源码和二进制审查。合约功能正确性以及合约内部的安全由开发者保证。
  3. 智能合约是一个去中心化的软件,官方只提供合约执行平台。合约升级由软件逻辑决定,请软件支持升级。
  4. 迅雷链智能合约开发目前支持基于EVM的Solidity语言和基于WASM的C/C++编写。
  5. 出于安全和法规考虑,开发者提供的合约不能包含 selfdestruct 代码进行合约销毁。

4.2 合约开发入门

4.2.1 注意事项

  1. 在编写智能合约前,需要对区块链基础有一定的了解。(附:ethereum-overviewSolidity)
  2. Ethereum Virtual Machine 是在以太坊上为智能合约提供运行时环境的虚拟机。迅雷链开放平台兼容EVM,但需遵循官方平台的平台使用约束。
  3. 账户类型分为外部账户(普通的交易账户地址)和合约账户。
  4. 创建合约就是向目标账户地址0发送交易的过程。
  5. 合约部署和调用过程中(改变合约内部状态变量)会消耗gas,gas_price*gas是每次交易产生的手续费。

4.2.2 compile

  1. 可以使用官方合约编译IDE Catalyst编写和部署调试合约。
  2. 可以使用浏览器编译调试运行智能合约环境Remix来快速编写合约,无依赖环境配置。
  3. 使用npm包solcjs,全局安装npm i -g solc,命令行使用solc编译对应合约。
  4. 使用 truffle 的 compile 命令。
  5. 本平台推荐使用Catalyst进行开发。

Catalyst使用指南 智能合约solidity开发框架truffle。 提供了一套完善的开发、调试、编译、部署、测试的本地环境。

4.2.3 安装truffle

npm i -g truffle

[root@opennode sandai]# truffle version
Truffle v4.1.5 (core: 4.1.5)
Solidity v0.4.21 (solc-js)

4.2.3 开始

  1. 使用truffle 初始化合约工程

     mkdir simple-storage
     cd simple-storage
     truffle init
  2. 新建合约文件:可以使用 truffle create contract SimpleStorage 命令行新建,也可以直接新建文件 contract/SimpleStorage.sol

     // SimpleStorage.sol
     pragma solidity ^0.4.21;
     contract SimpleStorage {
         uint myVariable;
    
         function set(uint x) public {
             myVariable = x;
         }
    
         function get() constant public returns (uint) {
             return myVariable;
         }
     }
  3. 添加migrate脚本:可以使用truffle create migration 2_deploy_contract 命令行方式新增,也可以直接新建文件 migrations/2_deploy_contract.js

     // 2_deploy_contract.js;truffle migrate命令的执行顺序与文件名有关,所以多个部署脚本需要按照顺序命名
     var SimpleStorage = artifacts.require("SimpleStorage");
     module.exports = function(deployer) {
             deployer.deploy(SimpleStorage);
     };
  4. 执行truffle compile编译合约,编译后的合约在build文件夹下。每个合约有一个对应的json文件,内含部署所需的bytecode,abiCode等

  5. 编辑 truffle.js ,设置truffle部署合约及与区块链交互的rpc连接。

     [root@localhost opennode]# vi truffle.js
     module.exports = {
         networks: {
             development: {
                 host: "127.0.0.1",
                 port: 8545,
                 network_id: "*"
             }
         }
     };
  6. 控制台开启truffle默认的区块链环境。

     truffle develop
     Truffle Develop started at http://127.0.0.1:9545/
    
     Accounts:
     (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
     (1) 0xf17f52151ebef6c7334fad080c5704d77216b732
     (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
     (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
     (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
     (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
     (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
     (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
     (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
     (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de
    
     Private Keys:
     (0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
     (1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
     (2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
     (3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
     (4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
     (5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
     (6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
     (7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
     (8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
     (9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5
    
     Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
    
     ⚠️  Important ⚠️  : This mnemonic was created for you by Truffle. It is not secure.
     Ensure you do not use it on production blockchains, or else you risk losing funds.
    
     truffle(develop)>

    这为truffle运行合约提供了本地的区块链环境,默认生成10个账户,每个账户初始余额为100ether。 也可以使用Ganache提供的图形化界面应用,需要修改配置连接的端口。

  7. 在一个新的控制台执行truffle migrate移植部署合约(或者在truffle develop控制台执行 migrate)。

  8. 使用truffle develop测试合约代码。

     SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()})
     // 0
     SimpleStorage.deployed().then(function(instance){return instance.set(100);});
     // 输出transaction信息
     SimpleStorage.deployed().then(function(instance){return instance.get.call();}).then(function(value){return value.toNumber()});
     // 100
  9. 使用truffle test 测试合约 使用truffle create test SimpleStorage新建或直接新建文件test/SimpleStorage.test.js。

     const SimpleStorage = artifacts.require('SimpleStorage');
    
     contract('SimpleStorage', function(accounts) {
         it("should assert true", function(done) {
             var simpleStorage = SimpleStorage.deployed();
             var instance;
             simpleStorage.then(res => {
                 instance = res;
                 return instance.get()
             }).then(value => {
                 assert.equal('0', value.toNumber(), 'not equal 0')
             }).then(() => {
                 instance.set(100)
             }).then(() => {
                 return instance.get()
             }).then(value => {
                 assert.equal('100', value.toNumber(), 'not equal 100')
             })
             done();
         });
     });

    在新开的控制台里,输入truffle test ./test/SimpleStorage.test.js

  10. 使用remix测试合约 将使用truffle 开发的合约,放在remix里,可快速模拟合约的部署和调用。 remix提供了合约的编译运行环境,并可以在控制台看到合约每条交易的详细信息,如输入输出参数,签名后的方法data,交易hash等信息。支持调试。

    1. 使用compile detail,可以看到合约编译详情。包括bytecode,abi和使用web3.js快速部署的方法。 图片1.png

    2. 使用run来create 合约,控制台可查看创建合约的交易。 图片.png

4.2.4 使用truffle unbox创建可交互合约应用

上面的步骤使用基本的truffle init创建了一个可编译部署调试的合约环境。 下面使用truffle unbox创建一个新的工程,unbox为我们提供了truffle工程模板,内置了一些合约应用交互的环境依赖。 可以在truffle boxes里查看官方提供的各种模板boxes。 下面使用的是react模板.

  1. 新建工程 truf-react

     mkdir truf-react
     cd truf-react
     truffle unbox react

    unbox过程会下载解压模板,执行npm install等操作。

  2. 配置项目的truffle.js

     module.exports = {
         // See <http://truffleframework.com/docs/advanced/configuration>
         // to customize your Truffle configuration!
         networks: {
             development: {
                 host: '127.0.0.1',
                 port: '9545',
                 network_id: '*' // Match any network id
             }
         }
     };
  3. 启动一个truffle develop

  4. 修改src/App.js

     import React, { Component } from 'react'
     import SimpleStorageContract from '../build/contracts/SimpleStorage.json'
     import getWeb3 from './utils/getWeb3'
    
     import './css/oswald.css'
     import './css/open-sans.css'
     import './css/pure-min.css'
     import './App.css'
    
     const contract = require('truffle-contract')
     const simpleStorage = contract(SimpleStorageContract)
    
     class App extends Component {
         constructor(props) {
             super(props)
    
             this.state = {
                 storageValue: 0,
                 web3: null,
                 inputValue: 0,
                 address: null
             }
    
             this.changeValueHandle = this.changeValueHandle.bind(this)
             this.setHandle = this.setHandle.bind(this)
         }
    
         componentWillMount() {
             // Get network provider and web3 instance.
             // See utils/getWeb3 for more info.
    
             getWeb3
             .then(results => {
                 this.setState({
                     web3: results.web3
                 })
    
                 // Instantiate contract once web3 provided.
                 this.instantiateContract()
             })
             .catch(() => {
                 console.log('Error finding web3.')
             })
         }
    
         instantiateContract() {
             /*
             * SMART CONTRACT EXAMPLE
             *
             * Normally these functions would be called in the context of a
             * state management library, but for convenience I've placed them here.
             */
    
             this.simpleStorageSet(5)
         }
    
         changeValueHandle(event) {
             this.setState({
                 inputValue: Number(event.target.value)
             })
         }
    
         setHandle() {
             this.simpleStorageSet(this.state.inputValue)
         }
    
         simpleStorageSet(x) {
             simpleStorage.setProvider(this.state.web3.currentProvider)
    
             // Declaring this for later so we can chain functions on SimpleStorage.
             var simpleStorageInstance
    
             // Get accounts.
             this.state.web3.eth.getAccounts((error, accounts) => {
                 simpleStorage.deployed().then((instance) => {
                     simpleStorageInstance = instance
                     this.setState({ address: instance.address })
                     // Stores a given value, 5 by default.
                     return simpleStorageInstance.set(x, {from: accounts[0]})
                 }).then((result) => {
                     // Get the value from the contract to prove it worked.
                     return simpleStorageInstance.get.call(accounts[0])
                 }).then((result) => {
                     // Update state with the result.
                     return this.setState({ storageValue: result.c[0] })
                 })
             })
         }
    
         render() {
             return (
                 <div className="App">
                     <nav className="navbar pure-menu pure-menu-horizontal">
                             <a href="#" className="pure-menu-heading pure-menu-link">Truffle Box</a>
                     </nav>
    
                     <main className="container">
                         <div className="pure-g">
                             <div className="pure-u-1-1">
                                 <h1>Good to Go!</h1>
                                 <p>Your Truffle Box is installed and ready.</p>
                                 <h2>Smart Contract Example</h2>
                                 <p>If your contracts compiled and migrated successfully, below will show a stored value of 5 (by default).</p>
                                 <p>Try changing the value stored on <strong>line 59</strong> of App.js.</p>
                                 <p>The stored value is: {this.state.storageValue}</p>
                                 <p>deployed contract address: {this.state.address}</p>
                             </div>
                             <div>
                                 <input type="number" onChange={this.changeValueHandle}/>
                                 <button onClick={this.setHandle}>set</button>
                             </div>
                         </div>
                     </main>
                 </div>
             );
         }
     }
    
     export default App

    新增了合约set方法的调用。并展示了合约的address。

  5. 新打开一个控制台,执行 npm run start

  6. 浏览器打开 http://lcoalhost:3000,可看到合约的结果

  7. 通过set和输入框设置合约storedData的值。

  8. 在trufle develop里输入

     //将xxx替换为address
     SimpleStorage.at('xxxx').then(res => {return res.get()})
     // 得到BigNUmber类型的返回值,c数组里的值即为设置的storedData的值。

4.2.5 使用浏览器插件 metamask 与区块链交互

参考 http://truffleframework.com/tutorials/pet-shop

4.2.6 合约开发参考文档与工具

solidity API
truffle文档
ganache 提供本地区块链环境的图形化界面
zeppelin-solidty 致力于安全的标准化的合约框架
web3.js 以太坊封装的与区块链交互的js
Smart Contract Security Best Practices(合约安全开发指南)

4.3 合约编码方法和工具

合约的调用和查询,传入的参数是根据合约ABICode编码后函数和函数参数的data。详细可参考solidity - Application Binary Interface Specification
下面介绍几种方式取得合约ABICode方法调用时对应的编码data。

4.3.1 使用remix调用合约获取data

在使用remix执行合约方法时,每次交易都有对应的交易详情,可以在控制台看到交易信息里的函数调用的input,以及对应的decodeInput和decodeOutput。
input就是调用函数时向区块链环境发送的JSON里的data。对应开放平台,在使用合约调用时,传入的data可以直接使用remix的data。 remix input data

4.3.2 使用ethers.js实现编码和解码

ethers.js是一个以太坊的js依赖包。里面提供了根据ABICode编码函数名的方法和编码参数的静态方法。
例:

//HelloWorld.sol
pragma solidity^0.4.23;

contract HelloWorld {
    address public owner;
    string public info;

    constructor(address _owner) public {
        owner = _owner;
    }

    function saySomething(string _str) public returns(string) {
        info = _str;
        return info;
    }
}
//编码function saySomething函数的 abi
var ethers = require('ethers')

var  abi = [
    {
        "constant": false,
        "inputs": [
            {
                "name": "_str",
                "type": "string"
            }
        ],
        "name": "saySomething",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "constructor"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "info",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "owner",
        "outputs": [
            {
                "name": "",
                "type": "address"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    }
];
var interface = new ethers.Interface(abi)
var abiInstance = interface.functions[abi[0].name] // abi[0]即saySomething function的abi
return abiInstance.sighash // 0xfe6b3783

获取函数参数编码data的方法:
使用ethers.utils.ABICoder的encode方法,如

// 编码 function saySomething 的输入参数 'Hello World'的data
var abiCoder = new ethers.utils.AbiCoder()
return abiCoder.encode(['string'], ['Hello World'])
//0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000

实际调用函数的input即为:函数saySomething encode的前八位 + 函数输入参数的encode。结果为

0xfe6b37830000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000

可对比上面使用remix调用方法时的input值,两者结果一致。

合约执行结束后,info值被设置为 "Hello World"。下面请求constant方法(同8.3 Demo合约查询),根据结果解析info值。

curl -k -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xd5b0df861803a07f330868104eec92ebdcce4c79","data":"0x370158ea"}, "latest"],"id":1}' https://sandbox-blockchain.xunlei.com/call
{"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"}
var ethers = require('ethers')

var outputTypes = ['string']
var response = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"

var abiCoder = new ethers.utils.AbiCoder()
abiCoder.decode(outputTypes, response)
// [ 'Hello World' ]

decode结果根据传入的outputTypes数组长度,解析对应的参数。

5. 合约接入流程

5.1 合约提交审核

开发者开发合约完成后,需要先提交到迅雷链测试环境部署和测试。经测试验证无误后,再向开放平台提交合约,部署正式环境。经官方审核代码后才可部署正式环境。

5.2 测试环境合约提交流程

测试环境流程

5.2.1 注册测试环境开发者账号

  1. 开发者根据测试接入API调用 获取邮箱验证码 接口,验证码有效期30分钟,每个未注册邮箱24小时内最多请求3次验证码。
  2. 开发者调用 使用邮箱注册 接口,使用上一步的验证码和邮箱注册。
  3. 测试平台根据开发者接口提交的邮箱地址(必须)和验证码(必须),向开发者邮箱发送一份邮件,内含servce_id和secret。
  4. 后续所有的测试接口请求,都需要带上此邮箱地址以及由邮箱地址、service_id和secret拼接字符串的md5值。测试平台的所有接口都将校验此md5。
  5. 此接口不提供额外查询功能,如开发者信息遗失,需走客服流程找回。

5.2.2 调用充值接口

  1. 开发者根据测试接入API调用 测试账号充值 接口。
  2. 调用充值接口成功后,测试平台会向接口所提交的address账户转入1个token。
  3. 测试平台的区块链环境完全独立,所以此测试token只能用于测试环境,转入其他区块链环境无效。
  4. 每个email地址每天最多发起10次转账申请。
  5. 测试环境下需要使用测试版的迅雷链助手App,测试版迅雷链助手App下载

5.2.3 开发合约应用和上层业务

  1. 开发者开发合约应用,并通过上层业务(App, H5等实现)功能封装对合约的调用。
  2. 合约的开发可参考合约开发指南

5.2.4 部署合约到迅雷链测试环境

  1. 迅雷链测试环境可以通过Catalyst编译、部署和调用。也支持手动调用接口部署和调用。
  2. 开发者通过手动调用 合约发布 接口,部署合约到迅雷链测试环境。
  3. 发布合约API接口需要提供合约bytecode和params,byetecode为合约编译的bytecode,params是构造函数的参数经过sign后可直接拼接在bytecode后用于部署的编码。参考合约编码工具
  4. 由于合约的部署是由平台方执行的,所以在合约的constructor方法里,避免使用msg.sender,如有权限和调用者限制需用到msg.sender,可通过参数传入用户的address代替。

例:

contract HelloWorld {
  address owner;
  constructor(address ownerArg) public {
    //owner = msg.sender; 使用ownerArg代替msg.sender
    owner = ownerArg;
  }
}

5.3 正式环境合约提交流程

在接入正式环境之前,需要将合约在测试环境部署并经过完善的功能测试,才能提交正式环境的审核。
提交开放平台正式审核时,平台方会根据提交到正式环境源码和对应的bytecode做校验审核。

合约审核流程

步骤如下

  1. 注册开发平台账户
  2. 认证个人或企业开发者账户
  3. 新建应用提交审核
  4. 基于新建的应用下新建合约
  5. 提交合约内容,含测试环境合约地址、源码包、byetecode、初始化参数、abicode等信息
  6. 开放平台根据提交的bytecode以及源码包等信息来审核合约
  7. 审核通过后,可发布合约,生成正式环境合约地址

提交(创建)合约

注意:

  • 接入正式环境时,平台方将审核合约应用的App或Web应用,并检查合约代码是否符合平台使用规范
  • 若合约审核未通过,将在合约列表里展示对应的合约状态,并通过邮件通知开发者。用户的修改需要通过测试环境的重新部署调试才能提交到正式环境。
  • 若合约审核通过,开发者可选择将合约正式发布上架,上架后将生成正式环境的合约地址。
  • 正式环境的合约执行调用和查询与测试环境方法一致,域名不同。
  • 提交正式环境的合约必须包含所有合约源代码。并指明提交时使用的编译环境。

6. 合约与迅雷链平台交互

正式环境合约的调用和查询与测试环境的API和流程一致,请求host不同。详情见API列表。

6.1 调用合约方法

合约调用指需要改变合约状态的函数调用,执行函数的同时也可以转账到合约账户。用户界面输入用户请求后,触发合约调用。
合约调用当前分为两种形式:通过迅雷链助手App扫描二维码发送合约调用的交易;通过业务方App唤起迅雷链助手发起合约调用的交易。  典型的第三方合约应用的调用流程如下(分为面向C端的调用和面向B端的运营调用):
contract_interactive

contract_interactive

测试环境迅雷链助手下载地址

6.1.1 唤起迅雷链助手方式调用合约

步骤如下:

  1. 第三方应用接受用户输入,启动合约调用流程
  2. 用户使用迅雷链链分配的service_id,到区块链后台请求,分配一个prepay_id。
  3. 后台收到请求后,产生一个prepay_id给第三方应用。
  4. 第三方应用打包交易,主要包括合约地址、gas_limit、转账金额、执行的函数和参数编码(data)、签名等信息。
  5. 唤醒迅雷链助手app,将交易信息发送给迅雷链助手。
  6. 迅雷链助手打包交易发送给区块链交易处理中心,同时将支付过程结果回调给第三方应用,最终交易转发给区块链处理。
  7. 区块链交易完成后将根据第三方请求信息的callback回调第三方后台。
  8. 第三方后台根据回调和prepayid交易查询,确认交易状态和合约数据,刷新应用交互数据(合约调用交互完成)。

第三方应用可以同步数据到其后台服务。(第三方功能)

区块链后台将合约调用请求处理完后,如果用户有填写回调信息,回调中心通知第三方应用后台(参考 回调协议)。第三方应用界面会同第三方应用后台同步信息,展示交易后的结果。

例:从玩客云调用迅雷链助手

vchouyi://payment/?tx-data=ZGVzYz3nlLXlvbFYWFhYJnRvPTB4MTIzNDU2Nzg5MDEyMzQ1Njc4OTAmdmFsdWU9MTIzLjQwJmdhc2xpbWl0PTUwMDAwJmRhdGE9MHgwMTAyMDMwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEmc2lnbj0wNEI3QTU1QzQ3NDQwRDk4NUE0NDgzNkZENTVFQkVCNw==&resource=d2t5&x-source=wky&callback=wky://x-callback-url/&cb-data=base64编码后的回调透传参数

具体解析

  • 合约执行业务 vchouyi://payment
  • 源app名字,resource=d2t5, 解码后是wky
  • 源app回调前缀,x-source=wky
  • callback=www.bai.com||schema://host/,app回调url或schema
  • 回调时,直接回传 &cb-data=abcdefg
  • 交易信息tx-data=ZGVzYz3nlLXlvbFYWFhYJnRvPTB4MTIzNDU2Nzg5MDEyMzQ1Njc4OTAmdmFsdWU9MTIzLjQwJmdhc2xpbWl0PTUwMDAwJmRhdGE9MHgwMTAyMDMwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEmc2lnbj0wNEI3QTU1QzQ3NDQwRDk4NUE0NDgzNkZENTVFQkVCNw==

解码后包含如下信息

  • 支付地址 &to=0x12345678901234567890
  • 支付token数量 &value=123
  • 最大支付费用 &gaslimit=50000
  • 调用代码 &data=0x010203040000000000000000000000000000000000000000000000000000000000000001
  • 用于标题 &desc=电影XXXX
  • sign交易签名 &sign=04B7A55C47440D985A44836FD55EBEB7

APP回调返回

(http://www.xx.com|scheme://host)?hash=交易回执&msg=错误描述(base64)&code=错误码&cb-data=透传信息&result=(success|fail|cancel)

6.1.2 使用迅雷链助手扫描二维码调用合约

步骤如下:

  1. 第三方应用接受用户输入,启动合约调用流程
  2. 用户使用迅雷链分配的service_id,到区块链后台请求,分配一个prepay_id。
  3. 后台收到请求后,产生一个prepay_id给第三方应用。
  4. 第三方应用打包交易,主要包括合约地址、gas_limit、转账金额、执行的函数和参数编码(data)、签名等信息,按照规则拼接成tx-data。
  5. 第三方通过web服务接受url请求,请求链接返回上述生成的tx-data作为response.body,结构如下请求url返回的结构
  6. 使用链接 http://fe-blockchain.xunlei.com/#/?action=**url** 生成二维码。
  7. 使用迅雷链助手扫描二维码。

请求url返回的结构

{
    "iRet":0,
    "sMsg": "ok",
    "data": {
        "tx_data": ""
    }
}

6.2 查询 constant 合约方法

查询合约constant状态和方法,不消耗gas,使用eth_call API方法直接调用。

样例: Example

在合约部署成功的情况下,执行以下操作:

  • 未部署合约
    [root@t05f058s2 ~]# curl -k -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xfe2587bdc1781f23d105002c328d3d2ea6cfcbdd","data":"0xb0f0c96a0000000000000000000000000000000000000000000000000000000000000005"}, "latest"],"id":1}' https://sandbox-blockchain.xunlei.com/call
    {"error":{"message":"invalid request","code":-32600},"jsonrpc":"2.0","id":1}
  • 已部署合约
    [root@t05f058s2 ~]# curl -k -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xe0e39a57a044451a00e4c73a2ea6bf83bd229a68","data":"0xb0f0c96a0000000000000000000000000000000000000000000000000000000000000005"}, "latest"],"id":1}' https://sandbox-blockchain.xunlei.com/call
    {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000007eff122b94897ea5b0e2a9abf47b86337fafebdc0000000000000000000000000000000000000000000000000000000000000005"}
  • 参数说明

to: 部署合约成功之后,获得的合约地址
data: 计算方法

abi=[{"payable":true,"stateMutability":"payable","type":"fallback"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"value","type":"uint256"}],"name":"hello","outputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]

ctx=eth.contract(abi).at('0xc197e61edcbccc4bf7a0f76250ca7246de03e773')

ctx.hello.getData.call(null, 5, {from:from})
"0xb0f0c96a0000000000000000000000000000000000000000000000000000000000000005"

7. API列表

  1. 测试环境域名 https://sandbox-blockchain.xunlei.com
  2. 正式环境域名 https://rpc-blockchain.xunlei.com

7.1 测试环境API

7.1.1 获取邮箱验证码

功能

获取邮箱验证码用于测试用户注册。

请求

方法:POST
URL: /api/linktest/email_code
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

7.1.2 使用邮箱注册

功能

测试注册接口,使用email地址。用户第一次注册,产生service_id/secret通过邮件形式发送给开发者。如果邮箱地址已经注册,返回错误码。

请求

方法:POST
URL: /api/linktest/regist
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email,主要用于接收测试消息
emailcode string 注册前使用的邮件验证码

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

7.1.3 测试账号充值

功能

申请将测试燃料充值到指定的账户,开发者给自有的账户充值后用于测试活动。
每申请一次转入10个测试燃料。每个email每天最多调用10次。
也可以通过Catalyst工具直接领取测试燃料。

请求

方法:POST
URL: /api/linktest/recharge
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email,主要用于接收测试消息
address string 需要充值的账号地址
sign string 签名md5(email=xxx&address=xxx&secret=xxx), xxx填写请求的实际值

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

7.1.4 合约发布

功能

用户发布合约到测试环境,用户提供编译后的字节码。

请求

方法:POST
URL: /api/linktest/contract/deploy
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email,主要用于接收测试消息
bytecode string 编译后的合约字节码,十六进制ABI格式
params string 构造函数初始化参数,十六进制ABI格式
sign string 签名md5(email=xxx&bytecode=xxx&params=xxx&secret=xxx), xxx填写请求的实际值。即使params为空也需要计算到sign里

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

data

参数名 参数类型 必须 说明
id int 合约部署产生的id,通过这个id可查询合约部署后的地址

7.1.5 合约地址查询

功能

通过合约部署产生的id查询合约账户地址。

请求

方法:POST
URL: /api/linktest/contract/address
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email,主要用于接收测试消息
id int 合约部署返回的id
sign string 签名md5(email=xxx&id=xxx&secret=xxx), xxx填写请求的实际值

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

data

参数名 参数类型 必须 说明
address string 账户地址

7.1.6 查询最近部署的合约地址

功能

查询最近部署的合约id和合约地址。

请求

方法:POST
URL: /api/linktest/contract/last
BODY: JSON

参数说明:

参数名 参数类型 必须 说明
email string 测试使用的email,主要用于接收测试消息
sign string 签名md5(email=xxx&secret=xxx), xxx填写请求的实际值

响应

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

data

参数名 参数类型 必须 说明
id int 合约ID
address string 账户地址

7.1.7 生成扫码支付的URL信息

功能

开发者提交交易信息,获取到一个交易URL用于扫码支持。支持合约支付和第三方支付。
此接口是为了方便开发者测试,已封装了请求prepay_id的流程,仅适用于测试环境。正式环境不提供此接口,需要开发者后台实现prepayid接口和url请求。

请求

方法:POST
URL: /api/linktest/tx_generate
BODY: JSON

请求参数

参数名 参数类型 必须 说明
email string 测试注册的email
to string 合约地址或者账户地址
value string 转入的token数量,十进制整数,单位wei
callback string 回调URL
title string 交易信息标题,例如:“合约调用-存证信息上链”
desc string 交易详细信息,例如:“兑换xxx服务”
gas_limit string 执行合约最多消耗GAS值,十进制整数,合约交易才填
data string 执行的合约代码,十六进制字符串,以0x开头。包含函数地址和调用参数。只发起转账,这个内容为空。合约交易才填
tx_type string 交易类型,合约交易:contract, 第三方交易:tx_third
sign string 请求签名,用于校验请求真实性 md5(email=xxx&to=xxx&value=xxx&secret=xxx)

响应

BODY: JSON

响应参数

参数名 参数类型 必须 说明
code int 错误码,0:成功 非0:失败
msg string 错误提示信息
data object 请求返回数据

data

参数名 参数类型 必须 说明
url string 生成的URL,用于扫码获取交易信息
expire int URL过期时间,单位毫秒

7.1.8 扫码获取交易信息

功能

获取订单详细信息,请求URL由接口(/api/linktest/tx_generate)生成。
开发者通过迅雷链助手APP扫码获取订单URL,迅雷链助手APP请求此URL获取交易信息,签发交易

请求

方法:GET
URL: /api/linktest/tx_info/:tx_id
BODY: null

参数说明:

参数名 参数类型 必须 说明
tx_id string 携带在path中的参数,动态交易ID,十六进制字符串

响应

参数名 参数类型 必须 说明
content stream 迅雷链助手APP支付协议格式的交易信息

7.2 通用API

7.2.1 唤起迅雷链助手协议

唤起协议

合约执行必须通过迅雷链助手,由第三方应用唤醒迅雷链助手App或者通过迅雷链助手App扫码。 迅雷链助手唤起协议为vchouyi://payment?

请求参数说明

参数 类型 必须 说明
scheme string 迅雷链助手scheme vchouyi
host string 迅雷链助手host值为:payment
tx-data byte[] Base64编码 主要包含支付的订单信息,key=value形式,以&连接
resource byte[] Base64编码 来源app(来源商户名称),限制10字符以内
cb-data byte[] Base64编码(可选)支付调起者需要迅雷链助手回传的额外信息
x-source string 源app scheme eg. wky-app(可选 iOS调用回调,安卓不处理),保留字段
callback string url编码,支付完成后,回调迅雷链助手的地址或者schema http://www.xx.com 或 scheme://host/?hash=交易回执&msg=错误描述(base64)&code=错误码&cb-data=透传信息&result=(success|fail|cancel)
  • 若第三方业务是在非迅雷链助手接入(比如浏览器,第三方app),callback可支持网页或协议app返回,会在业务完成后回调(表现为回到浏览器或第三方app)
  • 若业务在迅雷链助手中接入(比如扫二维码在迅雷链助手中打开业务页面),callback只支持网页协议返回,会在业务完成后回调(表现为在迅雷链助手打开网页)
  • 若不传callback,建议第三方自己轮询业务结果或者等待服务器的callback回传
  • 第三方可以通过查询web的UserAgent知晓业务是否在迅雷链助手中 关键字段'OneWallet' 下面是完整User-Agent
    iOS-User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E302 OneWallet/2.0.3 (iPhone; iOS 11.3.1; Scale/3.00; origin/2; nc/IN)

tx-data参数说明

参数 类型 必须 说明
desc string 合约执行描述,必须带上“合约执行-”前缀
callback string 做url编码,第三方应用后台用来接收区块链交易完成后的回调地址
to string 执行的合约地址
value string token数量(单位 wei),1 token = 10 ** 18 wei ,传整数,不带单位。如转1token的时候填写 1 000 000 000 000 000 000
prepay_id string 预支付订单号,通过接口getPrepayId获取
service_id string 业务id,通过迅雷链开放平台申请获取service_id和签名秘钥
data string 如果是合约执行,传入以0x开头的执行合约函数和参数的编码。
gas_limit string 最大的支付gas:合约交易时为估算的执行手续费;若交易费超出实际执行扣取,会退还回付款方;
tx_type string 合约交易时,固定取值contract;
sign string 交易签名 sign=md5(sha512(callback=xxx&prepay_id=xxx&service_id=xxx&to=xxx&value=xxx&key=私钥)) 此处的callback url不用编码

APP回调返回

(http://www.xx.com|scheme://host)?hash=交易回执&msg=错误描述(base64)&code=错误码&cb-data=透传信息&result=(success|fail|cancel)

7.2.2 合约constant方法查询

该方法用来做合约方法查询。 该方法是用来执行指定的message call,不会创建交易,因此该方法的调用是试用不会改变区块链状态数据库的,合约中的constant方法应该调用这个方法。

请求

方法:POST
URL: /call
BODY: JSON

参数说明:

字段 类型 约束 备注
jsonrpc String 必须 固定值 "2.0"
method String 必须 固定值 "eth_call"
params Array 必须 详细信息见下面表格
id Int 必须 固定值 1

params 详细

index 类型 约束 备注
0 Object 必须 结构如下object详细
1 String 必须 固定值 "latest"

object 详细

  • object - 交易调用对象
字段 类型 约束 大小 备注
from common.Address 必须 20 bytes 交易的发起者
to common.Address 必填 20 bytes 交易指向的地址,比如合约调用方法中就是合约地址
data hexutil.Bytes 可选 执行合约constant方法和参数的编码 详见 Ethereum Contract ABI

示例

{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
        {
            "from": "",
            "to": "",
            "data": ""
        }, 
        "latest"
    ],
    "id": 1
}

响应

字段 类型 约束 大小 备注
DATA 交易调用对象 合约执行结果

7.2.3 查询预估gas消耗(estimateGas)

功能

获取合约调用预估消耗的gas数量。用户在调用合约时需要传入gas_limit值,由于gas的消耗与区块链当前运行环境的难度值等相关,需实时计算。
如果调用合约执行时out-of-gas(即gas消耗完但合约还未执行完毕),则合约执行失败,并且已消耗的gas不会返还。
如果调用合约执行时的gas小于或等于提供的gas,则合约执行完毕,将退还未消耗的gas。
建议开发者在调用合约时,在此计算出的预计gas消耗值基础上加上一些,以保证合约执行成功。

请求

方法:POST
URL: /estimateGas
BODY: JSON

参数说明:

字段 类型 约束 备注
jsonrpc String 必须 固定值 "2.0"
method String 必须 固定值 "eth_estimateGas"
params Array 必须 详细信息见下面表格
id Int 必须 固定值 1

params 详细

index 类型 约束 备注
0 Object 必须 结构如下object详细

object 详细

  • object - 交易调用对象
字段 类型 约束 大小 备注
from common.Address 可选 20 bytes 交易的发起者
to common.Address 必填 20 bytes 交易指向的地址,比如合约调用方法中就是合约地址
value hexutil.Big 可选 该交易要发送的值
data hexutil.Bytes 可选 Hash of the method signature and encoded parameters. 详见 Ethereum Contract ABI

示例

{
    "jsonrpc": "2.0", 
    "method": "eth_estimateGas", 
    "params":[
        {
            "from": "",
            "to": "", 
            "data": "",
            "value": ""
        }
    ], 
    "id": 1
}

响应

参数名 参数类型 必须 说明
content stream 迅雷链助手APP支付协议格式的交易信息

7.2.4 获取合约执行所需的prepayId

请求方式: post

参数 类型 说明
service_id int 业务号,通过迅雷链申请
sign string 签名,签名算法:md5(sha512("service_id=业务号&key=私钥"))
timeout int 生成的prepay_id的有效期,单位为秒

请求格式与示例:

//为了保持和以太坊格式一致,请求post body数据要按照以下格式:
{
    "jsonrpc": "2.0",
    "method": "getPrepayId",
    "params": {
        "service_id": 0,
        "sign": "f93d2813227b68f77bf0db84c62011ca",
        "timeout": 1800
    }
}
  • Timeout字段以秒为单位
  • Timeout字段如果不输入或者输入负值,则生成的prepay_id默认超时时间为2小时[可配置]
  • Timeout字段如果输入了超过默认最大的时间[可配置],则生成的prepay_id有效期为配置的最大时间,目前默认为一天

响应参数:

参数 类型 说明
iRet int 0 成功
sMsg string 返回描述
data json object 成功返回prepay_id: 'xxxx',失败返回错误信息

返回示例:

{
"iRet":0,
"sMsg": "ok",
"data": {"prepay_id":"201711291656030000000101431771972107"}
}

7.2.5 根据prepayId查询订单状态

请求url
测试环境:https://sandbox-blockchain.xunlei.com/getOrderStatusByPrepayId
正式环境:https://rpc-blockchain.xunlei.com/getOrderStatusByPrepayId

参数 类型 说明
service_id int 业务号,通过迅雷链申请
prepay_id string 订单提交时获取的

请求格式与示例:

{"jsonrpc":"2.0","method":"getOrderStatusByPrepayId","params":{"service_id":1,"prepay_id":"20171019xxxxxxxxxxxx"}}

响应参数:

参数 类型 说明
iRet int 0 成功
sMsg int 返回描述
data array 返回数据,json编码的字符串

data格式:

参数 类型 说明
from string 付款地址
to string 收款地址
value string token数量,string,单位:wei, 例如:"1000000000000000000"
status string 订单状态,0初始,1成功,2失败
ctime string 交易时间,例:2017-10-19 15:37:00
hash string 交易hash
payload string 交易payload

返回示例:

{  
    "iRet":0,
    "sMsg":"success",
    "data":{
        "from":"0x7b6837189a3464d3c696069b2b42a9ae8e17dda1",
        "to":"0x00a2810b56e763406cad8be8ee90b0b89b370829",
        "value":"1000",
        "ctime":"2018-12-28 11:56:00",
        "status":0,
        "hash":"0xe254b5a67458e8d2b20472028738baba7bf5f10c5fd19f471b31c6725e58b10e",
        "pay_load":""
    }
}
  • 如果Prepay_id未失效,同时查询不到订单信息,那么获取订单信息返回”订单未支付”
  • 如果Prepay_id已失效,同时查询不到订单信息,那么获取订单信息返回”Prepay_id无效”
  • 如果查询到订单信息则正常返回

7.2.6 合约执行结果回调

交易成功后,区块链通知模块会把prepay_id回调接入方,接入方需要接收处理,并返回应答。
对后台通知交互时,如果区块链通知模块收到接入方的应答不是成功或超时,区块链通知模块认为通知失败,区块链通知模块会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但区块链通知模块不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

注意:同样的通知可能会多次发送给接入方系统。接入方系统必须能够正确处理重复的通知。

推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
特别提醒:接入方系统对于交易结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”。

区块链通知模块回调协议(post):

参数 类型 说明
prepay_id string 预支付ID
from string 付款地址
to string 收款地址
value string token数量,string,单位:wei, 例如:"1000000000000000000"
status string 订单状态,0初始,1成功,2失败
timestamp string unix时间戳
sign string md5(sha512(from=xxxxxx&prepay_id=xxxxxxxxxxxxx&status=1&timestamp=1512893272&to=xxxxx&value=xxxx&key=私钥))

示例:

from=xxxxxx&prepay_id=xxxxxxxxxxxxx&status=1&timestamp=1512893272&to=xxxxx&value=100000000000000&sign=4124bc0a9335c2xxxxxxxxxxxxxx

其中的sign=md5(sha512(from=xxxxxx&prepay_id=xxxxxxxxxxxxx&status=1&timestamp=1512893272&to=xxxxx&value=xxxx&key=私钥))

接入方收到回调后,需要给区块链通知模块回应(response):
return_code=0&return_msg=ok

8. DEMO-二维码方式调用合约

8.1 开发合约

  1. 开发HelloWorld合约
pragma solidity^0.4.23;

contract HelloWorld {
    address public owner;
    string public info;

    constructor(address _owner) public {
        owner = _owner;
    }

    function saySomething(string _str) public returns(string) {
        info = _str;
        return info;
    }
}
  1. 使用remix测试和本地调试(也可选择使用catalyst) HelloWorld-remix

8.2 注册测试环境账号并部署测试环境

  1. 使用邮箱注册测试环境账号,获取service_id和key。开发者可以自己实现接口请求或使用postman等工具来注册。
    下面提供apiary的api接口用于注册。调用接口时需根据实际情况计算sign和修改请求参数。 demo-apiary-regist

  2. 下载测试版迅雷链助手,新建个人测试用账户。使用第一步里的接口向对应的账户充值。

  3. 编译合约,获取部署合约所需的bytecode。
    这里如果使用truffle开发的话,可以使用truffle compile命令编译合约 /build 文件夹下获取对应合约的json文件,提取对应的bytecode。
    如果使用remix开发合约的话,点击 compile => Details 获取对应的bytecode。

    // HelloWorld.sol bytecode
    0x608060405234801561001057600080fd5b506040516020806104f783398101806040528101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610474806100836000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063370158ea1461005c5780638da5cb5b146100ec578063fe6b378314610143575b600080fd5b34801561006857600080fd5b50610071610225565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100b1578082015181840152602081019050610096565b50505050905090810190601f1680156100de5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156100f857600080fd5b506101016102c3565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561014f57600080fd5b506101aa600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506102e8565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101ea5780820151818401526020810190506101cf565b50505050905090810190601f1680156102175780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102bb5780601f10610290576101008083540402835291602001916102bb565b820191906000526020600020905b81548152906001019060200180831161029e57829003601f168201915b505050505081565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606081600190805190602001906103009291906103a3565b5060018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103975780601f1061036c57610100808354040283529160200191610397565b820191906000526020600020905b81548152906001019060200180831161037a57829003601f168201915b50505050509050919050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106103e457805160ff1916838001178555610412565b82800160010185558215610412579182015b828111156104115782518255916020019190600101906103f6565b5b50905061041f9190610423565b5090565b61044591905b80821115610441576000816000905550600101610429565b5090565b905600a165627a7a72305820e93b6ccfaace4a512cc7b4a45ba826b940ce758758fe21abe52cb4c1fecf211f0029
  4. 如果合约的构造函数带有初始化参数,需要计算初始化参数params的data。
    如果使用remix开发合约的话,点击 run,选中要部署的合约,输入初始化参数,点击 deploy。在控制台中可以查看此交易的详细信息,其中的input即为合约编译的bytecode与初始化参数组成的data。由于测试环境的部署接口,将合约编译的bytecode与初始化参数的params分离开来,所以这里需要将params对应的data单独提取。
    如果使用的是ethers.js,利用ethers.utils.ABICoder的encode方法,编译对应的初始化参数为部署合约的params。

    var ethers = require('ethers')
    var abiCoder = new ethers.utils.AbiCoder()
    return abiCoder.encode(['address'], ['0xca35b7d915458ef540ade6068dfe2f44e8fa733c'])
    // 0x000000000000000000000000ca35b7d915458ef540ade6068dfe2f44e8fa733c
  5. 调用测试环境测试环境部署合约接口,参数为上述的bytecode、params以及部署合约的email和计算的签名sign。请求结果会返回一个部署合约的id。
    然后调用根据id查询合约地址接口,查询已部署合约的地址。

8.3 调用合约和查询合约业务

  1. 调用 查询合约constant方法 接口,查询HelloWorld的info值。
    根据合约查询编码的方法,计算得info的data为 0x370158ea

    curl -k -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xd5b0df861803a07f330868104eec92ebdcce4c79","data":"0x370158ea"}, "latest"],"id":1}' https://sandbox-blockchain.xunlei.com/call
    {"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"}
    // AbiCoder.decode解码得值为空

    初始化函数时,info值为空。

  2. 编码 saySomething 函数,获取调用函数的data。参考使用ethers.js编码

    0xfe6b37830000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000
  3. 请求 查询预估gas消耗 接口,查询saySomething 函数执行的预估gas值。建议传入执行调用函数的gas值在查询的gas值基础上加上一些,以防出现out-of-gas的情况。

    curl -k -XPOST --data '{"jsonrpc":"2.0", "method":"eth_estimateGas", "params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xd5b0df861803a07f330868104eec92ebdcce4c79", "data":"0xfe6b37830000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000","value":""}], "id":1}' -H 'content-type:application/json' https://sandbox-blockchain.xunlei.com/estimateGas
    // 返回值
    {"jsonrpc":"2.0","id":1,"result":"0xaebc"}

    将上述结果转10进制得:

    0xaebc.toString('10')
    // "44732"

    在传入到下一步的执行接口时,将在此值的基础上加上10000,最终传入的gas数量为54732。

  4. 请求接口 生成扫码支付的URL信息 执行合约的 saySomething 函数。
    请求参数中的to地址为合约地址0xd5b0df861803a07f330868104eec92ebdcce4c79,data为前面生成的data,gas_limit为上一步的54732。
    计算sign md5(email=xxx&to=xxx&value=xxx&secret=xxx)。
    返回 data 的里 url 即为迅雷链助手app扫描二维码需要的地址。链接具有时效性,默认30分钟。

    {
    "code": 0,
    "data": {
     "url": "http://fe-blockchain.xunlei.com/#/?action=https%3A%2F%2Fsandbox-blockchain.xunlei.com%2Fapi%2Flinktest%2Ftx_info%2Ff4ebcdf4d756f0d8041c82d8e32e912a",
     "expire": 1800000
    },
    "msg": ""
    }
  5. 根据上一步的url,使用二维码生成工具,生成一个二维码。然后使用测试版迅雷链助手app扫描。
    迅雷链助手将调用合约执行页面,用户选择执行合约使用的账户,输入对应的密码。等待交易确认后,交易记录里可以看到合约执行结果。

  6. 再次调用 查询合约constant方法 接口,查询HelloWorld的info值。

    curl -k -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x7eff122b94897ea5b0e2a9abf47b86337fafebdc","to":"0xd5b0df861803a07f330868104eec92ebdcce4c79","data":"0x370158ea"}, "latest"],"id":1}' https://sandbox-blockchain.xunlei.com/call
    {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"}
    // AbiCoder.decode解码得值为 Hello World,调用成功

8.4 测试环境与正式环境接口区别

  1. 当前测试环境的账号注册、合约部署都需要开发者通过接口调用实现;或通过Catalyst实现。正式环境则是可以通过迅雷链开放平台注册和部署。
  2. 正式环境与测试环境的合约查询、合约执行、合约执行gas预估、获取prepay_id接口一致,域名不同。
  3. 正式环境暂不提供生成扫码支付的URL信息接口,所以开发者需要根据 使用迅雷链助手扫描二维码调用合约中所描述的步骤自己提供二维码所需的url及其对应的后台web服务。

9. 常见FAQ

9.1 如何下载正式环境迅雷链助手手机APP

扫码下载安装

app下载

9.2 如何下载测试环境迅雷链助手手机APP

扫码下载安装

app下载

9.3 如何获得区块链账号

用户下载安装APP后,即可通过APP生成账户。

9.4 如何获取 serviceid 和 key

迅雷链测试环境通过API接口邮件请求获取,请求后会通过邮件通知开发者。
迅雷链正式环境通过迅雷链开放平台申请服务,审核通过后平台下发service_id和key。