Fungible Asset

Deploy your Fungible Asset!

์•ž์„  ํŠœํ† ๋ฆฌ์–ผ์„ ํ†ตํ•ด Module์„ ์ž‘์„ฑํ•˜๊ณ  ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ํŠธ๋žœ์žญ์…˜์„ ํ†ตํ•ด Module์˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Module๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ด์šฉํ•˜์—ฌ Fungible Asset Module์„ ๋ฐฐํฌํ•˜๊ณ  ๋‚˜๋งŒ์˜ Fungible Asset์„ ๋งŒ๋“ค์–ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ“˜

์ด ํŠœํ† ๋ฆฌ์–ผ์„ ํ†ตํ•ด ์•„๋ž˜ ๋‚ด์šฉ์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!

  • Fungible Asset์„ ์ƒ์„ฑํ•˜๋Š” ๋กœ์ง๊ณผ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Fungible Asset Module๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜์—ฌ ์ „์†กํ•˜๊ณ  ์†Œ๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ „์ฒด ํŠœํ† ๋ฆฌ์–ผ ์ฝ”๋“œ๋Š” ์•„๋ž˜ ๋งํฌ๋ฅผ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Step 1. Initializing Move environment and Profile

ํŠœํ† ๋ฆฌ์–ผ ์ง„ํ–‰์„ ์œ„ํ•ด ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ„ฐ๋ฏธ๋„์˜ ๊ฒฝ๋กœ๋ฅผ ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

$ mkdir FungibleAsset
$ cd FungibleAsset

Move environment๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

$ aptos move init --name <your_porject_name>

์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ดˆ๊ธฐ ๊ณ„์ • ์ •๋ณด๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

$ aptos init --network testnet

config.yaml ํŒŒ์ผ์—์„œ account ๊ฐ’์„ ๋ณต์‚ฌํ•œ ๋’ค Move.toml ํŒŒ์ผ์˜ [addresses]์— ๋ถ™์—ฌ๋„ฃ์–ด ๋ฐฐํฌํ•  Account์˜ address๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์„ค์ • ์™„๋ฃŒ ํ›„ sources ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— move ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

$ cd sources
$ touch fungible_asset.move

Step 2. Writing Fungible Asset Module

์ „์ฒด์ ์ธ Fungible Asset์˜ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์ฝ”๋“œ๋Š” Aptos ์žฌ๋‹จ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ์ ์ธ Fungible Asset ๋ชจ๋“ˆ์„ ์ผ๋ถ€ ์ˆ˜์ •ํ•œ ๊ฒƒ์œผ๋กœ ์ „์ฒด ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// fungible_asset.move

module myAddress::fa_coin {
    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset};
    use aptos_framework::object::{Self, Object};
    use aptos_framework::primary_fungible_store;
    use aptos_framework::function_info;
    use aptos_framework::dispatchable_fungible_asset;
    use std::error;
    use std::signer;
    use std::string::{Self, utf8};
    use std::option;

    /// Only fungible asset metadata owner can make changes.
    const ENOT_OWNER: u64 = 1;
    const ASSET_SYMBOL: vector<u8> = b"MFA";

    #[resource_group_member(group = aptos_framework::object::ObjectGroup)]
    /// Hold refs to control the minting, transfer and burning of fungible assets.
    struct ManagedFungibleAsset has key {
        mint_ref: MintRef,
        transfer_ref: TransferRef,
        burn_ref: BurnRef,
    }
 

    /// Initialize metadata object and store the refs.
    // :!:>initialize
    fun init_module(admin: &signer) {
        let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
        primary_fungible_store::create_primary_store_enabled_fungible_asset(
            constructor_ref,
            option::none(),
            utf8(b"MyFAToken"), /* name */
            utf8(ASSET_SYMBOL), /* symbol */
            8, /* decimals */
            utf8(b"http://example.com/favicon.ico"), /* icon */
            utf8(b"http://example.com"), /* project */
        );

        // Create mint/burn/transfer refs to allow creator to manage the fungible asset.
        let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
        let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
        let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
        let metadata_object_signer = object::generate_signer(constructor_ref);
        move_to(
            &metadata_object_signer,
            ManagedFungibleAsset { mint_ref, transfer_ref, burn_ref }
        ); // <:!:initialize

        // Override the deposit and withdraw functions which mean overriding transfer.
        // This ensures all transfer will call withdraw and deposit functions in this module
        // and perform the necessary checks.
        // This is OPTIONAL. It is an advanced feature and we don't NEED a global state to pause the FA coin.
        let deposit = function_info::new_function_info(
            admin,
            string::utf8(b"fa_coin"),
            string::utf8(b"deposit"),
        );
        let withdraw = function_info::new_function_info(
            admin,
            string::utf8(b"fa_coin"),
            string::utf8(b"withdraw"),
        );
        dispatchable_fungible_asset::register_dispatch_functions(
            constructor_ref,
            option::some(withdraw),
            option::some(deposit),
            option::none(),
        );
    }

    #[view]
    /// Return the address of the managed fungible asset that's created when this module is deployed.
    public fun get_metadata(): Object<Metadata> {
        let asset_address = object::create_object_address(&@myAddress, ASSET_SYMBOL);
        object::address_to_object<Metadata>(asset_address)
    }

    /// Deposit function override to ensure that the account is not denylisted and the FA coin is not paused.
    /// OPTIONAL
    public fun deposit<T: key>(
        store: Object<T>,
        fa: FungibleAsset,
        transfer_ref: &TransferRef,
    ) {
        fungible_asset::deposit_with_ref(transfer_ref, store, fa);
    }

    /// Withdraw function override to ensure that the account is not denylisted and the FA coin is not paused.
    /// OPTIONAL
    public fun withdraw<T: key>(
        store: Object<T>,
        amount: u64,
        transfer_ref: &TransferRef,
    ): FungibleAsset  {
        fungible_asset::withdraw_with_ref(transfer_ref, store, amount)
    }

    // :!:>mint
    /// Mint as the owner of metadata object.
    public entry fun mint(admin: &signer, to: address, amount: u64) acquires ManagedFungibleAsset {
        let asset = get_metadata();
        let managed_fungible_asset = authorized_borrow_refs(admin, asset);
        let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset);
        let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount);
        fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa);
    }// <:!:mint

    /// Transfer as the owner of metadata object ignoring `frozen` field.
    public entry fun transfer(admin: &signer, from: address, to: address, amount: u64) acquires ManagedFungibleAsset {
        let asset = get_metadata();
        let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref;
        let from_wallet = primary_fungible_store::primary_store(from, asset);
        let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset);
        let fa = withdraw(from_wallet, amount, transfer_ref);
        deposit(to_wallet, fa, transfer_ref);
    }
    
    /// Burn fungible assets as the owner of metadata object.
    public entry fun burn(admin: &signer, from: address, amount: u64) acquires ManagedFungibleAsset {
        let asset = get_metadata();
        
        let burn_ref = &authorized_borrow_refs(admin, asset).burn_ref;
        let from_wallet = primary_fungible_store::primary_store(from, asset);
        fungible_asset::burn_from(burn_ref, from_wallet, amount);
    }

    /// Borrow the immutable reference of the refs of `metadata`.
    /// This validates that the signer is the metadata object's owner.
    inline fun authorized_borrow_refs(
        owner: &signer,
        asset: Object<Metadata>,
    ): &ManagedFungibleAsset acquires ManagedFungibleAsset {
        assert!(object::is_owner(asset, signer::address_of(owner)), error::permission_denied(ENOT_OWNER));
        borrow_global<ManagedFungibleAsset>(object::object_address(&asset))
    }
}

์ตœ์ดˆ Module ๋ฐฐํฌ ์‹œ init_module ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Object๋ฅผ ์ƒ์„ฑํ•˜๊ณ  Metadata๋ฅผ Object์—๊ฒŒ Resource๋กœ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ํ•ด๋‹น Metadata Resource๋ฅผ ์ด์šฉํ•˜์—ฌ Fungible Asset์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ(Ref)์„ ์ƒ์„ฑํ•œ ๋’ค Object์—๊ฒŒ Resource๋กœ ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ Module์„ ์ž‘์„ฑํ•˜๋ฉด ํ•ด๋‹น Object์˜ Owner๋Š” Mint, Transfer, Burn ๋“ฑ์˜ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ํš๋“ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ดํ›„ Module์— ์ •์˜ํ•œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Mint, Transfer, Burn ๋“ฑ ์›ํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ’ก **Ref๋Š” ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜ ์ž…๋‹ˆ๋‹ค!**

๋ณธ ํŠœํ† ๋ฆฌ์–ผ์€ Ref ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด Object์˜ Owner๋งŒ Ref๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‹ค์ œ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ Module ์ž‘์„ฑ ์‹œ ๋ณธ์ธ์˜ ๋กœ์ง์— ๋งž๊ฒŒ ์„ค์ •ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


Step 3. Understanding Module

์ด๋ฒˆ Step์—์„œ๋Š” Module์ด ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.


  • struct ManagedFungibleAsset : ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•˜๋Š” Ref๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ์กฐ์ฒด ์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ์—์„œ๋Š” mint, transfer, burn 3๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๊ถŒํ•œ์„ ํ™•์ธํ•˜๋Š” Ref๋ฅผ ๊ด€๋ฆฌํ•˜๋„๋ก ์ž‘์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct ManagedFungibleAsset has key {
    mint_ref: MintRef,
    transfer_ref: TransferRef,
    burn_ref: BurnRef,
}

  • ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ดˆ๊ธฐํ™”
    Module ๋ฐฐํฌ ์‹œ ์‹คํ–‰๋˜๋Š” constructor ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค. Object๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ตฌ์กฐ์ฒด๋ฅผ ํ•ด๋‹น Object์— ํ• ๋‹นํ•œ ํ›„ constructor_ref๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  constructor_ref์™€ ์ž…๋ ฅํ•œ ์ธ์ž ๊ฐ’์„ ์ด์šฉํ•ด ์‚ญ์ œ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  Object์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.
fun init_module(admin: &signer) {
    let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
    primary_fungible_store::create_primary_store_enabled_fungible_asset(
        constructor_ref,
        option::none(),
        utf8(b"MyFAToken"), /* name */
        utf8(ASSET_SYMBOL), /* symbol */
        8, /* decimals */
        utf8(b"http://example.com/favicon.ico"), /* icon */
        utf8(b"http://nodit.lambda256.io"), /* project */
    );


  • ๊ด€๋ฆฌ ์ฐธ์กฐ(Ref) ์ƒ์„ฑ
    ์ƒ์„ฑํ•œ constructor_ref๋ฅผ ์ด์šฉํ•ด mint_ref, burn_ref, transfer_ref์„ ์ƒ์„ฑํ•˜๊ณ  ๊ตฌ์กฐ์ฒด์— ์ž…๋ ฅ ํ›„ Object์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.
    ์ด๋ฅผ ํ†ตํ•ด Mint, Burn, Transfer ๊ธฐ๋Šฅ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    // Create mint/burn/transfer refs to allow creator to manage the fungible asset.
    let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
    let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
    let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
    let metadata_object_signer = object::generate_signer(constructor_ref);
    move_to(
        &metadata_object_signer,
        ManagedFungibleAsset { mint_ref, transfer_ref, burn_ref }
    ); // <:!:initialize


  • ํ•จ์ˆ˜ ๊ตฌ์กฐ์ฒด ์ƒ์„ฑ ๋ฐ ํ• ๋‹น
    function_info::new_function_info ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด fa_coin ๋ชจ๋“ˆ์˜ deposit ํ•จ์ˆ˜์™€ withdraw ํ•จ์ˆ˜์— ๋Œ€ํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ Object์— ํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์œ ์ €๋Š” deposit, withdraw ํ•จ์ˆ˜์— ์ปค์Šคํ…€ ๋กœ์ง์„ ์ž‘์„ฑํ•˜์—ฌ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    let deposit = function_info::new_function_info(
        admin,
        string::utf8(b"fa_coin"),
        string::utf8(b"deposit"),
    );
    let withdraw = function_info::new_function_info(
        admin,
        string::utf8(b"fa_coin"),
        string::utf8(b"withdraw"),
    );
    dispatchable_fungible_asset::register_dispatch_functions(
        constructor_ref,
        option::some(withdraw),
        option::some(deposit),
        option::none(),
    );
}

  • Object ๊ฐ€์ ธ์˜ค๊ธฐ
    Account Address์™€ Seed ๊ฐ’(๋ณธ ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” Symbol ๊ฐ’)์„ ์ด์šฉํ•ด Object์˜ ์ฃผ์†Œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•ด๋‹น ์ฃผ์†Œ์™€ ์ผ์น˜ํ•˜๋Š” Object๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
public fun get_metadata(): Object<Metadata> {
    let asset_address = object::create_object_address(&@myAddress, ASSET_SYMBOL);
    object::address_to_object<Metadata>(asset_address)
}

  • deposit ํ•จ์ˆ˜ ์ •์˜
    Object์™€ FungibleAsset ๊ตฌ์กฐ์ฒด, transfer_ref๋ฅผ ์ด์šฉํ•ด deposit_with_ref ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ Object์— ํ• ๋‹น๋œ balance์— amount ๋งŒํผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
public fun deposit<T: key>(
    store: Object<T>,
    fa: FungibleAsset,
    transfer_ref: &TransferRef,
) {
    fungible_asset::deposit_with_ref(transfer_ref, store, fa);
}


  • withdraw ํ•จ์ˆ˜ ์ •์˜
    Object์™€ FungibleAsset ๊ตฌ์กฐ์ฒด, transfer_ref๋ฅผ ์ด์šฉํ•ด withdraw_with_ref ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ Object์— ํ• ๋‹น๋œ balance์— amount ๋งŒํผ ๊ฐ์†Œ์‹œํ‚ต๋‹ˆ๋‹ค.
public fun withdraw<T: key>(
    store: Object<T>,
    amount: u64,
    transfer_ref: &TransferRef,
): FungibleAsset  {
    fungible_asset::withdraw_with_ref(transfer_ref, store, amount)
}

  • mint ํ•จ์ˆ˜ ์ •์˜
    Object๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ํ›„ signer์™€ Object๋ฅผ ์ด์šฉํ•˜์—ฌ Mint ๊ถŒํ•œ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ž์‚ฐ์„ ์ „์†ก๋ฐ›๋Š” ์ฃผ์†Œ์— ์ž”์•ก์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์ฒด ์กด์žฌ ์œ ๋ฌด ํ™•์ธ ํ›„ ์—†์œผ๋ฉด ์ด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ fungible_asset::mint ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๊ตฌ์กฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  to ์ฃผ์†Œ์˜ ๊ตฌ์กฐ์ฒด์˜ ์ž”๊ณ ๋ฅผ ๊ตฌ์กฐ์ฒด์˜ amount ๋งŒํผ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
public entry fun mint(admin: &signer, to: address, amount: u64) acquires ManagedFungibleAsset {
    let asset = get_metadata();
    let managed_fungible_asset = authorized_borrow_refs(admin, asset);
    let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset);
    let fa = fungible_asset::mint(&managed_fungible_asset.mint_ref, amount);
    fungible_asset::deposit_with_ref(&managed_fungible_asset.transfer_ref, to_wallet, fa);
}

  • transfer ํ•จ์ˆ˜ ์ •์˜
    Object๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ํ›„ signer์™€ Object๋ฅผ ์ด์šฉํ•˜์—ฌ Transfer ๊ถŒํ•œ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ž์‚ฐ์„ ์ „์†กํ•˜๋Š” ์ฃผ์†Œ์˜ FungibleStore๋ฅผ ํ™•์ธํ•œ ๋’ค ์ž…๋ ฅํ•œ amount ๋งŒํผ ์ž์‚ฐ์„ ๊ฐ์†Œ์‹œํ‚ค๊ณ  ์ž์‚ฐ์„ ์ „์†ก๋ฐ›๋Š” ์ฃผ์†Œ์˜ FungibleStore์˜ ์ž์‚ฐ์„ ํ™•์ธํ•œ ํ›„ amount ๋งŒํผ ์ž์‚ฐ์„ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
public entry fun transfer(admin: &signer, from: address, to: address, amount: u64) acquires ManagedFungibleAsset {
    let asset = get_metadata();
    let transfer_ref = &authorized_borrow_refs(admin, asset).transfer_ref;
    let from_wallet = primary_fungible_store::primary_store(from, asset);
    let to_wallet = primary_fungible_store::ensure_primary_store_exists(to, asset);
    let fa = withdraw(from_wallet, amount, transfer_ref);
    deposit(to_wallet, fa, transfer_ref);
}

  • burn ํ•จ์ˆ˜ ์ •์˜
    Object๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ํ›„ signer์™€ Object๋ฅผ ์ด์šฉํ•˜์—ฌ Burn ๊ถŒํ•œ์„ ๊ฐ€์ ธ์˜จ ํ›„ from ์ฃผ์†Œ๊ฐ€ ๋ณด์œ ํ•œ ์ˆ˜๋Ÿ‰์—์„œ amount ๋งŒํผ ์ฐจ๊ฐํ•ฉ๋‹ˆ๋‹ค.
public entry fun burn(admin: &signer, from: address, amount: u64) acquires ManagedFungibleAsset {
    let asset = get_metadata();
    let burn_ref = &authorized_borrow_refs(admin, asset).burn_ref;
    let from_wallet = primary_fungible_store::primary_store(from, asset);
    fungible_asset::burn_from(burn_ref, from_wallet, amount);
}

  • ๊ฒ€์ฆ๋œ Ref ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
    Owner์˜ signer ๊ฐ’๊ณผ Object๋ฅผ ์ด์šฉํ•˜์—ฌ ์†Œ์œ ์ž๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๊ตฌ์กฐ์ฒด๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ Mint, Transfer, Burn ๋“ฑ์˜ Ref๊ฐ€ ํ•„์š”ํ•œ ๋กœ์ง์— ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
inline fun authorized_borrow_refs(
    owner: &signer,
    asset: Object<Metadata>,
): &ManagedFungibleAsset acquires ManagedFungibleAsset {
    assert!(object::is_owner(asset, signer::address_of(owner)), error::permission_denied(ENOT_OWNER));
    borrow_global<ManagedFungibleAsset>(object::object_address(&asset))
	}
}

Step 4. Deploy your Module

์ตœ์ข…์ ์œผ๋กœ ์™„์„ฑํ•œ Module์€ Aptos CLI๋ฅผ ์ด์šฉํ•ด Aptos ๋„คํŠธ์›Œํฌ์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐฐํฌ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. Module Compile

move ํŒŒ์ผ์ด ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฒฝ๋กœ์—์„œ Aptos CLI๋ฅผ ์ด์šฉํ•ด Module์„ ์ปดํŒŒ์ผ ํ•ฉ๋‹ˆ๋‹ค.

$ aptos move compile

  1. Module Deploy

Compile์ด ์™„๋ฃŒ๋œ move ํŒŒ์ผ์„ Aptos CLI๋ฅผ ์ด์šฉํ•ด Aptos ๋„คํŠธ์›Œํฌ์— ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค. move ํŒŒ์ผ์ด ์ž‘์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฒฝ๋กœ์—์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

$ aptos move publish

Nodit์—์„œ ์ œ๊ณตํ•˜๋Š” Aptos Node API ์ค‘ Get account module API๋ฅผ ์ด์šฉํ•˜๋ฉด ํŠน์ • Account๊ฐ€ ๋ฐฐํฌํ•œ Module์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“˜

Nodit Aptos Node API๋Š” ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

์•„๋ž˜ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์—ฌ Get account module API๋ฅผ ๋”์šฑ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”!


Get account module API๋Š” Query Params๋กœ Account Address์™€ moduleName์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. header๋กœ X-API-KEY๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  Nodit์„ ํ†ตํ•ด ๋ฐœ๊ธ‰๋ฐ›์€ X-API-KEY๋ฅผ ์ž…๋ ฅํ•œ ํ›„ API๋ฅผ ํ˜ธ์ถœํ•ด ๋ณด์„ธ์š”!

curl --request GET \
     --url https://aptos-testnet.nodit.io/v1/accounts/<account_address>/module/<module_name> \
     --header 'X-API-KEY: <Your X-API-KEY>' \
     --header 'accept: application/json'

์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐฐํฌํ•œ Module์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

{
  "bytecode": "0xa11ceb0b060000...",
  "abi": {
    "address": "0xabc...90",
    "name": "<your_module_name>",
		...
  }
}

Step 5. Interact to Module

Module ๋ฐฐํฌ์— ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ํŠธ๋žœ์žญ์…˜์„ ํ†ตํ•ด Module์— ์ž‘์„ฑ๋œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ Fungible Asset์„ ๋ฐœํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜์˜ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ํŠธ๋žœ์žญ์…˜ ๋นŒ๋“œ
  • ํŠธ๋žœ์žญ์…˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  • ํŠธ๋žœ์žญ์…˜ ์„œ๋ช…
  • ํŠธ๋žœ์žญ์…˜ ์ œ์ถœ
  • ํŠธ๋žœ์žญ์…˜ ๊ฒฐ๊ณผ ํ™•์ธ

Aptos Typescript SDK๋ฅผ ์ด์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์„ ์‹คํ–‰ํ•ด ๋ณด์„ธ์š”!

import {
  Account,
  Aptos,
  AptosConfig,
  Ed25519PrivateKey,
} from "@aptos-labs/ts-sdk";

const config = new AptosConfig({
  fullnode: "your_Nodit_Aptos_testnet_node_endpoint",
  indexer: "your_Nodit_Aptos_indexer_endpoint",
});

const aptos = new Aptos(config);

const privateKey = "your_private_key"; // 0x12345...
const ed25519Scheme = new Ed25519PrivateKey(privateKey);
const senderAccount = Account.fromPrivateKey({
  privateKey: ed25519Scheme,
});
const amount: number = 100_000_000_000; // change amount to mint

(async (senderAccount: Account, amount: number) => {
  try {
    const senderAddress = senderAccount.accountAddress.toString();
    const transaction = await aptos.transaction.build.simple({
      sender: senderAddress,
      data: {
        function: "module_owner_address::fungible_asset::mint", //0x1::aptos_account::transfer
        functionArguments: [
          senderAddress,
          amount, // mint function requires to_address and amount as arguments
        ],
      },
    });

    const senderAuthenticator = aptos.transaction.sign({
      signer: senderAccount,
      transaction,
    });

    const submitTx = await aptos.transaction.submit.simple({
      transaction,
      senderAuthenticator,
    });

    const executedTransaction = await aptos.waitForTransaction({
      transactionHash: submitTx.hash,
    });

    console.log(executedTransaction);
  } catch (error) {
    console.error(error);
  }
})(senderAccount, amount);


์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœํ–‰์ด ๋˜์—ˆ๋‹ค๋ฉด ๋‹ค๋ฅธ ์ฃผ์†Œ๋กœ ์ „์†กํ•˜๋Š” transfer ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ๋ฐฉ๊ธˆ ๋ฐœํ–‰ํ•œ Fungible Asset์„ ์ „์†กํ•ด ๋ณด์„ธ์š”!


์œ„์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Module๊ณผ Fungible Asset์„ ๋ฐฐํฌํ•˜๊ณ  ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‚˜์š”?

  • ํ™•์ธ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์œ„์˜ ์ฝ”๋“œ์™€ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์„ธ์š”.
  • ์œ„์˜ ์ฝ”๋“œ์™€ ์ฐจ์ด๊ฐ€ ์—†๋Š”๋ฐ ๋˜์ง€ ์•Š๋‚˜์š”? ์—ฌ๊ธฐ[QnA ๋งํฌ]๋ฅผ ํด๋ฆญํ•˜์—ฌ QnA๋กœ ๋‚จ๊ฒจ์ฃผ์„ธ์š”!

๐Ÿ“˜

Aptos๋Š” ๋งค์šฐ ๋น ๋ฅด๊ฒŒ ์—…๋ฐ์ดํŠธ ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!

Aptos ์žฌ๋‹จ์—์„œ ๋ฐฐํฌํ•œ SDK ๋ฒ„์ „์— ๋”ฐ๋ผ ๋ณ€๊ฒฝ๋˜๋Š” ์ ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Nodit์€ ํ•ญ์ƒ ์ด๋ฅผ ํ™•์ธํ•˜๊ณ  ์žˆ์œผ๋‚˜ ์‹œ์ ์— ๋”ฐ๋ผ ์ฝ”๋“œ ๋ณ€๊ฒฝ์œผ๋กœ ์ธํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.