3.1 로컬넷에 토큰 스마트 컨트랙트 배포

이더리움의 스마트 컨트랙트 중 가장 많은 비중을 차지하는 건 토큰입니다. 이더리움은 블록체인 위에 올릴 수 있는 토큰을 쉽게 제작할 수 있어 많은 인기를 끌었고 암호화폐 시장이 크게 성장했습니다. EOS도 쉽게 스마트 컨트랙트 배포가 가능하며, 토큰 발행도 이더리움보다 쉽습니다.

프로젝트 생성

토큰 발행에 사용할 폴더를 생성합니다. EOS 폴더 안에 다음과 같이 생성합니다. 윈도 OS나 비주얼 스튜디오 코드에서 폴더를 생성하면 리눅스에서 인식 못하거나 권한 설정에 문제가 발생할 수 있으니 주의해야 합니다.

```
$ mkdir project
$ cd project
$ mkdir firstToken

```

 < project 생성 화면 > 

```
devtooth@DESKTOP-2:~/eos/eos$ mkdir project
devtooth@DESKTOP-2:~/eos/eos$ cd project
devtooth@DESKTOP-2:~/eos/eos/project$mkdir firstToken
devtooth@DESKTOP-2:~/eos/eos/project$ls
firstToken
devtooth@DESKTOP-2:~/eos/eos/project$cd firstToken
devtooth@DESKTOP-2:~/eos/eos/project/firstToken$

```

vim 에디터를 이용하여 firstToken.cpp 파일과 firstToken.hpp 파일을 생성합니다. 비주얼 스튜디오 코드에서 생성한 폴더와 파일이 나타날 것입니다.

 < visual studio code 화면 > 

생성한 폴더와 파일이 보이지 않으면 터미널에서 권한 설정을 변경해 보기 바랍니다.

```
$sudo chmod 777 폴더명 or 파일명

```

firstToken.hpp 작성

본격적으로 코딩을 시작해 보겠습니다. 먼저 헤더 파일인 firstToken.hpp를 작성합니다.

firstToken.hpp https://github.com/DevToothCrew/eos_book_example/blob/master/firstToken/firstToken.hpp
```
#pragma once // ①

②#include 
#include 
#include 
#include 

namespace devtooth {
   using std::string;
③   using namespace eosio;

④   class token : public contract {
       public:
⑤       account_name _owner;

⑥       token(account_name self):contract(self){
           _owner = self;
       }

⑦       //@abi action
       void create(account_name issuer, asset maximum_supply);
       //@abi action   
       void issue(account_name to, asset quantity, string memo);
       //@abi action
      void transfer(account_name from, account_name to, asset quantity, string memo);

       private:
⑧          //@abi table accounts i64
           struct account {
               asset balance;

               uint64_t primary_key()const { return balance.symbol.name(); }
           };
⑨         //@abi table stat i64
           struct currencies {
               asset        supply;
               asset        max_supply;
               account_name issuer;

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

⑩           typedef eosio::multi_index accounts;
           typedef eosio::multi_index stats;

⑪            void sub_balance( account_name owner, asset value );
          void add_balance( account_name owner, asset value, account_name ram_payer );
   };
}

```

소스 코드 한줄 한줄을 들어다 봅시다.

```
① #pragma once

```

헤더 파일의 중복을 방지하기 위한 선언입니다.

```
② #include 
#include 
#include 
#include 

```

스마트 컨트랙트 작성에 필요한 헤더 파일을 불러옵니다.

```
③ using namespace eosio;

```

eosio에 정의된 자료형과 함수를 사용하기 위해 선언합니다.

```
④ class token : public contract

```

Smart Contract 기능을 구현할 class를 선언합니다. eosio에 있는 contract 클래스를 상속받습니다.

```
⑤ account_name _owner;

```

account_name 타입으로 _owner 변수를 선언합니다. account_name은 EOS에서 사용하는 자료형으로 계정명을 의미합니다. 여기서는 스마트 컨트랙트를 배포하는 계정을 저장하기 위해 선언합니다.

```
⑥ token(account_name self):contract(self) {
	_owner = self;
}

```

스마트 컨트랙트가 배포될 때 실행되는 생성자 함수입니다. 여기서는 _owner 변수에 배포자를 저장합니다.

```
⑦ //@abi action
void create(account_name issuer, asset maximum_supply);
//@abi action
void issue(account_name to, asset quantity, string memo);
//@abi action
void transfer(account_name from, account_name to, asset quantity, string memo);

```

해당 계약에서 사용할 토큰을 생성하는 create 함수, 토큰을 발행하는 issue 함수, 토큰을 전송하는 transfer 함수를 선언합니다. 이 함수들을 스마트 컨트랙트의 Action으로 사용하기 위해 각 함수의 선언 바로 위에 //@abi action을 넣습니다. 그러면 abi 파일 생성 시 해당 함수가 Action 함수로 인식됩니다.

```
⑧ //@abi table accounts i64
struct account {
	asset balance;
	uint64_t primary_key()const { return balance.symbol.name();
}

```

테이블에 저장할 자료를 구조체로 선언합니다. 계정이 보유한 토큰의 수량을 확인하기 위한 asset 타입의 balance라는 변수를 멤버로 갖는 account라는 구조체를 선언했습니다. asset은 EOS에서 정의한 구조체 자료형으로, amount와 symbol을 멤버변수로 가지고 있습니다. 구조체 선언 상단에는 액션과 마찬가지로 abi에 테이블을 등록하기 위해서 //@abi table accounts i64 주석을 답니다. Accounts라는 이름의 table을 abi에 등록하겠다는 뜻입니다. 테이블에서 검색하기 위한 key는 primary_key() 함수로 등록합니다. Key는 변경되지 않는 고유값이므로 const를 붙여줍니다. 함수의 반환값이 key입니다. 여기서는 uint64_t 타입의 balance.symbol.name()의 값이 accounts라는 테이블의 key가 됩니다.

```
⑨ //@abi table stat i64
struct currencies { 
asset supply; 
asset max_supply;
account_name issuer;
uint64_t primary_key()const { return supply.symbol.name(); 
} 
};

```

EOS는 이더리움과 다르게 하나의 컨트랙트로 여러 개의 토큰을 만들 수 있습니다. 스마트 컨트랙트가 토큰에 귀속되는 게 아니라, 토큰을 함수로 만들기 때문입니다. 여러 개의 토큰을 관리하려면 각 토큰마다 최대 발행량, 현재 발행량, 발행인 등의 정보를 저장해야 합니다. 이러한 데이터는 stat이라는 테이블을 생성하고 저장합니다. 이 테이블은 발행량을 저장하기 위한 supply, 총 발행량을 기록할 max_supply, 발행인을 저장할 issuer를 멤버변수로 갖는 currencies 구조체를 저장합니다. 토큰은 이름이 중복되지 않으므로 발행하는 토큰의 symbol인 supply.symbol.name()의 값을 key로 갖습니다.

```
⑩ typedef eosio::multi_index accounts;
typedef eosio::multi_index stats;

```

앞서 선언한 테이블은 multi_index 테이블로 정의합니다. multi_index 테이블은 빠른 액세스를 위해 RAM에 상태 및 데이터를 캐시하는 방법으로, EOS에서 스마트 컨트랙트의 데이터를 저장하는 데 사용하는 컨테이너입니다. 구조체나 클래스로 구성된 데이터를 생성, 읽기, 수정, 삭제(CRUD)할 수 있으며 64비트 정수(uint64_t)로 된 기본 키로 색인할 수 있습니다. 기본 키 이외에 최대 16개의 추가 인덱스를 가질수 있으며 필드 유형은 uint64_t, uint128_t, uint256_t, long, double가 있습니다.

문법은 다음과 같습니다. Typedef eosio::multi_index [멀티인덱스의 이름]"입니다. 45번 줄을 예를 들면, 주석으로 선언했던 accounts 테이블 이름으로 account 구조체를 accounts라는 멀티 인덱스 테이블로 정의했습니다.

```
⑪ void sub_balance( account_name owner, asset value ); 
void add_balance( account_name owner, asset value, account_name ram_payer );

```

토큰을 가감하는 함수를 선언합니다. 이 함수들은 스마트 컨트랙트 내부에서만 사용하므로 abi 등록이 필요하지 않습니다.

firstToken.cpp 작성

https://github.com/DevToothCrew/eos_book_example/blob/master/firstToken/firstToken.cpp

firstToken.hpp 헤더 파일에 스마트 컨트랙트에서 사용할 함수와 테이블을 선언하고 abi 파일로 인식하도록 코드를 작성했습니다. firstToken.cpp 파일에는 헤더에 선언한 함수들을 정의합니다.

```
#include "firstToken.hpp"

namespace devtooth {

   void token::create(account_name issuer, asset maximum_supply)
   {
       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(), "Tokenwith symbol already exists");

       statstable.emplace(_self, [&](auto& s){
           s.supply.symbol = maximum_supply.symbol;
           s.max_supply    = maximum_supply;
           s.issuer        = issuer;
       });
   }

   void token::issue(account_name to, asset quantity, string memo)
   {
       auto sym = quantity.symbol;
       eosio_assert(sym.is_valid(), "Invalid symbol name");
       eosio_assert(memo.size() <= 256, "Memo has more than 256 bytes");

       auto sym_name = sym.name();
       stats statstable(_self, sym_name);
       auto existing = statstable.find(sym_name);
       eosio_assert(existing != statstable.end(), "Token with symbol does now exist, Create token before issue");
       const auto& st = *existing;

       require_auth(st.issuer);
       eosio_assert(quantity.is_valid(), "Invalid quantity");
       eosio_assert(quantity.amount > 0, "Must issue positive quantity");

       eosio_assert(quantity.symbol == st.supply.symbol, "Symbol precision mismatch");
       eosio_assert(quantity.amount <= st.max_supply.amount - st.supply.amount, "Quantity exceeds available supply");

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

       add_balance(st.issuer, quantity, 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)
   {
       eosio_assert(from != to, "Cannot transfer to self");
       require_auth(from);
       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, "Not exist symbol");

       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() <= 250, "Memo has more than 256 bytes");

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

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

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

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

   void token::add_balance(account_name owner, asset value, account_name ram_payer)
   {
       accounts to_acnts(_self, owner);
       auto to = to_acnts.find(value.symbol.name());
       if(to == to_acnts.end()){
           to_acnts.emplace( ram_payer, [&](auto& a){
               a.balance = value;
           });
       }
       else{
           to_acnts.modify(to, 0, [&](auto& a){
               a.balance += value;
           });
       }
   }
}

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

```

코드를 좀 더 세밀하게 들여다 보겠습니다.

```
#include "firstToken.hpp"

```

함수 및 테이블을 정의했던 firstToken.hpp 파일을 불러옵니다.

```
namespace devtooth

```

firstToken.hpp와 같은 namespace를 사용합니다.

```
void token::create(account_name issuer, asset maximum_supply)

```

토큰을 생성하는 create 함수입니다. 생성할 토큰을 발행할 발행인과, 최대 발행량을 인자로 넣습니다.

```
require_auth(_self);

```

create 함수의 실행자가 해당 권한이 있는지 require_auth() 함수로 확인합니다. 토큰 생성은 컨트랙트 소유자만 가능합니다. require_auth()의 기본 형태는 active 권한인지 검사합니다.

```
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");

```

eosio_assert() 함수로 예외 처리를 합니다. 사용 문법은 eosio_assert([조건], [메시지])입니다. 조건이 true면 다음 단계로 넘어가고 false면 메시지를 출력하고 동작을 멈춥니다.

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

```

이미 생성된 토큰 중 생성하려는 토큰과 중복된 심볼의 토큰이 있는지 확인합니다. 토큰 정보는 firstToken.hpp에서 정의한 multi_index_table ‘stats’에 저장되어 있으므로, 여기서는 stats 테이블을 사용합니다. Multi_index_table은 지역 객체(local instance)를 생성해 사용해야 합니다. 여기서는 statstable 이라는 지역 객체를 사용했습니다. 객체 생성 시 생성자 인자값으로 테이블 사용자, 범위(scope)를 대입합니다. 범위는 무엇이든 가능하지만 보통 테이블 key를 사용합니다. statstable 객체의 find 함수를 사용하여 같은 symbol의 토큰을 검색합니다. 테이블 내 데이터의 위치값이 existing 변수에 결과로 저장됩니다. 결괏값과 statstable.end() 값이 같다면 테이블에 자료가 없다는 뜻입니다.

```
statstable.emplace(_self, [&](auto& s){
           s.supply.symbol = maximum_supply.symbol;
           s.max_supply    = maximum_supply;
           s.issuer        = issuer;
});

```

multi_index_table에 새로운 토큰 정보를 등록합니다. Stats table 객체의 emplace 함수로 테이블에 데이터를 추가합니다. EOS 메인넷에 데이터를 저장하기 위해서는 데이터 크기만큼의 RAM이 필요합니다. 이 RAM은 따로 구매해야 합니다. 테이블에 데이터를 추가하는 것도 당연히 RAM이 소모됩니다. 때문에 emplace 함수는 첫 번째 인자값으로 램을 지불할 계정(ram_payer)을 필요로 합니다. 두 번째 인자값은 Lambda constructor 입니다. Lambda 함수는 테이블에 생성될 개체의 전체 초기화를 수행합니다. 이를 이용해 statstable 객체의 멤버들을 참조하고 값을 넣어줍니다.

```
void token::issue(account_name to, asset quantity, string memo)

```

생성된 토큰을 발행하는 issue 함수입니다.

```
stats statstable(_self, sym_name);
auto existing = statstable.find(sym_name);
eosio_assert(existing != statstable.end(), "Token with symbol does now exist, Create token before issue");

```

발행할 토큰이 이미 있는지 확인하기 위하여 multi_index_table 객체를 생성하고 검사합니다.

```
const auto& st = *existing;

```

검색 결과를 저장했던 existing의 값을 가져와서 st변수에 참조합니다.

```
require_auth(st.issuer);
eosio_assert(quantity.is_valid(), "Invalid quantity");
eosio_assert(quantity.amount > 0, "Must issue positive quantity");
eosio_assert(quantity.symbol == st.supply.symbol, "Symbol precision mismatch");
eosio_assert(quantity.amount <= st.max_supply.amount - st.supply.amount, "Quantity exceeds available supply");

```

st에 참조된 값을 이용하여 예외검사합니다.

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

```

statstable의 modify 함수를 사용해서 mult_index_table 값을 변경합니다. Modify 함수는 3개의 인자값을 필요로 합니다. 첫번째는 업데이트할 객체를 가리키는 반복자(iterator), 두번째는 업데이트되는 행의 저장소 사용량에 대한 RAM 지불자 계정, 세번째는 대상 객체를 업데이트하는 Lambda 함수입니다.

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

```

발행인(issuer)에게 발행하는 수만큼의 토큰을 입금합니다.

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

```

발행을 받는 계정이 발행인이 아닐 경우의 예외처리 입니다. SEND_INLINE_ACTION 함수를 이용해 transfer 함수를 실행하여 발행인(issuer)의 토큰을 수령인(to)에게 인자값 만큼 전송합니다.

```
void token::transfer(account_name from, account_name to, asset quantity, string memo)

```

토큰을 전송하는 transfer 함수입니다. 인자값으로 보내는 사람 from, 받는 사람 to, 수량 quantity, 메모 memo를 필요로 합니다.

```
eosio_assert(from != to, "Cannot transfer to self");

```

본인에게 전송할 수 없도록 예외처리 합니다.

```
auto sym = quantity.symbol.name();
stats statstable(_self, sym);
const auto& st = statstable.get(sym, "Not exist symbol");

```

전송하고자 하는 토큰이 존재하는지 확인하기 위해서 stats 객체를 생성하고 검색합니다.

```
sub_balance(from, quantity);

```

from 유저 계정에서 전송 수량만큼의 토큰을 차감합니다.

```
add_balance(to, quantity, from);

```

to 유저 계정에 전송 수량만큼의 토큰을 추가합니다.

```
void token::sub_balance(account_name owner, asset value)
Token 차감 함수를 정의합니다.
const auto& from = from_acnts.get(value.symbol.name(), "No balance object found");
eosio_assert(from.balance.amount >= value.amount, "Overdrawn balance");

```

accounts 테이블에 있는 토큰 정보를 변경하기 위해 객체를 생성하고, get 함수로 데이터를 가져옵니다.

```
if(from.balance.amount == value.amount){
           from_acnts.erase(from);
}

```

from 계정이 현재 보유하고 있는 토큰 수량이 전송하려는 수량과 같다면, 더 이상 남아 있는 토큰이 없으므로 테이블에서 from 계정 정보를 삭제합니다.

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

```

from 계정의 토큰 수량을 차감합니다.

```
void token::add_balance(account_name owner, asset value, account_name ram_payer)

``

토큰 수량의 증감 함수입니다.

```
accounts to_acnts(_self, owner);
auto to = to_acnts.find(value.symbol.name());
if(to == to_acnts.end()){
to_acnts.emplace( ram_payer, [&](auto& a){
           		a.balance = value;
});
}

```

테이블에 수령인의 정보가 없으면 새로 추가합니다.

```
else{
to_acnts.modify(to, 0, [&](auto& a){
a.balance += value;
});
}

```

테이블에서 수령인의 토큰 정보를 변경합니다.

```
EOSIO_ABI ( devtooth::token, (create)(issue)(transfer) )

```

EOSIO_ABI는 클래스에 있는 액션들을 abi에 전달하는 매크로입니다. 이전 버전의 apply() 함수를 포함하며, 블록체인에서 액션들을 지정하고 호출하는데 사용합니다.

문법은 다음과 같습니다.

EOSIO_ABI( [액션이 포함된 컨트랙트 클래스], (액션1)(액션2)..(액션n) )

firstToken 컨트랙트 배포

firstToken.hpp, firstToken.cpp 두 소스 코드를 컴파일해서 wasm, abi 파일을 생성하겠습니다. 먼저 eosiocpp 명령어의 -o 옵션을 사용하여 wasm 파일을 생성합니다.

```
$ eosiocpp -o firstToken.wasm firstToken.cpp

```

 < wasm file 생성 명령어 > 

```
devtooth@DESKTOP-2:~/eos/project/fistToken$eosiocpp -o firstToken.wasm firstToken.cpp
WARNING: this tool is deprecated and will be removed in a future relase
Please consider using the EOSIO.CDT (https://github.com/EOSIO/eosio.cdt/)
devtooth@DESKTOP-2:~/eos/project/firstToken$

```

eosiocpp 명령어의 -g 옵션을 사용하여, firstToken.hpp 파일로 abi 파일을 생성합니다.

```
$ eosiocpp -g firstToken.abi firstToken.hpp

```

 < abi file 생성 명령어 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$ eosiocpp -g firstToken.abi firstToken.hpp
WARNING: this tool is deprecated and will be removed in a future relase
Please consider using the EOSIO.CDT (https://github.com/EOSIO/eosio.cdt/)
2018-10-23TO4:01:52.811 thread-0   abi)generator.hpp:68	       ricardian_contracts ] Warning,no ricardian clauses found for

Generated firstToken.abi…
devtooth@DESKTOP-2:~/eos/project/firstToken$

```

생성된 wasm과 abi 두 파일로 스마트 컨트랙트를 로컬 노드에 배포(Deploy)합니다.

```
$ cleos set contract [계정이름] [deploy_path] [wasm_path] [abi_path]
// 예시
$ cleos set contract devtooth ./ ./firstToken.wasm ./firstToken.abi

```

 < contract set 명령어 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$
devtooth@DESKTOP-2:~/eos/project/firstToken$cleos set contract devtooth ./ ./firstToken.wasm ./firstToken.abi
Reading WAST/WASM from ./firstToken.wasm…
Using already assembled WASM…
Publishing contract…
executed transaction: ce14b40f7b63ed55e7897a2a8bd64bde56379526d91222b83a77d8cc943a5832  8136 bytes 803 us
#	eosio<=eosio::setcode	{"account":"devtooth","vmtype":0,"vmversion":0,"code":"0061736d01000000017e1560037f7e7f0060057f7…
{"account":"devtooth","abi":"0e656f73696f3a3a6162692f312e300005076163636f756e7400010762616c616e6…
warning: transaction executed locally, but may not be confirmed by the network yet
devtooth@DESKTOP-2:~/eos/project/firstToken$

```

파일의 이름과 폴더의 이름이 같으면 폴더의 상위 디렉토리에서 다음 명령어로 배포할 수 있습니다.

```
$ cleos set contract [계정 이름] [folder_name]
```

```
// 예시
$ cd .. 
$ cleos set contract devtooth ./firstToken

```

스마트 컨트랙트를 로컬 노드에 배포했습니다. 이제 컨트랙트에 있는 action으로 토큰을 생성해보겠습니다. 컨트랙트의 action은 다음 명령어로 실행합니다.

```
$ cleos push action [컨트랙트 배포한 계정 이름] [액션 이름] ‘[ "[인자값 1]" "[인자값 2]" ]’ -p [액션 사용 계정 이름]

```
```
// 예시
$ cleos push action devtooth create ‘["devtooth" "10000.000 DEV"]’ -p devtooth

```

주의해야 할 점은 인자값을 넣을 때 ‘ ‘와 " " 잘 구분하여 ‘[" "]’의 규칙을 잘 지켜야 합니다. firstToken에서 create 액션을 실행할 때 인자값으로 발행할 계정과 발행할 토큰의 총량을 지정합니다.

 < token create 액션 실행 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$ cleos push action devtooth create ‘["devtooth" "10000.000 DEV"]’ -p devtooth
executed transaction: d0fa70cb5e3f05c9fa021cefd2fcaa12da784d01342ac777c8cb431edbd65ff5  120 bytes 2945us
# devtooth<=devtooth::create	{"issuer":"devtooth","maximum_supply":"10000.000 DEV"}
warning: transaction executed locally, but may not be confirmed by the network yet
devtooth@DESKTOP-2:~/eos/project/firstToken$

```

issue와 transfer 액션을 사용해 보겠습니다. 명령어에서 action 이름을 create에서 issue로 바꿔주고, issue action에 맞는 인자값으로 변경합니다.

```
// 예시
$  cleos push action devtooth issue ‘["devtooth" "10000.000 DEV" "MyFirstToken"]’ -p devtooth

```

 < token issue 액션 실행 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$ cleos push action devtooth issue ‘["devtooth" "10000.000 DEV" "MyFirstToken"]’ -p devtooth
executed transaction: 1725ee60ba0bd3e870c783c15eb25646cf35f25fbba44673dee5aba2f2d755a9  136 bytes 678 us
# devtooth<=devtooth::issue	{"to":"devtooth","quantity":"10000.000 DEV","memo":"MyFirstToken"}
warning: transaction executed locally, but may not be confirmed by the network yet

```

발행된 토큰을 계정에서 계정으로 transfer를 이용해 전송을 봅시다. cleos push action 명령어로 액션을 동작시킵니다. 이때 다음과 같이 action에 맞는 인자값을 넣습니다.

```
// 예시
$ cleos push action devtooth transfer ‘["devtooth" "hanbitmedia" "5000.000 DEV" "FirstTransfer"]’ -p devtooth

```

 < token transfer 액션 실행 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$ cleos push action devtooth transfer ‘["devtooth" "hanbitmedia" "5000.000 DEV" "FirstTransfer"]’ -p devtooth
executed transaction: 37605d8e8abc0fba49cfa7556c168f9629b47561465f5897d8600a119555b89ae  144 bytes  759 us
# devtooth<=devtooth::transfer	{"from":"devtooth","to":"hanbitmedia","quantity":"5000.000 DEV","memo":"FirstTransfer"}
# devtooth<=devtooth::transfer	{"from":"devtooth","to":"hanbitmedia","quantity":"5000.000 DEV","memo":"FirstTransfer"}
warning: transaction executed locally, but may not be confirmed by the network yet

```

생성된 토큰 정보는 모두 table에 저장됩니다. stat Table의 정보는 다음 명령어로 확인할 수 있습니다.

```
$ cleos get table [컨트랙트 배포한 계정 이름] [스코프] [테이블 이름]

```

```
// 예시
$ cleos get table devtooth DEV stat

```

 < devtooth 계정의 DEV token table > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$cleos get table devtooth DEV stat
{
  "rows": [{
	"supply": "10000.000 DEV"
         "max_supply": "10000.000 DEV",
         "issuer": "devtooth"
      }
    ],
   "more": false
}

```

stat 테이블이 아닌 account를 직접 검색하면 가지고 있는 여러 토큰 정보를 찾을 수 있습니다.

```
// 예시
$ cleos get table devtooth devtooth accounts

```

 < devtooth 계정의 accounts 정보 > 

```
devtooth@DESKTOP-2:~/eos/project/firstToken$ cleos get table devtooth devtooth accounts
{
  "rows": [{
	"balance":  "5000.000 ABC"
    },{
         "balance":  "10000.000 DEV"
     }
    ],
   "more": false
}

```

Prev | Next