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

[EOS源码分析]EOS账号钱包密钥等基本概念及操作实践

cleos

cleos应用程序是用户端命令行交互模块,用于解析用户命令,执行钱包,账号等如下操作
cleos依赖keosd和nodeos等应用程序处理这些操作。
当keosd没有启动时,cleos会自动启动该程序,对应的代码如下:

void ensure_keosd_running() {
    …
    binPath.append("keosd"); // if cleos and keosd are in the same installation directory

    if (boost::filesystem::exists(binPath)) {
        //启动keosd
        ::boost::process::child keos(binPath, pargs,
                                     bp::std_in.close(),
                                     bp::std_out > bp::null,
                                     bp::std_err > bp::null);
        if (keos.running()) {
            std::cerr << binPath << " launched" << std::endl;
            keos.detach();
            sleep(1);
        } else {
        }
    } else {
    }
}

action&message&Transaction

目前在EOS中message和action的概念是一样的,实际上action是自3.0以后的新规范,message的概念已经可以抛弃,由于3.0版本发布不久,互联网上的大部分文字和材料依然使用message的概念.
一个transaction包含一个或者多个action, 和以太坊交易概念差不多。所以,你可以简单的把action看成以太坊的transaction

key

EOS里的key就是public key公钥,类似以太坊的地址。key是通过类似RSA,椭圆曲线算法生成的公钥。

$ cleos create key
Private key: 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU
Public key: EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv

以太坊地址可以收款,付款,而EOS的key只具备验证作用,只有account才具备收款,付款功能。
注意:cleos并不保存生成的私钥,所以得你自己记录private key,public key

钱包

钱包是一个私钥库,里面保存着私钥公钥对, 类似以太坊里的keystore信息。里面保存的私钥数据其实是私钥加密后数据,需要用户输入密码才能还原出真正的私钥。智能合约的action执行都需要钱包来解锁相关的公钥(key).所以钱包可以看成是钥匙箱。

创建默认钱包并导入key

  • 创建钱包
    $ cleos wallet create
    Creating wallet: default
    Save password to use in the future to unlock this wallet.
    Without password imported keys will not be retrievable.
    "PW5KWPhDnRCLBFjBuEBdUmFP4a2F8H36KvTv4DhtfHRZokrqAK9bT"

    钱包本身也是有密码的,用来加密钱包数据的

  • 导入key
    $ cleos wallet import 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
  • 查看wallet里的keys
    $ cleos wallet keys
    [[
      "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV”, //公钥
      “5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3”    //私钥
    ]
    ]

    创建其他钱包并导入key

    
    $ cleos wallet create -n test
    Creating wallet: test
    Save password to use in the future to unlock this wallet.
    Without password imported keys will not be retrievable.
    “PW5KNZ27fR8qcYpsw2B5uM7yir1CnYVSejAi8R3sYdRK4DyW6BN6v"

$ cleos wallet import 5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU

$ build/programs/cleos/cleos wallet keys
[[
//这个是default钱包的key
"EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
“5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3”
],[
//这个是test钱包的key
"EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv",
"5JdchMrwMwD1PsZKCjpbaCQ4aJ3cFKzSWmCQfRzKCiGrDWds3PU"
]
]

####列出所有钱包
```shell
$ build/programs/cleos/cleos wallet list
Wallets:
[
  "default *",
  "test *"
]

解锁及锁定钱包

$ cleos wallet unlock -n test
$ cleos wallet lock -n test

-n xxx是指定操作的钱包名字,如果不带-n xxx则是操作默认钱包

钱包数据文件

$ls ~/eosio-wallet/
config.ini    default.wallet    test.wallet
$ cat ~/eosio-wallet/default.wallet
{
  "cipher_keys": "0bcea4c898817192773ca628e9f829eed0c1e5024bf3de3d5032e2e029842b9756b46e37357403858a6f38a67268f30d9f70d8ce45e930802927f227d65b0162615872173637142a645cb50f1816d47c187e6345f8ced73896efe5a5a636197595f457641379408afb501dc483b108c131cf4f7fdd3381497c6a3245b9773c02217a4cd77b30f4da23a519c5caa01e56"
}

可见一个钱包对应~/eosio-wallet/的一个文件,文件里面的内容就是密码加密私钥后的内容

Account(账号)

EOS的账号对应以太坊的智能合约地址,它扩展了账号的概念,一个账号由智能合约+权限管理构成。

权限管理

权限管理模块可以精细的定义一个Account/key或者几个Account/key(比如多重签名机制)对账号数据的访问权限。目前有两个默认的权限类型

  • owner权限
    可以修改任意账号和key的权限
  • active
    可转账,DPOS投票,及其它上层定义的权限修改
    用户也可自定义权限类型。同时引入了权重和阈值的概念,该机制使得多重签名实现非常简单,也更有扩展性,permission格式如下
    {
      "threshold": 100,/*An integer that defines cumulative signature weight required for authorization*/
      "keys": [], /*An array made up of individual permissions defined with an EOS PUBLIC KEY*/
      "accounts": [] /*An array made up of individual permissions defined with an EOS ACCOUNT*/
    }

配置示例如下

默认权限配置:

Permission Accounts/Keys Weight Threshold
owner 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1
active 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1

组合和自定义权限

Permission Accounts/Keys Weight Threshold
owner 2
@bob 1
@stacy 1
active 1
@bob 1
@stacy 1
publish 2
@bob 2
@stacy 1
EOS7KBTMkUq4VPakqsZUnZfBbMbS2U7cn9qSa3q6G5ZzEeUeNSVgv 1
  • bob和stacy两个账号一起才能行使owner权限

  • bob, stacy两个账号的任一一个都可以行使active权限

  • bob可以行使publish权限,但是stacy和”EOS7KBTMkU…"没法单独行使publish权限,但是他们一起可以行使publish权限
    这些权限的检测一般在执行action操作时执行。

    创建账号account

    cleos create account [OPTIONS] creator name OwnerKey ActiveKey

    该命令本身是一个action,会产生一个transaction,最后会保存在链上的,所以该操作依赖nodeos程序,必须启动nodeos程序。上面的OwnerKey,ActiveKey都是公钥。creator必须是一个已经存在的账号,这里就有个问题了,我们第一次创建账号,从哪里获取这个creator账号?这个账号就是eosio, eosio这个特殊账号是在nodeos启动时自动生成的,且这个账号的private key,和public key是hardcode固定的。当然搭建私有网络你可以通过修改config文件来修改这两个值,但是你同时也得修改这个私有网络的其他节点对应的private key, public key值。

    $ cleos create account -j eosio testaccount EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
    {
    "transaction_id": "18440905e1dc17eecee17eaa50f5c589f703c3c4f66738316c2f56d12ce7ec84",
      "action_traces": [{
            ...
            "data": {
              "creator": "eosio",
              "name": "testaccount",
              "owner": {
                "threshold": 1,
                "keys": [{
                    "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
                    "weight": 1
                  }
                ],
                "accounts": [],
                "waits": []
              },
              "active": {
                "threshold": 1,
                "keys": [{
                    "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
                    "weight": 1
                  }
                ],
                "accounts": [],
                "waits": []
              }
            },
            ….
          },
          ...
        }
      ],
    }
    }

    会产生两种默认权限owner和active
    创建账号其实就是创建一个action

    chain::action create_newaccount(const name& creator, const name& newaccount, public_key_type owner, public_key_type active) {
     return action {
        tx_permission.empty() ? vector<chain::permission_level>{{creator,config::active_name}} : get_account_permissions(tx_permission),
        eosio::chain::newaccount{
           .creator      = creator,
           .name         = newaccount,
           .owner        = eosio::chain::authority{1, {{owner, 1}}, {}},
           .active       = eosio::chain::authority{1, {{active, 1}}, {}}
        }
     };
    }

    你在nodeos程序端会看到一个trxs

    1254502ms thread-0   producer_plugin.cpp:585       block_production_loo ] Produced block 000006d1c28d6c68... #1745 @ 2018-05-22T02:20:54.500 signed by eosio [trxs: 1, lib: 1744, confirmed: 0]

    查看账号信息

      $ cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
      memory:
           quota:        -1 bytes  used:      2.66 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
           limit:               -1 us

    修改权限命令格式

    $ cleos set account permission [OPTIONS] account permission authority [parent]
  • account,表示要修改的账户

  • permission 表示要设置的权限(上面的owner,active,publish)

  • authority 权限内容,JSON字符串

  • parent 上级权限

    修改active权限

      $ cleos set account permission testaccount active '{"threshold" : 1, "keys" : [], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":1}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ owner
      executed transaction: b1bc9680a9ba615a6de8c3f7c692d7d28ff97edae245bb40f948692b14ea6c15  160 bytes  189 us
      #         eosio <= eosio::updateauth            {"account":"testaccount","permission":"active","parent":"owner","auth":{"threshold":1,"keys":[],"acc...
      warning: transaction executed locally, but may not be confirmed by the network yet
      $ build/programs/cleos/cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 bob@active, 1 stacy@active,
      memory:
           quota:        -1 bytes  used:     2.674 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
           limit:               -1 us 

    新增自定义权限

      $ cleos set account permission testaccount publish '{"threshold" : 2, "keys" : [{"permission":{"key":"EOS8X7Mp7apQWtL6T2sfSZzBcQNUqZB7tARFEm9gA9Tn9nbMdsvBB","permission":"active"},"weight":1}], "accounts" : [{"permission":{"actor":"bob","permission":"active"},"weight":2}, {"permission":{"actor":"stacy","permission":"active"},"weight":1}]}’ active
      executed transaction: a0f8d79f92e375b13c6f6da55b5b1f1aeebcbf1240a9bd287d3c845fc6b6941d  200 bytes  171 us
      #         eosio <= eosio::updateauth            {"account":"testaccount","permission":"publish","parent":"active","auth":{"threshold":2,"keys":[{"ke...
      warning: transaction executed locally, but may not be confirmed by the network yet
      $ cleos  get account testaccount
      privileged: false
      permissions:
           owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
              active     1:    1 bob@active, 1 stacy@active,
                 publish     2:    1 EOS1111111111111111111111111111111114T1Anm2 bob@active, 1 stacy@active,
      memory:
           quota:        -1 bytes  used:     3.066 Kb   
    
      net bandwidth: (averaged over 3 days)
           used:                -1 bytes
           available:           -1 bytes
           limit:               -1 bytes
    
      cpu bandwidth: (averaged over 3 days)
           used:                -1 us   
           available:           -1 us   
    
           limit:               -1 us   

    权限应用场景

    一个account(账号)可以有智能合约,智能合约有各种action,每个action可以指定permission,这样就可以限制action的执行权限了,具体细节我会在后面的博文里单独介绍

    转载自:http://blog.csdn.net/itleaks

[EOS源码分析]EOS区块同步及生产

本文所有实践都是基于EOS v1.0.1,请切到该分支然后对比源码,切换命令git checkout v1.0.1

提到区块生产和同步,我们肯定有几个疑问?

  • 节点同步
  1. 节点从哪里同步数据,节点如何知道哪些节点有最新的区块数据以同步数据
  2. genesis文件不同的节点互联会怎么样?
  3. 节点什么时候同步数据
  • 区块生产
    节点什么时候生产?节点是一启动就开始生产?还是等同步好了才生产?任何节点都可以生产?
    接下来我们就一一解答

    区块同步数据

    节点信息获取

    节点要同步数据,必须得知道从哪里节点获取他们的区块信息。这个靠显式声明的种子节点解决,种子节点然后会发回更多节点信息。在config.ini文件里添加如下内容即可添加种子节点,这些种子节点信息一般是EOS链的创建者和运营者管理的,比如jungleTestNet测试网,我们要想加入这个测试网络就需要配置如下信息。

    p2p-peer-address = jungle.cryptolions.io:19876
    p2p-peer-address = jungle.cryptolions.io:29876
    p2p-peer-address = dev.cryptolions.io:39876
    不同genesis文件的节点互联

    首先来说下genesis是什么东西
    genesis文件是一个用来描述创世块信息的文件

    最重要的是initial_timestamp和initial_key

  • initial_timestamp跟区块生产相关,后面区块生产分析时会用到这个值
  • initial_key是创建这个genesis的公钥,系统将会以这个公钥创建eosio这个系统账号,而系统的智能合约的核心操作都需要系统账号授权,也就是说initial key控制着eosio.system等智能合约。eosio.system智能合约控制整个系统的。比如生产者注册就必须使用eosio.system智能合约。
    void initialize_database() {
        authority system_auth(conf.genesis.initial_key);
        create_native_account( config::system_account_name, system_auth, system_auth, true );
    }

    不同genesis文件,就代表是不同的链,这样的节点其实是不能互联的。但是由于节点服务器信息(ip, 端口)是公开的,不排除有误加的情况。所以必须有机制拒绝这样的连接,这个是连接握手节点通过检测chain_id来实现的

    void net_plugin_impl::handle_message( connection_ptr c, const handshake_message &msg) {
        if (msg.generation == 1) {
           //检测是否属于同一个链
           if( msg.chain_id != chain_id) {
              elog( "Peer on a different chain. Closing connection");
              c->enqueue( go_away_message(go_away_reason::wrong_chain) );
              return;
           }
        }
    }

    chain_id不一样时,你会发现如下错误输出

    那chain_id具体是怎么生成的呢?

    
    controller_impl( const controller::config& cfg, controller& s  )
     :chain_id( cfg.genesis.compute_chain_id() ){
    }

chain::chain_id_type genesis_state::compute_chain_id() const {
digest_type::encoder enc;
fc::raw::pack( enc, *this );
return chain_id_type{enc.result()};
}

其实就是将genesis文件的数据做一次类似hash的操作
#####同步时机
新连接建立时握手阶段就会互相检测各自链的状态,并开始同步区块数据
```cpp
 void sync_manager::recv_handshake (connection_ptr c, const handshake_message &msg) {
      controller& cc = chain_plug->chain();
      //本地不可逆区块number
      uint32_t lib_num = cc.last_irreversible_block_num( );
      //remote peer不可逆区块number
      uint32_t peer_lib = msg.last_irreversible_block_num;
      reset_lib_num(c);
      c->syncing = false;

      //接下来就是比较本地区块高度和远端节点区块高度,有如下4情况
      //--------------------------------
      // sync need checks; (lib == last irreversible block)
      //
      // 0. my head block id == peer head id means we are all caugnt up block wise
      // 1. my head block num < peer lib - start sync locally
      // 2. my lib > peer head num - send an last_irr_catch_up notice if not the first generation
      //
      // 3  my head block num <= peer head block num - update sync state and send a catchup request
      // 4  my head block num > peer block num ssend a notice catchup if this is not the first generation
      //
      //-----------------------------

      uint32_t head = cc.head_block_num( );
      //本地节点的区块头num小于远端节点不可逆区块头num时,同步
      if (head < peer_lib) {
         fc_dlog(logger, "sync check state 1");
         // wait for receipt of a notice message before initiating sync
         if (c->protocol_version < proto_explicit_sync) {
            start_sync( c, peer_lib);
         }
         return;
      }
      ….
}
区块生产

一个节点要生成区块,必须满足两个条件

  1. chain-> _production_enabled==true
    _production_enabled=true有几种情况
  • config.ini和或者启动时带有enable-stale-production
    这里看到enable-stale-production这个参数的作用了吧,它的意思是哪怕本地区块头是过时的区块,也继续生产。那什么情况下需要置位这个参数呢?私有链有其他生产者之前必须赋值这个变量
    创世块的时间戳(genesis文件中的initial_timestamp字段)是一个确定的值,节点nodeos第一次启动时当前时间肯定远大于这个创世块的时间戳,因而正常情况下,系统应该已经基于这个创世块生产很多后续区块,因而需要先同步到最新再生产新块的。但是由于这个链是你自己刚建立的,你确定没有其他节点基于你本地的区块(包括创世块)生产了其他区块,因此立即基于当前区块生产的新块是合法的且也是你应该做的。
  • 区块同步完成时,这个很好理解,当我们已经同步下来所有区块时,我们自然可以基于最新的区块生产新的区块
    void on_incoming_block(const signed_block_ptr& block) {
         //如果下一个块的截止时间大于当前时间,意味着同步完成
         if( chain.head_block_state()->header.timestamp.next().to_time_point() >= fc::time_point::now() )
            _production_enabled = true;
    }
  1. 节点被投票成了21个代表中的一个,且到了生产区块的turn(21个代表节点是分时生产区块的)
    我们知道EOS采用的DPOS+BFT,一个节点要成为真正“生产者”,必须被系统其他节点投票出来成为21个超级节点中的一个。同时,被选择为超级节点后,也是和其他20个节点轮流生产。其实,这里存在一个生产者注册流程,也就说一个节点光配置为producer是不够的,还需要通过eosio.system智能合约注册生产者,这个操作权限只授予给了创世块的initial_key的持有人。

    producer_plugin_impl::start_block_result producer_plugin_impl::start_block() {
    chain::controller& chain = app().get_plugin<chain_plugin>().chain();
    const auto& hbs = chain.head_block_state();
    _pending_block_mode = pending_block_mode::producing;
    
    // Not our turn
    //获取当前生产者
    const auto& scheduled_producer = hbs->get_scheduled_producer(block_time);
    
    // If the next block production opportunity is in the present or future, we're synced.
    if( !_production_enabled ) {
       //还在同步,不能生产
       _pending_block_mode = pending_block_mode::speculating;
    } else if( _producers.find(scheduled_producer.producer_name) == _producers.end()) {//检测当前生产者是否属于本节点的
       //不是自己的turn
       _pending_block_mode = pending_block_mode::speculating;
    } else if (signature_provider_itr == _signature_providers.end()) {
       //没有producer的签名,即没有producer的私钥
       elog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key", scheduled_producer.block_signing_key));
       _pending_block_mode = pending_block_mode::speculating;
    } else if ( _pause_production ) {
       ...
    }
    try {
       uint16_t blocks_to_confirm = 0;
       chain.abort_block();
       //轮到该节点的producer生产了,真正生产区块
       chain.start_block(block_time, blocks_to_confirm);
    } FC_LOG_AND_DROP();
    }
    生产流程图

转载自:http://blog.csdn.net/itleaks

针对EOS主网一些常用操作

查询余额(当前合约的余额)
cleos -u http://mainnet.genereos.io get currency balance bcskilltoken bcskillsurou BCSKILL
部署发币合约
cleos -u http://mainnet.genereos.io set contract bcskilltoken build/contracts/eosio.token -p bcskilltoken
执行发币_创建
cleos -u http://mainnet.genereos.io push action bcskilltoken create '[ "bcskillsurou", "1000000000.0000 BCSKILL"]' -p bcskilltoken
派发
cleos -u http://mainnet.genereos.io push action bcskilltoken issue '[ "bcskillsurou", "1000000000.0000 BCSKILL", "test" ]' -p bcskilltoken
转账
cleos -u http://mainnet.genereos.io push action bcskilltoken transfer '[ "bcskillsurou", "bcskillcom11", "100.0000 BCSKILL", "test transfer" ]' -p bcskillsurou
购买ram
cleos -u http://mainnet.genereos.io system buyram bcskillsurou bcskillsurou '0.5 EOS'
抵押cpu bandwidth
cleos -u http://mainnet.genereos.io system delegatebw bcskillsurou bcskillsurou '0.5 EOS' '0.5 EOS'
创建账户
cleos -u http://mainnet.genereos.io system newaccount --stake-net '0.0010 EOS' --stake-cpu '0.0010 EOS' --buy-ram-kbytes 8
获取ram价格
cleos -u http://mainnet.genereos.io get table eosio eosio rammarket

对于查询EOS的例子如下
cleos -u http://mainnet.genereos.io get currency balance eosio.token zbeosbp11111 EOS

持续更新

Scatter 在测试网络中与Dapp交互使用

本文章将演示Scatter在测试网络中,与Dapp项目进行常规操作。

1.Dapp测试项目搭建

1.clone Dapp测试项目代码
git clone https://github.com/generEOS/eostoolkit.git
2.编译运行eostoolkit
  • 进入eostoolkit源码目录,修改package.json中dependencies的eosjs为当前最新版本,发文时"eosjs": "^15.0.1"
  • 执行npm install安装项目依赖,然后执行npm start执行。
  • chrome打开http://127.0.0.1:8080 访问Dapp项目。
  • 点击Attach Identity链接Scatter扩展,匹配适合的身份信息。

    对于Scatter 查找是否存在针对该Dapp项目网络的身份,是根据Scatter添加的身份是否拥有与此Dapp项目相同的chain id身份。

    2.修改Dapp项目配置,支持EOS测试网络。

  • 由于我们要测的是自己的EOS测试网络,所以修改下Dapp项目代码

    修改eostoolkit\src\scatter-client.jsx,修改host,port,chainId为我们测试网络对应的参数

    const httpNetwork = {
      blockchain:'eos',
      host:'192.168.1.112', // ( or null if endorsed chainId )
      port:8888, // ( or null if defaulting to 80 )
      chainId:"cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f", // Or null to fetch automatically ( takes longer )
    }

    修改 networkhttpNetwork

    connectIdentity() {
        this.state.scatter.getIdentity({accounts:[{chainId:httpNetwork.chainId, blockchain:httpNetwork.blockchain}]}).then(() => {
            console.log('Attach Identity');
            console.log(this.state.scatter.identity);
            this.setState({identity: window.scatter.identity});
        }).catch(error => {
            console.error(error);
            console.error(chainId:httpNetwork.chainId);
        });
    }
  • 等待Dapp项目重新编译运行后,再次点击Attach Identity链接Scatter扩展,匹配适合的身份信息。

    已经正确检测到我们上篇文章添加的账号身份。
  • 选择对应的Identity,点击 Select Identity
  • 点击Accept Dapp项目将接受此身份信息

    3.测试Dapp与Scatter的交互

    此处只举查看账号信息的例子,其余的自行测试
    Lookup Accounts输入Enter Account Name 查询账户 dapp.exec1

    点击Search