EOS nodejs 合约交互 ( 三 ) - eosjs.contract() 获取合约并执行动作

yufei       6 年 前       5093

其实,使用 eosjs 来执行一个合约动作,目前好像有两种方式,第一种比较复杂,就是使用 eosjs.transact() 方法,而第二种则比较简单,首先使用 eosjs.contract() 方法获取合约,然后再调用该方法的返回值里的合约方法。

本章节,我们主要来讲讲 eosjs.contract() 方法及使用范例

准备

  1. 我们希望你是按照顺序一篇一偏读过来的,哈哈。不然你会发现我没有阐述合约信息.

  2. 假设你已经使用 npm i eosjs 安装了 eosjs

  3. 假设存在一个文件 micromsg.js 文件,内容如下,接下来我们所有的 javascript 都在这个文件中

const Eos = require('eosjs');
const config = {
    account: {
        contract: "micromsg",   // 合约账号
        from: "xiaoming",       // 发送者账号
        to: "xiaohong"          // 接收者账号
    },
    network: {
        protocol:'http',        // 协议 http or https
        host: 'localhost',      // 远程主机
        port: '8080',           // 远程端口号
        keyProvider:['5J6Ly95q876iEwG1c4bBcWAWqHbibRmhDsxtSrnsSswpT7r7gAC'], // 私钥
        chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f', // EOS 链 id
        timeout: 10000          // 10s 超时
    }
}

var eos = Eos(config.network);

eosjs.contract() 方法

eosjs.contract() 方法用户获取一个合约,它有一个参数,也是唯一的一个参数,就是合约所在的账号名

但这个方法有点特殊,如果我们直接调用 console.log(eosjs.contract()) 方法会抛出一个异常,而不是显示该方法的帮助信息

console.log(eos.contract());

运行结果如下

assert.js:86
  throw new AssertionError(obj);
  ^

AssertionError [ERR_ASSERTION]: account string required
    at Object.abiAsync (/Users/yufei/Downloads/contract/micromsg/node_modules/eosjs/lib/abi-cache.js:32:12)
    at Object.genContractActions (/Users/yufei/Downloads/contract/micromsg/node_modules/eosjs/lib/write-api.js:352:28)
    at Object.merge.contract (/Users/yufei/Downloads/contract/micromsg/node_modules/eosjs/lib/write-api.js:112:14)
    at Object.<anonymous> (/Users/yufei/Downloads/contract/micromsg/micromsg.js:21:17)
    at Module._compile (internal/modules/cjs/loader.js:707:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:718:10)
    at Module.load (internal/modules/cjs/loader.js:605:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:544:12)
    at Function.Module._load (internal/modules/cjs/loader.js:536:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:760:12)

但不管怎么样,从报错信息中可以看出,使用该方法,我们需要传递合约账号名

参数说明

参数 类型 默认值 说明
account string 必填 合约所在的账号

返回值

如果合约存在,且配置正常,那么会返回当前合约的所有信息,同时返回当前合约可以调用的动作

结果很长,我只截取最重要的部分

{ transaction: [Function: _callee],
  read: [Function],
  send: [Function],
  fc:
   { abi:
      { version: 'eosio::abi/1.1',
        types: [],
        structs:
         [ { name: 'message',
             base: '',
             fields:
              [ { name: 'id', type: 'uint64' },
                { name: 'from', type: 'name' },
                { name: 'to', type: 'name' },
                { name: 'msg', type: 'string' },
                { name: 'tm', type: 'uint64' },
                { name: 'readed', type: 'bool' } ] },
           { name: 'read',
             base: '',
             fields:
              [ { name: 'to', type: 'name' },
                { name: 'msg_id', type: 'uint64' } ] },
           { name: 'send',
             base: '',
             fields:
              [ { name: 'from', type: 'name' },
                { name: 'to', type: 'name' },
                { name: 'msg', type: 'string' } ] } ],
        actions:
         [ { name: 'read', type: 'read', ricardian_contract: '' },
           { name: 'send', type: 'send', ricardian_contract: '' } ],
        tables:
         [ { name: 'messages',
             index_type: 'i64',
             key_names: [],
             key_types: [],
             type: 'message' } ],
        ricardian_clauses: [],
        error_messages: [],
        abi_extensions: [],
        variants: [] },
     schema:
      { message:
         { fields:
            { id: 'uint64',
              from: 'name',
              to: 'name',
              msg: 'string',
              tm: 'uint64',
              readed: 'bool' } },
        read:
         { fields: { to: 'name', msg_id: 'uint64' },
           action: { name: 'read', account: 'micromsg' } },
        send:
         { fields: { from: 'name', to: 'name', msg: 'string' },
           action: { name: 'send', account: 'micromsg' } } },
....

触发异常

如果想要获取的合约不存在,则会抛出一个合约不存在的异常

eos.contract("noexist")
.then(rs => console.log(rs))
.catch(e => console.log(e))

运行结果如下

{ Error: {"code":500,"message":"Internal Service Error","error":{"code":0,"name":"exception","what":"unspecified","details":[]}}
    at /Users/yufei/Downloads/contract/micromsg/node_modules/eosjs-api/lib/apigen.js:112:23
    at process.internalTickCallback (internal/process/next_tick.js:77:7) status: 500, statusText: 'Internal Server Error' }

使用 eosjs.contract() 方法获取合约 abi 信息

如果合约存在,那么 eosjs.contract() 方法返回的结果中的 fc.abi 就是该合约的 abi 信息

eos.contract(config.account.contract)
.then(rs => console.log(rs.fc.abi))
.catch(e => console.log(e))

运行结果如下

{ version: 'eosio::abi/1.1',
  types: [],
  structs:
   [ { name: 'message',
       base: '',
       fields:
        [ { name: 'id', type: 'uint64' },
          { name: 'from', type: 'name' },
          { name: 'to', type: 'name' },
          { name: 'msg', type: 'string' },
          { name: 'tm', type: 'uint64' },
          { name: 'readed', type: 'bool' } ] },
     { name: 'read',
       base: '',
       fields:
        [ { name: 'to', type: 'name' },
          { name: 'msg_id', type: 'uint64' } ] },
     { name: 'send',
       base: '',
       fields:
        [ { name: 'from', type: 'name' },
          { name: 'to', type: 'name' },
          { name: 'msg', type: 'string' } ] } ],
  actions:
   [ { name: 'read', type: 'read', ricardian_contract: '' },
     { name: 'send', type: 'send', ricardian_contract: '' } ],
  tables:
   [ { name: 'messages',
       index_type: 'i64',
       key_names: [],
       key_types: [],
       type: 'message' } ],
  ricardian_clauses: [],
  error_messages: [],
  abi_extensions: [],
  variants: [] }

从返回的结果中可以看出,这和我们本地的 micromsg.abi 文件简直一模一样

cat micromsg.abi 
{
    "____comment": "This file was generated with eosio-abigen. DO NOT EDIT Sun Dec  2 08:15:50 2018",
    "version": "eosio::abi/1.1",
    "structs": [
        {
            "name": "message",
            "base": "",
            "fields": [
                {
                    "name": "id",
                    "type": "uint64"
                },
                {
                    "name": "from",
                    "type": "name"
                },
                {
                    "name": "to",
                    "type": "name"
                },
                {
                    "name": "msg",
                    "type": "string"
                },
                {
                    "name": "tm",
                    "type": "uint64"
                },
                {
                    "name": "readed",
                    "type": "bool"
                }
            ]
        },
        {
            "name": "read",
            "base": "",
            "fields": [
                {
                    "name": "to",
                    "type": "name"
                },
                {
                    "name": "msg_id",
                    "type": "uint64"
                }
            ]
        },
        {
            "name": "send",
            "base": "",
            "fields": [
                {
                    "name": "from",
                    "type": "name"
                },
                {
                    "name": "to",
                    "type": "name"
                },
                {
                    "name": "msg",
                    "type": "string"
                }
            ]
        }
    ],
    "types": [],
    "actions": [
        {
            "name": "read",
            "type": "read",
            "ricardian_contract": ""
        },
        {
            "name": "send",
            "type": "send",
            "ricardian_contract": ""
        }
    ],
    "tables": [
        {
            "name": "messages",
            "type": "message",
            "index_type": "i64",
            "key_names": [],
            "key_types": []
        }
    ],
    "ricardian_clauses": [],
    "variants": [],
    "abi_extensions": []
}

调用 micromsg 合约的 send() 方法

从前面的介绍中可以知道,合约的方法是直接在 eosjs.contract() 的返回结果中以键值的方式存在的。

{ transaction: [Function: _callee],
  read: [Function],
  send: [Function],
...

因此,一般的合约的动作调用格式如下

eos.contract(config.account.contract)
.catch(e => console.log(e))
.then(contract =>{
    console.log(contract.methodname(params));
})

其中, methodname 是合约的动作,params 是要传递给合约的动作的参数

跟 eosjs 其它方法一样,我们可以调用 send() 不传递任何参数来获取帮助信息,如下

eos.contract(config.account.contract)
.catch(e => console.log(e))
.then(contract =>{
    console.log(contract.send());
})

显示结果如下

CONTRACT
micromsg

ACTION
send

PARAMETERS
{
    "from": "name",
    "to": "name",
    "msg": "string"
}

EXAMPLE
micromsg.send({
    "from": "",
    "to": "",
    "msg": ""
})

另一方面,调用合约动作返回的同样是一个 Promise,因此,我们也需要使用 then()catch() 来获取返回值和捕获异常

也就是说,从获取合约,到执行合约动作的代码一般如下

eos.contract(contractName)
.catch(e => console.log(e))
.then(contract =>{
    contract.methodname(params)
    .then( rs => { console.log(rs);})
    .catch( e => console.log(e))
})

例如,我们要调用 micromsg 合约的 send() 动作,使用小明给小红发一条信息的代码如下

eos.contract(config.account.contract)
.catch(e => console.log(e))
.then(contract =>{
    contract.send({from:"xiaoming",to:"xiaohong",msg:"i miss you"})
    .then( rs => { console.log(rs);})
    .catch( e => console.log(e))
})

输出结果如下

{"code":500,"message":"Internal Service Error","error":{"code":3040003,"name":"tx_no_auths","what":"Transaction should have at least one required authority","details":[]}}

哎呀,报错了,错误提示我们没有添加授权

这是什么意思呢?其实很简单,错误信息和下面这条命令一样

cleos push action push micromsg send '["xiaoming","xiaohong","i miss you"]' 

而正确的命令是

cleos push action push micromsg send '["xiaoming","xiaohong","i miss you"]' -p xiaoming

发现缺了什么没有,对,就是缺少 -p xiaoming

那么,在 eosjs 中要怎么传递这个参数呢?

哈哈,不用着急, 在 eosjs 中,几乎所有方法都存在一个隐形的参数 options ,这个参数是可选的,一般是方法的最后一个参数,用于指定一些配置项,

比如,我们可以使用下面的配置项来传递授权

{
    authorization: ["xiaoming"]
}

或者同时指定权限的方式

{
    authorization: ["xiaoming@active"]
}

当然了,对于我们的配置项,就是

{
    authorization: [`${config.account.from}@active`]
}

好了,我们可以该一下调用合约的方法,添加配置项

eos.contract(config.account.contract)
.catch(e => console.log(e))
.then(contract =>{
    contract.send({from:"xiaoming",to:"xiaohong",msg:"i miss you"},{authorization: [`${config.account.from}@active`]})
    .then( rs => { console.log(rs);})
    .catch( e => console.log(e))
})

运行结果如下

{ broadcast: true,
  transaction:
   { compression: 'none',
     transaction:
      { expiration: '2018-12-02T03:22:09',
        ref_block_num: 19324,
        ref_block_prefix: 2454067230,
        max_net_usage_words: 0,
        max_cpu_usage_ms: 0,
        delay_sec: 0,
        context_free_actions: [],
        actions:
         [ { account: 'micromsg',
             name: 'send',
             authorization: [ { actor: 'xiaoming', permission: 'active' } ],
             data: '0000006c3a498deb0000006cd2468deb0a69206d69737320796f75' } ],
        transaction_extensions: [] },
     signatures:
      [ 'SIG_K1_K5AYPwMSPJXgVvNV39WzRQsmhFdTXfEevUH5TTQ8zVrh5XWEKPCbSdGNfmLeDAEHr4wJQuip2cyd75B4YV2uG1LEe2Gtk2' ] },
  transaction_id:
   'ca35e5151f87a150f8c74209e4717ce11330eff523a51ce216c4ce983c164392',
  processed:
   { id:
      'ca35e5151f87a150f8c74209e4717ce11330eff523a51ce216c4ce983c164392',
     block_num: 84863,
     block_time: '2018-12-02T03:21:10.500',
     producer_block_id: null,
     receipt:
      { status: 'executed', cpu_usage_us: 542, net_usage_words: 15 },
     elapsed: 542,
     net_usage: 120,
     scheduled: false,
     action_traces:
      [ { receipt:
           { receiver: 'micromsg',
             act_digest:
              'b8f84135fd128efb56723def246fac9febbb0473425c487bd0a4926e37bd4d96',
             global_sequence: 84938,
             recv_sequence: 8,
             auth_sequence: [ [ 'xiaoming', 19 ] ],
             code_sequence: 1,
             abi_sequence: 1 },
          act:
           { account: 'micromsg',
             name: 'send',
             authorization: [ { actor: 'xiaoming', permission: 'active' } ],
             data: { from: 'xiaoming', to: 'xiaohong', msg: 'i miss you' },
             hex_data: '0000006c3a498deb0000006cd2468deb0a69206d69737320796f75' },
          context_free: false,
          elapsed: 333,
          console: "#6 xiaoming send 'i miss you' to xiaohong at 1543720870",
          trx_id:
           'ca35e5151f87a150f8c74209e4717ce11330eff523a51ce216c4ce983c164392',
          block_num: 84863,
          block_time: '2018-12-02T03:21:10.500',
          producer_block_id: null,
          account_ram_deltas: [ { account: 'micromsg', delta: 156 } ],
          except: null,
          inline_traces:
           [ { receipt:
                { receiver: 'xiaoming',
                  act_digest:
                   'b8f84135fd128efb56723def246fac9febbb0473425c487bd0a4926e37bd4d96',
                  global_sequence: 84939,
                  recv_sequence: 7,
                  auth_sequence: [ [ 'xiaoming', 20 ] ],
                  code_sequence: 1,
                  abi_sequence: 1 },
               act:
                { account: 'micromsg',
                  name: 'send',
                  authorization: [ { actor: 'xiaoming', permission: 'active' } ],
                  data: { from: 'xiaoming', to: 'xiaohong', msg: 'i miss you' },
                  hex_data: '0000006c3a498deb0000006cd2468deb0a69206d69737320796f75' },
               context_free: false,
               elapsed: 7,
               console: '',
               trx_id:
                'ca35e5151f87a150f8c74209e4717ce11330eff523a51ce216c4ce983c164392',
               block_num: 84863,
               block_time: '2018-12-02T03:21:10.500',
               producer_block_id: null,
               account_ram_deltas: [],
               except: null,
               inline_traces: [] },
             { receipt:
                { receiver: 'xiaohong',
                  act_digest:
                   'b8f84135fd128efb56723def246fac9febbb0473425c487bd0a4926e37bd4d96',
                  global_sequence: 84940,
                  recv_sequence: 7,
                  auth_sequence: [ [ 'xiaoming', 21 ] ],
                  code_sequence: 1,
                  abi_sequence: 1 },
               act:
                { account: 'micromsg',
                  name: 'send',
                  authorization: [ { actor: 'xiaoming', permission: 'active' } ],
                  data: { from: 'xiaoming', to: 'xiaohong', msg: 'i miss you' },
                  hex_data: '0000006c3a498deb0000006cd2468deb0a69206d69737320796f75' },
               context_free: false,
               elapsed: 3,
               console: '',
               trx_id:
                'ca35e5151f87a150f8c74209e4717ce11330eff523a51ce216c4ce983c164392',
               block_num: 84863,
               block_time: '2018-12-02T03:21:10.500',
               producer_block_id: null,
               account_ram_deltas: [],
               except: null,
               inline_traces: [] } ] } ],
     except: null } }

哈哈,从执行结果中可以看出,我们的动作执行是提交成功的,也执行成功了,因为我们看到了输出

#6 xiaoming send 'i miss you' to xiaohong at 1543720870

也就是说,使用 eosjs 对任何合约方法的调用,除非是延时交易,不然会等到合约执行成功才返回。

合约动作调用失败

合约动作也有可能因为各种各样的原因调用失败从而引发异常,一般情况下,调用失败都是合约内使用 eosio_assert 抛出的异常

比如我们的 micromsg 合约,会检查 msg 参数的长度,如果为 0 则抛出异常

现在,我们来触发这个异常看看,我们把 msg 参数设置为空字符串

eos.contract(config.account.contract)
.catch(e => console.log(e))
.then(contract =>{
    contract.send({from:"xiaoming",to:"xiaohong",msg:""},{authorization: [`${config.account.from}@active`]})
    .then( rs => { console.log(rs);})
    .catch( e => console.log(e))
})

运行结果如下

{"code":500,"message":"Internal Service Error","error":{"code":3050003,"name":"eosio_assert_message_exception","what":"eosio_assert_message assertion failure","details":[]}}

这里没有显示具体

1 回复  |  直到 Dec 26, 2019

penglipeng

#1   •   6 年 前   •  

简单实用能看懂,赞一个,我刚把智能合约写完,只会cleos命令交互,学到了! 我在想这个能做微信小程序吗?,公司提的要求没有头绪

penglipeng

#2   •   6 年 前   •  

简单实用能看懂,赞一个,我刚把智能合约写完,只会cleos命令交互,学到了! 我在想这个能做微信小程序吗?,公司提的要求没有头绪

penglipeng

#3   •   6 年 前   •  

谢谢楼主,学到了,我想问问能做微信小程序吗?,eosjs和wxjs能一起吗?公司提的要求做微信小程序,没有头绪

sbihgu

#4   •   4 年, 12 月 前   •  

您当前教程的版本是多少呢,我安装您的文档实践,在get_code时出现“Returning WAST from get_code is no longer supported”,是因为节点版本太低还是?

简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.