1 of 22

eosio.token.hpp public actions

#include <eosio/asset.hpp>#include <eosio/eosio.hpp>��CONTRACT token : public contract {� public:� using contract::contract;�� ACTION create( const name& issuer, const asset& maximum_supply);� ACTION issue( const name& to, const asset& quantity, const string& memo );� ACTION transfer( const name& from, const name& to, const asset& quantity, const string& memo );�

static asset get_balance( const name& token_contract_account, const name& owner, const symbol_code& sym_code )� {� accounts accountstable( token_contract_account, owner.value );� const auto& ac = accountstable.get( sym_code.raw() );� return ac.balance;� }�

using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;

2 of 22

Go to:

3 of 22

static asset get_balance(

const name& token_contract_account,

const name& owner,

const symbol_code& sym_code )

Contracts cannot call a getter function on another contract

If another contract wishes to read another, it can use the table API to search the data. Or, for more convenience, a contract can write a function that others can borrow to perform common operations. E.g.

#include “eosio.token.hpp”

...

asset balance = token::get_balance("eosio.token"_n, user, symbol_code("EOS"));

4 of 22

using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>

Instead of

action( permission_level{user,"active"_n}, // permission"eosio.token"_n, // account "code""notify"_n, // actionstd::make_tuple(get_self(), user, quantity, memo)

).send();

Use

token::transfer_action eosio_token_transfer("eosio.token"_n, {get_self(), "active"_n});�...

eosio_token_transfer.send(get_self(), user, quantity, memo);

5 of 22

.code permission

To send a code to another contract, it must have a permission set with the actor = the account to send to, and permission = code

https://www.google.com/search?q=eosio.code+permission

6 of 22

eosio.token.hpp private tables and functions

private:� ...

TABLE account {� asset balance;� uint64_t primary_key()const { return balance.symbol.code().raw(); }� };�� typedef eosio::multi_index< "accounts"_n, account > accounts;� ...

void sub_balance( const name& owner, const asset& value );� void add_balance( const name& owner, const asset& value, const name& ram_payer );� };

7 of 22

Asset class

eosio::asset

amount: int64_t

eosio::symbol

precision: uint8_t

eosio::asset

amount: int64_t 1000.0

precision: uint8_t 4

code: uint64_t "EOS"

eosio::symbol_code

code: uint64_t

symbol sym = symbol("EOS", 4);�asset myAsset = asset(1000.0, sym);�myAsset.is_valid();

8 of 22

TABLE account {� asset balance;� uint64_t primary_key()const { return balance.symbol.code().raw(); }� };

The account name is used as the scope of the table. And the asset symbol as the search index. This means account name need not exist in RAM.

9 of 22

eosio.token.cpp transfer()

void token::transfer( const name& from, const name& to, const asset& quantity, const string& memo )�{� check( from != to, "cannot transfer to self" );� require_auth( from );� check( is_account( to ), "to account does not exist");� auto sym = quantity.symbol.code();� stats statstable( get_self(), sym.raw() );� const auto& st = statstable.get( sym.raw() );�� require_recipient( from );� require_recipient( to );�� check( quantity.is_valid(), "invalid quantity" );� check( quantity.amount > 0, "must transfer positive quantity" );� check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );� check( 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 );�}

10 of 22

check( from != to, "cannot transfer to self" );

Check a predicate is true, if not throw an error with a message

11 of 22

auto sym = quantity.symbol.code();

const auto& st = statstable.get( sym.raw() );

...

check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );

Use the symbol code (e.g. “EOS”) to find the token in the contracts statistics table.

Make sure the precision (number of decimal places) provided in the argument matches the token. E.g.

“1.000 EOS” as the argument it will fail, because the EOS token has 4 decimal places. It should be “1.0000 EOS”

12 of 22

require_recipient( to );

This is a special wrapper to send an action to another contract with the same action and arguments as the action it was sent. This notification can then be received by another contract. It is the same as writing this:

The to account can receive this by writing a receiver, and execute some code if it wants.

13 of 22

Receiving a notification

[[eosio::on_notify("eosio.token::transfer")]]

void deposit(name from, name to, asset quantity, std::string memo);

This is different from using an ACTION macro. It tells the compiler to trigger this function if and only if the action is sent from the eosio.token account, and wants to execute the transfer() transaction.

The arguments of the function must match the arguments of the sending action. Wildcards supported e.g.

[[eosio::on_notify("*::*")]]

14 of 22

check( quantity.is_valid(), "invalid quantity" );�check( quantity.amount > 0, "must transfer positive quantity" );�...

check( memo.size() <= 256, "memo has more than 256 bytes" );

Check the asset is valid

Check the amount is valid

Check the memo is not too long

15 of 22

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

Check is to account has authorized transaction (returns bool)

Figure out who should pay for RAM

16 of 22

void token::sub_balance(

const name& owner,

const asset& value

)

{� accounts from_acnts( get_self(), owner.value );�� const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" );� check( from.balance.amount >= value.amount, "overdrawn balance" );�� from_acnts.modify( from, owner, [&]( auto& a ) {� a.balance -= value;� });�}

Code: eosio.token (account with write access / the account the contract is deployed to)

Scope: from

Search with: symbol code

17 of 22

const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" );�check( from.balance.amount >= value.amount, "overdrawn balance" );

Use the muti_index_table API to search for a value and if none is found throws an error.

Searches using the symbol code.

Check that the balance is greater than the amount to be sent.

18 of 22

add_balance(

const name& owner,

const asset& value,

const name& ram_payer

)

{� accounts to_acnts( get_self(), owner.value );� auto to = to_acnts.find( value.symbol.code().raw() );� if( to == to_acnts.end() ) {� to_acnts.emplace( ram_payer, [&]( auto& a ){� a.balance = value;� });� } else {� to_acnts.modify( to, same_payer, [&]( auto& a ) {� a.balance += value;� });� }�}

19 of 22

to_acnts.modify( to, same_payer, [&]( auto& a ) {� a.balance += value;� });

same_payer

Is used to tell the API to charge the same account that was previously charged for RAM for this row.

20 of 22

Extra topics

Debugging with print()

Using containers in tables (vector, map, list etc) is possible

Understanding the dispatcher,

Creating secondary indexes

Singleton tables - use when only on variable (row) is needed

Error handling (note that deferred transactions may be depreciated)

21 of 22

More examples

22 of 22

Contract writing tools

Developer documentationhttps://developers.eos.io/

Contact API - guide to the eosio library� https://eosio.github.io/eosio.cdt/latest

eosio.contracts - system contracts� https://github.com/EOSIO/eosio.contracts/tree/master/contracts

Eoslib - eosio library implementations (advanced)� https://github.com/EOSIO/eosio.cdt/tree/master/libraries/eosiolib

Chain libraries - used my nodeos core (advanced)� https://github.com/EOSIO/eos/tree/master/libraries/chain