使用EOS3.0发行代币

一 运行eos客户端,启动私有网络

./nodeos -e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin --plugin eosio::account_history_api_plugin

eosio generated block bcc817bc... #637 @ 2018-05-10T09:22:13.000 with 0 trxs, lib: 636
eosio generated block 527d07ae... #638 @ 2018-05-10T09:22:13.500 with 0 trxs, lib: 637

二 创建钱包

./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.
"PW5J9ASbLXgn2RmuW2YdHwuzsys3YJkGJonegRmxK52DyWCKcYNXn"

(1)钱包私钥可以用于钱包解锁,
命令为 “cleos wallet unlock --password 私钥”

(2)如重复执行“./cleos wallet create”,则报错,提示钱包文件已存在

Error 3140001: Wallet already exists
Try to use different wallet name.
Error Details:
Wallet with name: 'default' already exists at /home/li/.local/share/eosio/nodeos/data/./default.wallet

三 加载 bios 合约

./cleos set contract eosio ../../contracts/eosio.bios -p eosio

Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: 24cee6eb3def42662b3cc2879e75f6b3386c8d5b8dda2eb31d1bffd5170d0c80  3280 bytes  2200576 cycles
#         eosio <= eosio::setcode               {"account":"eosio","vmtype":0,"vmversion":0,"code":"0061736d0100000001581060037f7e7f0060057f7e7e7e7e...
#         eosio <= eosio::setabi                {"account":"eosio","abi":{"types":[],"structs":[{"name":"set_account_limits","base":"","fields":[{"n...

注意:eosio.bios 路径根据实际情况选择目录

如果我们把 EOS 看做一个操作系统,那么 bios 合约就是让我们能进行最底层的操作的合约,其它合约的运行建立在 bios 合约的基础之上,所以我们先来加载 bios 合约。

四 创建账户

(1)生成密钥对
./cleos create key

Private key: 5Ke4aKjY9PGZJnxNV54REsGUqQqRZNpJKr6unnkWLmU1oTT16Cw
Public key: EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm

(2)把用户私钥导入钱包
./cleos wallet import 5Ke4aKjY9PGZJnxNV54REsGUqQqRZNpJKr6unnkWLmU1oTT16Cw

imported private key for: EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm

(3)用公钥创建账户

./cleos create account eosio bytemaster EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm

executed transaction: 78d3a0752630842bc08d99a2d584982b8ccc89a98b0b5aeadd0792ea76737830  352 bytes  102400 cycles
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"bytemaster","owner":{"threshold":1,"keys":[{"key":"EOS7KYYgetJuZ7SpkQj9aD...

(4)查看账户,确实创建成功了
./cleos get account bytemaster

{
  "account_name": "bytemaster",
  "permissions": [{
      "perm_name": "active",
      "parent": "owner",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm",
            "weight": 1
          }
        ],
        "accounts": []
      }
    },{
      "perm_name": "owner",
      "parent": "",
      "required_auth": {
        "threshold": 1,
        "keys": [{
            "key": "EOS7KYYgetJuZ7SpkQj9aDw2Sui7EuLGsKqRp7gq7qQZMx7gkfcGm",
            "weight": 1
          }
        ],
        "accounts": []
      }
    }
  ]
}

(5)另外再创建一个账户,tester
./cleos create key

Private key: 5KTVgaQwPkyCxJdZqTiZLMbxjZfcqKqFLnLSoUvuGUoUdvTCFaR
Public key: EOS6H3yfhgcrH1mJs92bbvzYAakMShjcj8SmnmQd3NFBSwVz6EKyM

./cleos wallet import 5KTVgaQwPkyCxJdZqTiZLMbxjZfcqKqFLnLSoUvuGUoUdvTCFaR

imported private key for: EOS6H3yfhgcrH1mJs92bbvzYAakMShjcj8SmnmQd3NFBSwVz6EKyM

./cleos create account eosio tester
EOS6H3yfhgcrH1mJs92bbvzYAakMShjcj8SmnmQd3NFBSwVz6EKyM EOS6H3yfhgcrH1mJs92bbvzYAakMShjcj8SmnmQd3NFBSwVz6EKyM

executed transaction: 8ff09b833b2c9a8b9143af165158e052c5ae10c8f35554aa678a95c962d1fd58  352 bytes  102400 cycles
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"tester","owner":{"threshold":1,"keys":[{"key":"EOS6H3yfhgcrH1mJs92bbvzYAa...

五 部署货币合约

(1)查看bytemaster的合约
./cleos get code bytemaster

code hash: 0000000000000000000000000000000000000000000000000000000000000000

(2)部署代币合约
./cleos set contract bytemaster ../../contracts/eosio.token

Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: e1b0fdb6626b4e64640efa8a63cf6c6fb487c5b9897c30ba9778667827b91425  8040 bytes  2200576 cycles
#         eosio <= eosio::setcode               {"account":"bytemaster","vmtype":0,"vmversion":0,"code":"0061736d01000000018a011660067f7e7f7f7f7f006...
#         eosio <= eosio::setabi                {"account":"bytemaster","abi":{"types":[],"structs":[{"name":"transfer","base":"","fields":[{"name":...

(3)查看bytemaster的合约,看到代币合约已经部署
./cleos get code bytemaster

code hash: 7c6a300874835ad928de4f30712023758157bd50cb423ab039443f56a84167ff

(4)创建代币
./cleos push action bytemaster create '[ "bytemaster", "1000000000.0000 EOS", 0, 0, 0]' -p bytemaster@active

executed transaction: 56f722fc175854b52a56d32c98d41d3e1c8b3bcfb9cc8c2520503b04bc037a68  248 bytes  104448 cycles
#    bytemaster <= bytemaster::create           {"issuer":"bytemaster","maximum_supply":"1000000000.0000 EOS","can_freeze":0,"can_recall":0,"can_whi...

其中create接口,在eosio.token.abi有声明

六 发行代币

./cleos push action bytemaster issue '[ "bytemaster", "100.0000 EOS", "memo" ]' -p bytemaster

issue '[ "bytemaster", "100.0000 EOS", "memo" ]' -p bytemaster
executed transaction: 4f932f48780e3c83d82ba263844302398f42f79e8bd2b52d3f8eaa2bc0deb79e  256 bytes  107520 cycles
#    bytemaster <= bytemaster::issue            {"to":"bytemaster","quantity":"100.0000 EOS","memo":"memo"}
>> issue

其中issue接口,在eosio.token.abi有声明

七 测试代币转账

./cleos push action bytemaster transfer '[ "bytemaster", "tester", "25.0000 EOS", "m" ]' -p bytemaster

executed transaction: e7ebf695a213842dc3049f388fda128a8d9c05d078273bc75eeb5863fabcd104  256 bytes  109568 cycles
#    bytemaster <= bytemaster::transfer         {"from":"bytemaster","to":"tester","quantity":"25.0000 EOS","memo":"m"}
>> transfer
#        tester <= bytemaster::transfer         {"from":"bytemaster","to":"tester","quantity":"25.0000 EOS","memo":"m"}

其中transfer接口,在eosio.token.abi有声明

八 总结

《1 了解发布token的顺序
启动私有链
创建钱包
加载 bios 合约
创建用户
部署token合约
创建代币
发行代币

九 引用

https://github.com/EOSIO/eos/wiki/Tutorial-eosio-token-Contract 《Tutorial eosio token Contract》
http://liyuechun.org/2018/04/13/eos-contract/ 《第二篇 - 从零到壹学习EOS - EOS Currency 合约案例》
https://mp.weixin.qq.com/s/3_4KCu8YcbGlEO2Pn6vK_A 《最友善EOS开发教程——从如何发行「新垣结衣币」说起》

十 eosio.token合约相关文件内容

包含(eosio.token.abi, eosio.token.hpp eosio.token.cpp)

eosio.token.abi

{
  "types": [],
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": [
        {"name":"from", "type":"account_name"},
        {"name":"to", "type":"account_name"},
        {"name":"quantity", "type":"asset"},
        {"name":"memo", "type":"string"}
      ]
    },{
     "name": "create",
     "base": "",
     "fields": [
        {"name":"issuer", "type":"account_name"},
        {"name":"maximum_supply", "type":"asset"},
        {"name":"can_freeze", "type":"uint8"},
        {"name":"can_recall", "type":"uint8"},
        {"name":"can_whitelist", "type":"uint8"}
     ]
  },{
     "name": "issue",
     "base": "",
     "fields": [
        {"name":"to", "type":"account_name"},
        {"name":"quantity", "type":"asset"},
        {"name":"memo", "type":"string"}
     ]
  },{
      "name": "account",
      "base": "",
      "fields": [
        {"name":"balance", "type":"asset"},
        {"name":"frozen", "type":"uint8"},
        {"name":"whitelist", "type":"uint8"}
      ]
    },{
      "name": "currency_stats",
      "base": "",
      "fields": [
        {"name":"supply", "type":"asset"},
        {"name":"max_supply", "type":"asset"},
        {"name":"issuer", "type":"account_name"},
        {"name":"can_freeze", "type":"uint8"},
        {"name":"can_recall", "type":"uint8"},
        {"name":"can_whitelist", "type":"uint8"},
        {"name":"is_frozen", "type":"uint8"},
        {"name":"enforce_whitelist", "type":"uint8"}
      ]
    }
  ],
  "actions": [{
      "name": "transfer",
      "type": "transfer",
      "ricardian_contract": ""
    },{
      "name": "issue",
      "type": "issue",
      "ricardian_contract": ""
    }, {
      "name": "create",
      "type": "create",
      "ricardian_contract": ""
    }

  ],
  "tables": [{
      "name": "accounts",
      "type": "account",
      "index_type": "i64",
      "key_names" : ["currency"],
      "key_types" : ["uint64"]
    },{
      "name": "stat",
      "type": "currency_stats",
      "index_type": "i64",
      "key_names" : ["currency"],
      "key_types" : ["uint64"]
    }
  ],
  "ricardian_clauses": []
}

eosio.token.hpp

/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#pragma once

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

#include <string>

namespace eosiosystem {
   class system_contract;
}

namespace eosio {

   using std::string;

   class token : public contract {
      public:
         token( account_name self ):contract(self){}

         void create( account_name issuer,
                      asset        maximum_supply,
                      uint8_t      issuer_can_freeze,
                      uint8_t      issuer_can_recall,
                      uint8_t      issuer_can_whitelist );


         void issue( account_name to, asset quantity, string memo );

         void transfer( account_name from,
                        account_name to,
                        asset        quantity,
                        string       memo );

      private:

         friend eosiosystem::system_contract;

         inline asset get_supply( symbol_name sym )const;
         
         inline asset get_balance( account_name owner, symbol_name sym )const;

      private:
         struct account {
            asset    balance;
            bool     frozen    = false;
            bool     whitelist = true;

            uint64_t primary_key()const { return balance.symbol.name(); }
         };

         struct currency_stats {
            asset          supply;
            asset          max_supply;
            account_name   issuer;
            bool           can_freeze         = true;
            bool           can_recall         = true;
            bool           can_whitelist      = true;
            bool           is_frozen          = false;
            bool           enforce_whitelist  = false;

            uint64_t primary_key()const { return supply.symbol.name(); }
         };

         typedef eosio::multi_index<N(accounts), account> accounts;
         typedef eosio::multi_index<N(stat), currency_stats> stats;

         void sub_balance( account_name owner, asset value, const currency_stats& st );
         void add_balance( account_name owner, asset value, const currency_stats& st,
                           account_name ram_payer );

      public:
         struct transfer_args {
            account_name  from;
            account_name  to;
            asset         quantity;
            string        memo;
         };
   };

   asset token::get_supply( symbol_name sym )const
   {
      stats statstable( _self, sym );
      const auto& st = statstable.get( sym );
      return st.supply;
   }

   asset token::get_balance( account_name owner, symbol_name sym )const
   {
      accounts accountstable( _self, owner );
      const auto& ac = accountstable.get( sym );
      return ac.balance;
   }

} /// namespace eosio

eosio.token.cpp

/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */

#include <eosio.token/eosio.token.hpp>

namespace eosio {

void token::create( account_name issuer,
                    asset        maximum_supply,
                    uint8_t      issuer_can_freeze,
                    uint8_t      issuer_can_recall,
                    uint8_t      issuer_can_whitelist )
{
    require_auth( _self );

    auto sym = maximum_supply.symbol;
    eosio_assert( sym.is_valid(), "invalid symbol name" );
    eosio_assert( maximum_supply.is_valid(), "invalid supply");
    eosio_assert( maximum_supply.amount > 0, "max-supply must be positive");

    stats statstable( _self, sym.name() );
    auto existing = statstable.find( sym.name() );
    eosio_assert( existing == statstable.end(), "token with symbol already exists" );

    statstable.emplace( _self, [&]( auto& s ) {
       s.supply.symbol = maximum_supply.symbol;
       s.max_supply    = maximum_supply;
       s.issuer        = issuer;
       s.can_freeze    = issuer_can_freeze;
       s.can_recall    = issuer_can_recall;
       s.can_whitelist = issuer_can_whitelist;
    });
}


void token::issue( account_name to, asset quantity, string memo )
{
    print( "issue" );
    auto sym = quantity.symbol.name();
    stats statstable( _self, sym );
    const auto& st = statstable.get( sym );

    require_auth( st.issuer );
    eosio_assert( quantity.is_valid(), "invalid quantity" );
    eosio_assert( quantity.amount > 0, "must issue positive quantity" );
    eosio_assert( quantity <= st.max_supply - st.supply, "quantity exceeds available supply");

    statstable.modify( st, 0, [&]( auto& s ) {
       s.supply += quantity;
    });

    add_balance( st.issuer, quantity, st, st.issuer );

    if( to != st.issuer )
    {
       SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
    }
}

void token::transfer( account_name from,
                      account_name to,
                      asset        quantity,
                      string       /*memo*/ )
{
    print( "transfer" );
    require_auth( from );
    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" );

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

void token::sub_balance( account_name owner, asset value, const currency_stats& st ) {
   accounts from_acnts( _self, owner );

   const auto& from = from_acnts.get( value.symbol.name() );
   eosio_assert( from.balance.amount >= value.amount, "overdrawn balance" );

   if( has_auth( owner ) ) {
      eosio_assert( !st.can_freeze || !from.frozen, "account is frozen by issuer" );
      eosio_assert( !st.can_freeze || !st.is_frozen, "all transfers are frozen by issuer" );
      eosio_assert( !st.enforce_whitelist || from.whitelist, "account is not white listed" );
   } else if( has_auth( st.issuer ) ) {
      eosio_assert( st.can_recall, "issuer may not recall token" );
   } else {
      eosio_assert( false, "insufficient authority" );
   }

   from_acnts.modify( from, owner, [&]( auto& a ) {
       a.balance -= value;
   });
}

void token::add_balance( account_name owner, asset value, const currency_stats& st, account_name ram_payer )
{
   accounts to_acnts( _self, owner );
   auto to = to_acnts.find( value.symbol.name() );
   if( to == to_acnts.end() ) {
      eosio_assert( !st.enforce_whitelist, "can only transfer to white listed accounts" );
      to_acnts.emplace( ram_payer, [&]( auto& a ){
        a.balance = value;
      });
   } else {
      eosio_assert( !st.enforce_whitelist || to->whitelist, "receiver requires whitelist by issuer" );
      to_acnts.modify( to, 0, [&]( auto& a ) {
        a.balance += value;
      });
   }
}

} /// namespace eosio

EOSIO_ABI( eosio::token, (create)(issue)(transfer) )