이전 글에서 Aptos 네트워크에서 Hello World 프로그램을 개발하는 방법을 간략히 소개했습니다. 이제부터는 DeFi 애플리케이션 개발과 보안 관련 사항에 대해 좀 더 깊이 살펴보겠습니다. 늘 그렇듯이, 기본적이지만 중요한 개념들부터 시작하고자 합니다. 이 글에서는 Aptos 코인 (즉, Aptos의 대체 가능 토큰 [1])의 개발 및 관리, 그리고 상호작용 방법에 대해 중점적으로 다루겠습니다.
요약
이 글에서 다루는 내용:
- Aptos 코인이란 무엇인가?
- 코인을 생성하고 관리하는 방법은?
- 코인과 상호작용하는 방법은?
0x1. Aptos 코인에 대하여
DeFi의 원자와 같은 존재인 토큰(또는 코인)은 블록체인 생태계에서 널리 사용되고 있습니다. 전자 화폐, 스테이킹 지분, 조직 관리를 위한 투표권 등 다양한 것들을 표현하는 데 활용될 수 있습니다. 어떤 의미에서, DeFi의 일상적인 활동은 단순히 블록체인 시스템 전반에 걸친 방대한 양의 토큰 흐름으로 볼 수 있습니다.
이더리움은 토큰에 대한 표준 집합을 개발했습니다. 가장 잘 알려진 것은 ERC20으로, 표준 ERC20 토큰이 준수해야 하는 인터페이스를 규정합니다. ERC20은 대체 가능 토큰 표준이며, ERC721과 같은 대체 불가능 토큰 표준도 존재합니다.
다른 블록체인 시스템과 유사하게, Aptos도 각 블록체인에서 디지털 자산이 어떻게 생성되고 사용되는지를 정의하는 토큰 표준 [2]을 가지고 있습니다. 구체적으로, Aptos에서 대체 가능 토큰은 **코인(coin)**이라고 하며, 대체 불가능 토큰(즉, NFT)은 **토큰(token)**이라고 합니다. 이어서 Aptos 코인을 생성하고, 관리하며, 상호작용하는 방법에 대해 살펴보겠습니다.
0x2. 첫 번째 코인 생성 및 관리
Aptos는 공식 표준 모듈(ERC20과 유사한)인 coin.move를 제공합니다. 이 모듈의 API를 호출함으로써 누구든지 쉽게 자신만의 코인을 만들 수 있습니다. 또한 coin.move는 복잡한 DeFi 애플리케이션을 구축하는 데 중요하고 유용한 코인 관리를 위한 권한 메커니즘도 제공합니다. 이어서 이 모듈을 기반으로 코인을 생성하는 방법을 시연하겠습니다.
이전 글에서 언급했듯이, 다음 명령을 입력하여 프로젝트를 생성할 수 있습니다:
aptos move init --name my_coin
그런 다음 sources 폴더 아래에 새 Move 파일을 생성해야 합니다. 이제 다음 샘플 코드로 채워보겠습니다. 이 코드는 BSC라는 표준 코인을 생성하고 관리하기 위한 bsc 모듈을 정의합니다.
module BlockSec::bsc{
use aptos_framework::coin;
use aptos_framework::event;
use aptos_framework::account;
use aptos_std::type_info;
use std::string::{utf8, String};
use std::signer;
struct BSC{}
struct CapStore has key{
mint_cap: coin::MintCapability<BSC>,
freeze_cap: coin::FreezeCapability<BSC>,
burn_cap: coin::BurnCapability<BSC>
}
struct BSCEventStore has key{
event_handle: event::EventHandle<String>,
}
fun init_module(account: &signer){
let (burn_cap, freeze_cap, mint_cap) = coin::initialize<BSC>(account, utf8(b"BSC"), utf8(b"BSC"), 6, true);
move_to(account, CapStore{mint_cap: mint_cap, freeze_cap: freeze_cap, burn_cap: burn_cap});
}
public entry fun register(account: &signer){
let address_ = signer::address_of(account);
if(!coin::is_account_registered<BSC>(address_)){
coin::register<BSC>(account);
};
if(!exists<BSCEventStore>(address_)){
move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
};
}
fun emit_event(account: address, msg: String) acquires BSCEventStore{
event::emit_event<String>(&mut borrow_global_mut<BSCEventStore>(account).event_handle, msg);
}
public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
let mint_coin = coin::mint<BSC>(amount, mint_cap);
coin::deposit<BSC>(to_address, mint_coin);
emit_event(to_address, utf8(b"minted BSC"));
}
public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
let burn_coin = coin::withdraw<BSC>(account, amount);
coin::burn<BSC>(burn_coin, burn_cap);
emit_event(signer::address_of(account), utf8(b"burned BSC"));
}
public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
let freeze_address = signer::address_of(account);
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"freezed self"));
}
public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"emergency freezed"));
}
public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
emit_event(unfreeze_address, utf8(b"unfreezed"));
}
}
0x2.1 기본 설계
먼저 구조체 부분을 살펴보겠습니다. 총 세 가지 구조체가 정의되어 있습니다.
BSC구조체: 코인의 고유 식별자로 사용됩니다. 따라서 이 코인은BlockSec::bsc::BSC경로를 통해 고유하게 식별될 수 있습니다.CapStore구조체:aptos_framework::coin모듈에서 얻은 일부 권한(capability)을 저장하는 데 사용됩니다. 이 권한들은 특정 특수 작업에 대한 허가에 해당하며, 이후에 설명하겠습니다.BSCEventStore구조체: 사용자 이벤트를 기록하는 데 사용됩니다.
이러한 구조체들 다음에는 init_module 함수가 있으며, 이 함수는 모듈을 초기화하는 데 사용되고 모듈이 체인에 배포될 때 단 한 번만 호출됩니다. 이 함수에서 모듈은 coin::initialize<BSC>를 호출하여 BlockSec::bsc::BSC를 새 코인의 고유 식별자로 등록합니다.
public fun initialize<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
initialize_internal(account, name, symbol, decimals, monitor_supply, false)
}
/// 코인을 발행하는 데 필요한 권한.
struct MintCapability<phantom CoinType> has copy, store {}
/// 코인 저장소를 동결하는 데 필요한 권한.
struct FreezeCapability<phantom CoinType> has copy, store {}
/// 코인을 소각하는 데 필요한 권한.
struct BurnCapability<phantom CoinType> has copy, store {}
등록 후에는 aptos_framework::coin에서 BlockSec::bsc::BSC 타입을 사용하는 모든 제네릭 함수가 이 코인에 대해 작동합니다. 이 등록 과정은 세 가지 권한, 즉 MintCapability, FreezeCapability, BurnCapability를 반환합니다. 이 권한들은 각각 코인 발행, 사용자 계정 동결, 코인 소각에 필요합니다. 어떤 의미에서, 이러한 권한 구조체의 기능은 열쇠와 유사합니다. 특정 권한의 자물쇠를 여는 데 사용되며, 누군가가 열쇠를 가지고 있으면 해당 권한을 얻을 수 있습니다. 여기서는 이러한 권한들을 나중에 사용하기 위해 CapStore 구조체(이 모듈의 관리자/배포자가 소유)에 저장합니다.
한편, 등록 과정에서 관련 정보를 기록하기 위해 CoinInfo 구조체가 관리자 주소 아래에 저장됩니다:
/// 특정 코인 타입에 대한 정보. 코인 생성자의 계정에 저장됩니다.
struct CoinInfo<phantom CoinType> has key {
name: string::String,
/// 코인의 심볼, 일반적으로 이름의 축약형.
/// 예를 들어, 싱가포르 달러는 SGD입니다.
symbol: string::String,
/// 사용자 표현을 얻기 위해 사용되는 소수점 자리수.
/// 예를 들어, `decimals`가 `2`라면, `505` 코인의 잔액은
/// 사용자에게 `5.05` (`505 / 10 ** 2`)로 표시되어야 합니다.
decimals: u8,
/// 현재 존재하는 이 코인 타입의 양.
supply: Option<OptionalAggregator>,
}
init_module 함수 호출 후, 코인이 체인에 등록됩니다. 그러나 현재로서는 유통이 존재하지 않기 때문에 아무도 이 코인을 사용할 수 없습니다. 코인을 사용 가능하게 만들려면 발행, 배분, 소각 등의 작업이 지원되어야 합니다. 이러한 작업들은 코인을 등록할 때 얻은 권한들을 필요로 합니다.
0x2.2 코인 관리
이 코인은 다음 규칙을 따르도록 설계되었습니다:
- 관리자(admin)만 코인을 발행할 수 있습니다.
- 사용자는 언제든지 자신의 코인을 소각할 수 있습니다.
- 사용자는 언제든지 자신의 계정을 동결/해제할 수 있습니다.
이에 따라 다섯 가지 관리 함수, 즉 mint_coin, burn_coin, freeze_self, emergency_freeze, unfreeze를 정의합니다. 처음 두 함수는 각각 코인 발행과 코인 소각을 담당하며, 나머지 세 함수는 계정 동결 및 해제에 사용됩니다.
코인 발행
우리 모듈에서 mint_coin 함수는 코인을 발행하는 데 사용됩니다. 관리자만 코인을 발행할 수 있으므로, 이 함수에서 해당 권한을 검증해야 합니다.
public entry fun mint_coin(cap_owner: &signer, to_address: address, amount: u64) acquires CapStore, BSCEventStore{
let mint_cap = &borrow_global<CapStore>(signer::address_of(cap_owner)).mint_cap;
let mint_coin = coin::mint<BSC>(amount, mint_cap);
coin::deposit<BSC>(to_address, mint_coin);
emit_event(to_address, utf8(b"minted BSC"));
}
이 함수는 세 가지 매개변수를 필요로 합니다:
cap_owner는&signer타입으로, 즉 트랜잭션의 개시자입니다.to_address는 발행된 코인이 입금될 주소를 나타냅니다.amount는 발행되는 코인의 수량을 나타냅니다.
세 가지 단계로 구성됩니다: 코인 발행 권한 획득, 코인 발행, 코인 입금.
먼저, mint_coin 함수의 시작 부분에서 signer::address_of(cap_owner)를 통해 트랜잭션 개시자의 계정 주소를 얻을 수 있습니다. 그 후 borrow_global<CapStore>를 사용하여 해당 계정이 CapStore를 소유하고 있는지 확인함으로써 모듈의 관리자인지 검증합니다. 이를 통해 관리자만 코인을 발행할 수 있으며, 다른 사용자는 이 단계에서 실패하게 됩니다.
둘째, mint_coin 함수는 aptos_framework::coin 모듈의 mint 함수를 호출하여 코인을 발행합니다.
public fun mint<CoinType>(
amount: u64,
_cap: &MintCapability<CoinType>,
): Coin<CoinType> acquires CoinInfo {
if (amount == 0) {
return zero<CoinType>()
};
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::add(supply, (amount as u128));
};
Coin<CoinType> { value: amount }
}
여기서 MintCapability가 필요합니다. 구체적으로, _cap이라는 매개변수가 MintCapability의 참조로 전달되어야 합니다. 그러면 MintCapability 권한과 관련된 허가도 그에 따라 이전됩니다. 명시적인 접근 제어는 없지만, 검증은 Move 언어에 의해 강제됩니다.
셋째, mint_coin 함수는 deposit 함수를 호출하여 발행된 코인을 지정된 to_address에 입금합니다.
팁#1: 권한이 있는 계정에 대한 접근 제어는 유사한 방법을 사용하여 검증할 수 있습니다.
코인 소각
코인 소각 절차는 코인 발행과 다릅니다. 구체적으로, 관리자만 mint_coin 함수를 호출할 수 있는 반면, 모든 사용자가 burn_coin 함수를 호출할 수 있습니다. 이를 위해 burn_coin 함수는 이러한 사용자들을 위해 권한을 일시적으로 상승시켜야 합니다. 즉, BurnCapability 권한을 획득해야 합니다.
public entry fun burn_coin(account: &signer, amount: u64) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let burn_cap = &borrow_global<CapStore>(owner_address).burn_cap;
let burn_coin = coin::withdraw<BSC>(account, amount);
coin::burn<BSC>(burn_coin, burn_cap);
emit_event(signer::address_of(account), utf8(b"burned BSC"));
}
이 함수는 두 가지 매개변수를 필요로 합니다:
account는&signer타입으로, 즉 트랜잭션의 개시자입니다.amount는 소각되는 코인의 수량을 나타냅니다.
세 가지 단계로 구성됩니다: 코인 소각 권한 획득, 코인 출금, 코인 소각.
분명히, aptos_framework::coin 모듈의 burn 함수는 호출자가 BurnCapability에 대한 참조를 전달하도록 요구하지만, 이 권한은 관리자의 CapStore에 저장되어 있습니다. 따라서 일반 사용자가 보유한 코인을 소각하기 위해 이 권한을 얻을 수 있도록 허용해야 합니다.
public fun burn<CoinType>(
coin: Coin<CoinType>,
_cap: &BurnCapability<CoinType>,
) acquires CoinInfo {
let Coin { value: amount } = coin;
assert!(amount > 0, error::invalid_argument(EZERO_COIN_AMOUNT));
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::sub(supply, (amount as u128));
}
}
이 목표를 달성하기 위해 Move 언어에서 제공하는 borrow_global 연산자를 사용할 수 있습니다. 이 연산자는 계정의 불변 전역 저장소에서 특정 데이터 타입을 읽는 데 사용됩니다. 이 연산자를 사용함으로써, 모듈은 관리자가 소유한 권한을 다른 사용자에게 빌려줄 수 있습니다. 즉, 원하는 권한을 얻으려면 관리자 주소가 필요합니다.
그러나 burn_coin 함수의 트랜잭션 개시자는 관리자가 아닌 사용자입니다. 따라서 관리자 주소를 signer를 통해 얻을 수 없습니다(mint_coin 함수처럼). 다행히도, BSC와 함께 aptos_std::type_info를 통해 이 구조체가 정의된 모듈의 주소를 얻을 수 있습니다. 모듈이 관리자 주소 아래에 배포되었으므로, 이를 통해 관리자 주소를 얻고 최종적으로 BurnCapability 권한을 얻을 수 있습니다.
팁#2:
_borrow_global_연산자를 사용하여 모듈의 권한을 일시적으로 얻을 수 있습니다.
BurnCapability를 얻은 후, 모듈은 사용자로부터 지정된 양의 코인을 출금하고 해당 권한으로 코인을 소각할 수 있습니다.
코인 계정 동결 및 해제
위의 논의를 바탕으로, 이제 코인 계정 관리를 쉽게 이해할 수 있습니다. 구체적으로, 사용자가 자신의 코인 계정을 동결할 수 있도록 freeze_self 함수를 제공합니다. 또한 관리자만 사용할 수 있는 긴급 동결을 위한 emergency_freeze 함수도 제공합니다. 또한 긴급 동결 메커니즘의 존재로 인해 사용자가 스스로 동결을 해제할 수 없어야 합니다. 따라서 unfreeze 함수도 관리자가 사용자 계정을 해제하도록 요구합니다.
public entry fun freeze_self(account: &signer) acquires CapStore, BSCEventStore{
let owner_address = type_info::account_address(&type_info::type_of<BSC>());
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
let freeze_address = signer::address_of(account);
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"freezed self"));
}
public entry fun emergency_freeze(cap_owner: &signer, freeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::freeze_coin_store<BSC>(freeze_address, freeze_cap);
emit_event(freeze_address, utf8(b"emergency freezed"));
}
public entry fun unfreeze(cap_owner: &signer, unfreeze_address: address) acquires CapStore, BSCEventStore{
let owner_address = signer::address_of(cap_owner);
let freeze_cap = &borrow_global<CapStore>(owner_address).freeze_cap;
coin::unfreeze_coin_store<BSC>(unfreeze_address, freeze_cap);
emit_event(unfreeze_address, utf8(b"unfreezed"));
}
팁#3: 함수를 통해 권한을 반환할 때는 주의하세요! 이러한 권한을 획득한 악의적인 사용자는 코인 보유자에게 피해를 주기 위해 이를 남용할 수 있습니다!
0x3. 코인과 상호작용하기
여기서는 코인과 상호작용하는 방법에 집중합니다.
0x3.1 등록
모듈에 register 함수가 있다는 것을 눈치채셨을 것입니다:
public entry fun register(account: &signer){
let address_ = signer::address_of(account);
if(!coin::is_account_registered<BSC>(address_)){
coin::register<BSC>(account);
};
if(!exists<BSCEventStore>(address_)){
move_to(account, BSCEventStore{event_handle: account::new_event_handle(account)});
};
}
이 함수는 사용자가 코인 사용 권한과 이벤트 레코더를 등록하는 데 도움을 줍니다. 특정 코인을 사용하려면 aptos_framework::coin 모듈은 사용자가 aptos_framework::coin::register 함수를 통해 코인 사용 권한을 먼저 명시적으로 등록해야 한다고 규정합니다.
public fun register<CoinType>(account: &signer) {
let account_addr = signer::address_of(account);
assert!(
!is_account_registered<CoinType>(account_addr),
error::already_exists(ECOIN_STORE_ALREADY_PUBLISHED),
);
account::register_coin<CoinType>(account_addr);
let coin_store = CoinStore<CoinType> {
coin: Coin { value: 0 },
frozen: false,
deposit_events: account::new_event_handle<DepositEvent>(account),
withdraw_events: account::new_event_handle<WithdrawEvent>(account),
};
move_to(account, coin_store);
}
사용자는 이 함수를 통해 이 코인 타입을 등록한 경우에만 해당 코인을 정상적으로 보유할 수 있습니다. 즉, 특정 코인을 보유하고 싶지 않다면, 다른 사람들이 당신의 동의 없이 당신의 계정에 코인을 넣을 수 없습니다. 등록은 실제로 (대상 코인 타입의) CoinStore 구조체를 당신의 계정에 넣는 것입니다. 이 CoinStore 구조체는 잔액을 기록하는 Coin 구조체를 포함합니다.
팁#4: 이더리움 토큰과 달리, Aptos 코인은 사용자의 명시적인 등록 없이는 보유하거나 운용할 수 없습니다.
0x3.2 전송
이제 BSC 코인을 보유하고 있다고 가정하면, aptos_framework::coin 모듈의 transfer 함수를 호출하여 이 코인들을 전송할 수 있습니다.
public entry fun transfer<CoinType>(
from: &signer,
to: address,
amount: u64,
) acquires CoinStore {
let coin = withdraw<CoinType>(from, amount);
deposit(to, coin);
}
이것은 coin 모듈에서 제공하는 진입 함수입니다. 로직은 두 가지 공개 함수, 즉 withdraw와 deposit의 호출로 구성됩니다. withdraw 함수는 &signer 권한이 필요하며, 이는 계정에서 일정 금액의 자산을 코인으로 출금하는 데 사용됩니다. deposit 함수는 코인이 등록된 모든 계정에 코인을 입금할 수 있습니다. 이 함수는 추가 권한이 필요 없으며 지정된 코인을 계정 주소에 입금합니다. 최종적으로 전송된 코인은 대상 주소의 CoinStore 구조체에 저장된 코인과 자동으로 병합됩니다.
팁#5: 출금 후, 코인의 자산은 현재
_transfer_함수의 제어 하에 있습니다. 이 함수는 추가 권한을 획득하지 않고도 이 자산을_deposit_함수에 전달할 수 있습니다.
0x3.3 분할 및 병합
이더리움 토큰과 달리, 코인의 유통은 사용자의 잔액을 수정하는 것으로 업데이트될 수 없습니다. 대신, coin 모듈에서 Coin 구조체를 출금함으로써 달성할 수 있습니다. 이를 통해 사용자는 이 구조체를 다른 모듈에 전달함으로써 자산 유통을 실현합니다. 구조체는 그것을 정의한 모듈에 의해서만 조작될 수 있으므로, coin 모듈은 다양한 시나리오의 필요를 충족하기 위해 코인을 더 작은 단위로 나누고 여러 코인을 병합하는 것을 포함하여 Coin 구조체를 조작하는 일부 인터페이스를 제공합니다.
1. extract 함수는 코인을 분할하는 데 사용됩니다. Coin 구조체를 받아 그 안의 자산 일부를 추출하여 새 Coin 구조체를 생성하고 새 구조체를 반환합니다.
public fun extract<CoinType>(coin: &mut Coin<CoinType>, amount: u64): Coin<CoinType> {
assert!(coin.value >= amount, error::invalid_argument(EINSUFFICIENT_BALANCE));
coin.value = coin.value - amount;
Coin { value: amount }
}
2. extract_all 함수는 원래 Coin 구조체의 전체 값을 추출하여 새 Coin 구조체에 입금하는 데 사용됩니다. 결과적으로 원래 Coin 구조체의 값은 0이 됩니다(일명 zero_coin). zero_coin 구조체는 destroy_zero 함수를 호출하여 소멸시킬 수 있습니다.
public fun extract_all<CoinType>(coin: &mut Coin<CoinType>): Coin<CoinType> {
let total_value = coin.value;
coin.value = 0;
Coin { value: total_value }
}
public fun destroy_zero<CoinType>(zero_coin: Coin<CoinType>) {
let Coin { value } = zero_coin;
assert!(value == 0, error::invalid_argument(EDESTRUCTION_OF_NONZERO_TOKEN))
}
3. merge 함수는 코인을 병합하는 데 사용됩니다. 두 Coin 구조체, 즉 source_coin과 dst_coin의 값을 dst_coin 구조체로 병합하고 source_coin 구조체를 소멸시킬 수 있습니다.
public fun merge<CoinType>(dst_coin: &mut Coin<CoinType>, source_coin: Coin<CoinType>) {
spec {
assume dst_coin.value + source_coin.value <= MAX_U64;
};
dst_coin.value = dst_coin.value + source_coin.value;
let Coin { value: _ } = source_coin;
}
4. zero 함수는 zero_coin 구조체를 생성하는 데 사용됩니다.
public fun zero<CoinType>(): Coin<CoinType> {
Coin<CoinType> {
value: 0
}
}
0x4. 코인 테스트
이 코인을 빠르게 테스트하려면, 먼저 다음 명령으로 배포할 수 있습니다 (Move.toml에서 배포자 계정 주소를 설정하는 것을 잊지 마세요!)
$ aptos move publish --package-dir ./
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_coin
package size 2751 bytes
Do you want to submit a transaction for a range of [868300 - 1302400] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
"Result": {
"transaction_hash": "xxx",
...
이렇게 하면 코인이 체인에 성공적으로 배포되지만 아직 유통이 없습니다. 발행된 코인을 받으려면 계정을 등록해야 합니다.
$ aptos move run --function-id default::bsc::register
Do you want to submit a transaction for a range of [153100 - 229600] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
"Result": {
"transaction_hash": "xxx",
...
두 번째 명령에서 **Your-address**는 자신의 주소로 교체해야 합니다(.aptos/config.yaml의 account 참조). 브라우저에 주소를 입력하고 Resources 탭을 클릭하면, 이 계정에 이제 100개의 BSC 코인이 있는 것을 확인할 수 있습니다.


다른 계정으로 코인을 전송하려면, 해당 계정이 코인을 사용하기 위해 등록하도록 잊지 마세요. transfer 함수는 제네릭 함수이므로, 다음과 같이 제네릭 매개변수를 BSC로, to_address를 지정해야 합니다:
$ aptos move run — function-id 0x1::coin::transfer — type-args BSC-module-address::bsc::BSC — args address:To_address u64:1
이 명령은 코인 모듈의 transfer 함수를 호출하여 1개의 BSC 코인을 **To_address**로 전송합니다. 여기서 0x1::coin::transfer는 transfer 함수의 함수 ID입니다. BlockSec::bsc::BSC가 코인의 식별자임을 기억하세요. 제네릭 매개변수는 반드시 이것으로 지정되어야 합니다. 또한 **BSC-module-address**는 모듈 배포자 계정 주소로 교체되어야 하며, 이는 Move.toml에서 **BlockSec**에 할당됩니다.
0x5. 다음 내용
자신만의 코인을 생성하고, 관리하고, 상호작용하는 방법을 이해한 후, 첫 번째 DeFi 핵심 프로젝트인 자동화 시장 조성자(AMM)를 구축하는 방법을 시연하겠습니다. Move 개발 및 보안 실천과 관련된 더 흥미로운 주제들이 다뤄질 예정입니다. 기대해 주세요!
참고 문헌
[1] https://aptos.dev/concepts/coin-and-token/aptos-coin/
[2] https://aptos.dev/concepts/coin-and-token/index
[3] https://aptos.dev/concepts/coin-and-token/aptos-token



