您正在查看: Surou 发布的文章

EOS 智能合约最佳安全开发指南

EOS 智能合约最佳安全开发指南

这篇文档旨在为 EOS 智能合约开发人员提供一些智能合约的安全准则已知漏洞分析。我们邀请社区对该文档提出修改或完善建议,欢迎各种合并请求(Pull Request)。若有相关的文章或博客的发表,也请将其加入到参考文献中。

目录

安全准则

EOS 处于早期阶段并且有很强的实验性质。因此,随着新的 bug 和安全漏洞被发现,新的功能不断被开发出来,其面临的安全威胁也是不断变化的。这篇文章对于开发人员编写安全的智能合约来说只是个开始。

开发智能合约需要一个全新的工程思维,它不同于我们以往项目的开发。因为它犯错的代价是巨大的,很难像中心化类型的软件那样,打上补丁就可以弥补损失。就像直接给硬件编程或金融服务类软件开发,相比于 Web 开发和移动开发都有更大的挑战。因此,仅仅防范已知的漏洞是不够的,还需要学习新的开发理念:

  • 对可能的错误有所准备。任何有意义的智能合约或多或少都存在错误,因此你的代码必须能够正确的处理出现的 bug 和漏洞。需始终保证以下规则:
    • 当智能合约出现错误时,停止合约
    • 管理账户的资金风险,如限制(转账)速率、最大(转账)额度
    • 有效的途径来进行 bug 修复和功能提升
  • 谨慎发布智能合约。 尽量在正式发布智能合约之前发现并修复可能的 bug。
    • 对智能合约进行彻底的测试,并在任何新的攻击手法被发现后及时的测试(包括已经发布的合约)
    • 从 alpha 版本在麒麟测试网(CryptoKylin-Testnet)上发布开始便邀请专业安全审计机构进行审计,并提供漏洞赏金计划(Bug Bounty)
    • 阶段性发布,每个阶段都提供足够的测试
  • 保持智能合约的简洁。复杂会增加出错的风险。
    • 确保智能合约逻辑简洁
    • 确保合约和函数模块化
    • 使用已经被广泛使用的合约或工具(比如,不要自己写一个随机数生成器)
    • 条件允许的话,清晰明了比性能更重要
    • 只在你系统的去中心化部分使用区块链
  • 保持更新。通过公开资源来确保获取到最新的安全进展。
    • 在任何新的漏洞被发现时检查你的智能合约
    • 尽可能快的将使用到的库或者工具更新到最新
    • 使用最新的安全技术
  • 清楚区块链的特性。尽管你先前所拥有的编程经验同样适用于智能合约开发,但这里仍然有些陷阱你需要留意:
    • require_recipient(account_name name) 可触发通知,调用name合约中的同名函数,官方文档

已知漏洞

数值溢出

在进行算术运算时,未进行边界检查可能导致数值上下溢,引起智能合约用户资产受损。

漏洞示例

存在缺陷的代码:batchTransfer 批量转账

typedef struct acnts {
    account_name name0;
    account_name name1;
    account_name name2;
    account_name name3;
} account_names;

void transfer(symbol_name symbol, account_name from, account_names to, uint64_t balance)
{
    require_auth(from);
    account fromaccount;

    require_recipient(from);
    require_recipient(to.name0);
    require_recipient(to.name1);
    require_recipient(to.name2);
    require_recipient(to.name3);

    eosio_assert(is_balance_within_range(balance), "invalid balance");
    eosio_assert(balance > 0, "must transfer positive balance");

    uint64_t amount = balance * 4; //乘法溢出

    int itr = db_find_i64(_self, symbol, N(table), from);
    eosio_assert(itr >= 0, "Sub-- wrong name");
    db_get_i64(itr, &fromaccount, (account));
    eosio_assert(fromaccount.balance >= amount, "overdrawn balance");

    sub_balance(symbol, from, amount);

    add_balance(symbol, to.name0, balance);
    add_balance(symbol, to.name1, balance);
    add_balance(symbol, to.name2, balance);
    add_balance(symbol, to.name3, balance);
}

防御方法

尽可能使用 asset 结构体进行运算,而不是把 balance 提取出来进行运算。

真实案例

权限校验

在进行相关操作时,应严格判断函数入参和实际调用者是否一致,使用require_auth进行校验。

漏洞示例

存在缺陷的代码:transfer 转账

void token::transfer( account_name from,
                      account_name to,
                      asset        quantity,
                      string       memo )
{
    eosio_assert( from != to, "cannot transfer to self" );
    eosio_assert( is_account( to ), "to account does not exist");
    auto sym = quantity.symbol.name();
    stats statstable( _self, sym );
    const auto& st = statstable.get( sym );

    require_recipient( from );
    require_recipient( to );

    eosio_assert( quantity.is_valid(), "invalid quantity" );
    eosio_assert( quantity.amount > 0, "must transfer positive quantity" );
    eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
    eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );

    auto payer = has_auth( to ) ? to : from;

    sub_balance( from, quantity );
    add_balance( to, quantity, payer );
}

防御方法

使用require_auth( from )校验资产转出账户与调用账户是否一致。

真实案例

暂无

apply 校验

在处理合约调用时,应确保每个 action 与 code 均满足关联要求。

漏洞示例

存在缺陷的代码:

// extend from EOSIO_ABI
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
}

EOSIO_ABI_EX(eosio::charity, (hi)(transfer))

防御方法

使用

if( ((code == self  && action != N(transfer) ) || (code == N(eosio.token) && action == N(transfer)) || action == N(onerror)) ) { }

绑定每个关键 action 与 code 是否满足要求,避免异常调用。

真实案例

参考文献

致谢

  • 麒麟工作组
  • eosiofans
  • 荆凯(EOS42)
  • 星魂
  • 岛娘
  • 赵余(EOSLaoMao)
  • 字符

清理MAC下CLion的安装记录

今天MAC下安装CLion编译EOS,结果一运行就推出,怀疑是之前安装过,有什么本地的记录,或者授权校验的问题,导致新版本安装后,无法运行。所以得清理下本地的缓存等文件
本地安装的版本为 CLion2018.2

rm -rfv ~/Library/Preferences/clion2018.2
rm -rfv ~/Library/Caches/clion2018.2
rm -rfv ~/Library/Application\ Support/clion2018.2
rm -rfv ~/Library/Logs/clion2018.2

参考文件 https://gist.githubusercontent.com/denji/9731967/raw/jetbrains-uninstall.sh


最简单的试用CLion的方式
编辑hosts

vim /private/etc/hosts

0.0.0.0 account.jetbrains.com添加到文件尾部
然后到http://idea.lanyus.com/ 获取注册码,然后到CLion 输入Activation code 即可

以传统的WEB开发方式,来举例理解Dapp开发

前言

常规的讲解DApp开发的文章,大都直接从区块链方式讲起,对于没有接触过区块链的同学,可能不太好理解,今天我们尝试从传统的web开发,对比讲解DApp开发。

对于目前常见的DApp大都是以Web的形式,其实DApp并不局限于Web形式,也可以是PC客户端,也可以是App。为了更好理解和演示,我们将以常见的Web形式的DApp讲起。

演示实例

我们将要演示的DApp例子为,一个计算器,合约中计算两个输入的值,并将值存入链上,并提供查询的接口。

如果我们拿常规的Web去实现下这个App。基本分为3部分,前端(获取输入的值),服务器Api接收程序(接收前端输入的值,并计算结果,并将结果写入数据库,以及提供查询的接口),数据库(存储计算后的值)。

实现和部署对比

实现 Web App Web DApp
前端 普通html 普通html
服务器Api接收程序 php开发的api程序 执行链上部署的合约
数据库 mysql 写入链上对应的RAM
服务器部署 云服务器 前端放在云服务器,合约部署在EOS网络

传统Web实现

1.前端

<form method="post" name="form" action="http://www.xxx.xx/postGet.php">
  <table >
      <tr>
       <td>第一个参数:</td>
       <td><input type="text" name="FirstParm"/></td>
      </tr>
      <tr>
       <td>第二个参数:</td>
       <td><input type="text" name="SecondParm"/></td>
      </tr>
      <tr>
       <td><input type="submit" name="Submit" value="计算两数和"/></td>
      </tr>
  </table>
</form>

2.创建数据库

CREATE DATABASE results;
CREATE TABLE result(name CHAR(13), iresult int);

3.Api接收程序

    <?php
    $firstparm = $_POST[FirstParm'];
    $secondparm = $_POST['SecondParm'];
    $result = $firstparm + $secondparm;
        $con = mysql_connect("localhost","cryptokylinq","5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b");
        if (!$con) {
          die('Could not connect: ' . mysql_error());
          }
        mysql_select_db("results", $con);
        mysql_query("INSERT INTO result (iresult) VALUES ($result)");
        mysql_close($con);
    ?>

查询

    <?php
    $con = mysql_connect("localhost","cryptokylinq","5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b");
    if (!$con) {
      die('Could not connect: ' . mysql_error());
      }
    mysql_select_db("results", $con);
    $result = mysql_query("SELECT * FROM result");
    while($row = mysql_fetch_array($result)) {
      echo $row['iresult'] ;
      echo "<br />";
      }
    mysql_close($con);
    ?>

上面三步简单的完成了,常规Web的计算,写入,查询操作。

Web DApp实现

1.编写合约

//Calculator.hpp
#include <eosiolib/asset.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

class Calculator : public eosio::contract {
public:
    Calculator(account_name self)
            : eosio::contract(self)
            ,results(_self, _self){}

public:
    void doit(account_name from, const uint64_t firstParm, const uint64_t secoundParm);

private:
//@abi table results i64
    struct result{
        account_name name;
        uint8_t iresult;
        uint64_t primary_key()const { return name; }
        EOSLIB_SERIALIZE(result, (name)(iresult))
    };

    typedef eosio::multi_index<N(results),result> result_table;
    result_table results;
};
EOSIO_ABI( Calculator, (doit) )
#include "Calculator.hpp"


void Calculator::doit(account_name from, const uint64_t firstParm, const uint64_t secoundParm){
    require_auth(from);

    eosio_assert(firstParm >= 1, "firstParm mast >= 0"); //没有实际意义 只是做个数值的判断演示
    auto account_itr = results.find(from);
    if(account_itr == results.end()){
        account_itr = results.emplace(_self, [&](auto& result){
            result.name = from;
            result.iresult = firstParm + secoundParm;
        });
    }
    else{
        results.modify(account_itr, 0, [&](auto& result) {
            result.iresult = firstParm + secoundParm;
        });
    }
}

合约中包含了数据库的创建格式,合约部署后,相当于,数据库也创建好了,以及数据写入需要执行的api接口

2.合约编译,需要将合约初步编译,才能部署到链上

  • 生成API文件
    suroudeMacBook-Pro:Calculator surou$ eosiocpp -g Calculator.abi Calculator.cpp
  • 生成WAST文件
    suroudeMacBook-Pro:Calculator surou$ eosiocpp -o Calculator.wast Calculator.cpp

    执行完成后目录文件如下

    suroudeMacBook-Pro:Calculator surou$ ls
    Calculator.abi  Calculator.cpp  Calculator.hpp  Calculator.wasm Calculator.wast

3.创建麒麟测试网的测试账号,用于合约的部署,以及使用资源的购买

  • 创建账号:http://faucet.cryptokylin.io/create_account?new_account_name
    比如我们要创建的账号为cryptokylinq,访问链接:http://faucet.cryptokylin.io/create_account?cryptokylinq
    {
      msg: "succeeded",
      keys: - {
      active_key: - {
          public: "EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws",
          private: "5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b"
      },
      owner_key: - {
          public: "EOS89vNXgGrkM4GYFAav78FgnX94QzVXYtcEKiKicSq4YcykPmnC7",
          private: "5KZbLRwZAZQJQKS6sM1RL1d2Af9s49zA9qka9f63ce7J2jUDWwQ"
      }
      },
      account: "cryptokylinq"
    }

    一个账号一般有两对秘钥,对应两个权限owner(类似网站后台的超级管理员)和active(部分权限的管理员),当然还可以创建多个自由配置权限的秘钥对。在EOS网络里,谁拥有这个私钥,谁就拥有对应账号的对应权限。相同的秘钥对可以创建多个账号。

  • 申请测试的EOS代币,访问 http://faucet.cryptokylin.io/get_token?cryptokylinq
    {
      msg: "succeeded"
    }
  • 查询账户余额 https://tools.cryptokylin.io/#/tx/

申请来的测试EOS代币,将用于购买EOS网络上的CPU,NET,RAM资源(RAM为购买,CPU和NET是按抵押EOS多少,计算分配你多少资源使用)。相当于云服务器中的增加对应的硬件资源。CPU和NET与云服务中的一样,计算和网络资源。RAM相当于云服务器中的RAM+磁盘存储,比如发起交易时会临时消耗,隔一段时间(暂时未确定恢复时间的规则,应该小于三天,稍后查证后,在做补充),如果是合约存储数据,那就相当于写入了磁盘,如果数据不删除的话,会持续占用。

下面开始购买所需的资源,之前统计一般部署合约需要消耗300k左右的 RAM(包含临时消耗)。 CPU和NET基本分别抵押 0.1EOS就可以了。

由于我们要操作账号,所以我们要先创建个钱包,可以使用eosio 自带的keosd,也可以使用其他三方的app 钱包中购买资源。
我们在开发环境,所以直接使用keosd操作了,

  • 创建钱包
    suroudeMacBook-Pro:eosio-wallet surou$ cleos  wallet create --to-console
    Creating wallet: default
    Save password to use in the future to unlock this wallet.
    Without password imported keys will not be retrievable.
    "PW5HxNY7tKndZp8c96afmhaHezq2CA9x1dbziYXzJcvK7sVdf7kQq"

    记得备份这个钱包的解锁密码"PW5HxNY7tKndZp8c96afmhaHezq2CA9x1dbziYXzJcvK7sVdf7kQq"

  • 将上面创建的账号cryptokylinq的私钥导入新创建的钱包 (一般常规操作只需要导入active权限的私钥)
    suroudeMacBook-Pro:eosio-wallet surou$ cleos wallet import
    private key: imported private key for: EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws

    导入完成后,购买所需的RAM,CPU,NET资源
    购买10EOS的RAM

    suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com system buyram cryptokylinq cryptokylinq "10 EOS"
    executed transaction: d59370bcb3dbf1d3aaa1b5c92de23283603c6d9bfc2e38e8def5e0de8eabe6e1  128 bytes  1511 us
    #         eosio <= eosio::buyram                {"payer":"cryptokylinq","receiver":"cryptokylinq","quant":"10.0000 EOS"}
    #   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
    #  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
    #     eosio.ram <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
    #   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}
    #  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}
    #  eosio.ramfee <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}

CPU NET分别抵押10 EOS (实际使用量,一般在分别1 EOS就够了,测试网不花钱,可劲豪)

suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com system delegatebw cryptokylinq cryptokylinq "10 EOS" "10 EOS"
executed transaction: 4df295028046be07f669fd3dca1b496434f5ee36efb0cd6f31a738ff6e7b5602  144 bytes  2021 us
#         eosio <= eosio::delegatebw            {"from":"cryptokylinq","receiver":"cryptokylinq","stake_net_quantity":"10.0000 EOS","stake_cpu_quant...
#   eosio.token <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}
#  cryptokylinq <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}
#   eosio.stake <= eosio.token::transfer        {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}

此时RAM,CPU,NET资源都准备好了,相当于云服务器硬件配置都买好了
下面开始部署合约,相当于传统的Web开发,将API程序部署到云服务器

suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com  set contract cryptokylinq Calculator/ -p  
Reading WASM from Calculator/Calculator.wasm...
Publishing contract...
executed transaction: f0086795a65644a96e282b165e1fc146869a37cfd6a2e51528367f7594d4d21f  3056 bytes  1329 us
#         eosio <= eosio::setcode               {"account":"cryptokylinq","vmtype":0,"vmversion":0,"code":"0061736d01000000015d1060047f7e7e7e0060000...
#         eosio <= eosio::setabi                {"account":"cryptokylinq","abi":"0e656f73696f3a3a6162692f312e30000206726573756c740002046e616d65046e6...

下一步我们直接测下合约action接口,相当于直接测试云服务器的API接口

suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com  push action cryptokylinq doit '["cryptokylinq","1","2"]' -p cryptokylinq
executed transaction: 7c0de68f77a656f98cd54e03f37dbb00a1a325cee844614968d7d05a2668f36c  120 bytes  1834 us
#  cryptokylinq <= cryptokylinq::doit           {"from":"cryptokylinq","firstParm":1,"secoundParm":2}

查询下结果

suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com  get table cryptokylinq cryptokylinq results
{
  "rows": [{
      "name": "cryptokylinq",
      "iresult": 3
    }
  ],
  "more": false
}

可以看到,已经将结果写到了链上 "iresult": 3,相当于常规Web程序数据库中已存入数据
目前此DApp对比常规Web程序已经完成了,API和数据库部分,下面开始完成前端的调用

DApp前端

前端与链交互,是通过rpc进行操作的,并且eosio 官方也提供了 eosjs.js 开发库,方便操作。
html中调用的关键js代码为

import EOS from 'eosjs'
const EOS_CONFIG = {
  contractName: "cryptokylinq", // Contract name 更好理解的应该是合约的部署所在的账号名
  contractReceiver: "cryptokylinq", // User executing the contract (should be paired with private key)
  clientConfig: {
    keyProvider: '5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b', // Your private key
    httpEndpoint: 'http://api-kylin.starteos.io', // EOS http endpoint
    chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191'
  }
}
//执行合约
DoContract(){
let eosClient = EOS(EOS_CONFIG.clientConfig)
    eosClient.contract(EOS_CONFIG.contractName)
      .then((contract) => {
      //执行链上合约
        contract.doit(EOS_CONFIG.contractReceiver,"1","2", { authorization: [EOS_CONFIG.contractReceiver] })
          .then((res) => { console.log("Success") })
          .catch((err) => {console.log("Fail"); console.log(err) })
      })
}

查询结果

eos.getTableRows(true,"cryptokylinq","cryptokylinq","results")

返回

{
    "rows":[
        {
        "name":"cryptokylinq",
        "iresult":3
        }
    ],
        "more":false
}

至此完成了DApp从合约编写,合约部署,账号创建,合约调用,eosjs使用,一整套简单的流程。

EOS开发库收集(持续更新)

开发语言

C Sharp

  • https://github.com/GetScatter/eos-sharp

    Eos eos = new Eos(new EosConfigurator()
    {    
      HttpEndpoint = "https://nodes.eos42.io", //Mainnet
      ChainId = "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
      ExpireSeconds = 60,
      SignProvider = new DefaultSignProvider("myprivatekey")
    });
  • https://github.com/eosnewyork/EOSDotNet

      var chainAPI = new ChainAPI("https://api.eosnewyork.io");
    
      string  _code  =  "eosio.token", _action  =  "transfer", _memo  =  "";
      TransferArgs  _args  =  new  TransferArgs(){ from  =  "account1", to  =  "account2", quantity  =  "1.0000 EOS", memo  =  _memo };
    
      //called asynchronously
      var abiJsonToBinAsync = await chainAPI.GetAbiJsonToBinAsync(_code, _action, _args);
      //called synchronously
      var abiJsonToBinSync = chainAPI.GetAbiJsonToBin(_code, _action, _args);

    Go

  • https://github.com/eoscanada/eos-go

      api := eos.New("http://testnet1.eos.io")
    
      infoResp, _ := api.GetInfo()
      accountResp, _ := api.GetAccount("initn")
      fmt.Println("Permission for initn:", accountResp.Permissions[0].RequiredAuth.Keys)

Python

  • https://github.com/EvaCoop/eosjs_python

      from eosjs_python import Eos
    
      eos = Eos({
          'http_address': 'http://172.18.0.1:8888',
          'key_provider': '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'
      })
    
      eos.newaccount({
          'creator': 'eosio',
          'name': 'mytestacc13',
          'owner_public_key': 'EOS7vTHtMbZ1g9P8BiyAGD7Ni7H6UALVLVCW13xZrXT4heCBke3it',
          'active_public_key': 'EOS8KKKYBBdwrmXRRynDXSxTX2qoT9TA4agahXXF4ccUgRCy81RNc',
          'buyrambytes_bytes': 8192,
          'delegatebw_stake_net_quantity': '100.0000 SYS',
          'delegatebw_stake_cpu_quantity': '100.0000 SYS',
          'delegatebw_transfer': 0
      })
  • https://github.com/eosnewyork/eospy

      # Get chain information
      pycleos --url https://api.eosnewyork.io get info
    
      # get information about a block
      pycleos --url https://api.eosnewyork.io get block 447
    
      # Retrieve an account from the blockchain
      pycleos --url https://api.eosnewyork.io get account --account eosio
    
      # Retrieve the code and ABI for an account
      pycleos --url https://api.eosnewyork.io get code --account eosio

PHP

  • https://github.com/zyq20130111/eos_php

      /* Create the rest client */
      EosApiRestClient eosApiRestClient = EosApiClientFactory.newInstance("http://127.0.0.1:8888").newRestClient();
    
      /* Create the json array of arguments */
      Map<String, String> args = new HashMap<>(4);
      args.put("from", "currency");
      args.put("to", "eosio");
      args.put("quantity", "44.0000 CUR");
      args.put("memo", "My First Transaction");
      AbiJsonToBin data = eosApiRestClient.abiJsonToBin("currency", "transfer", args);```
    
      /* Get the head block */
      Block block = eosApiRestClient.getBlock(eosApiRestClient.getChainInfo().getHeadBlockId());

Java

Dapp js 相关

ubuntu 安装最新版本 yarn

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn