Alva

Alva

programmer
github
telegram
email

Let's Move - Learn Move Sui (2): Complete the On-chain Deployment of the Coin Contract

letsmove

Let's Move is a Move learning incentive program for SUI, encouraging more people to learn the Move language.

Learning Log (alva-lin)

Task 2 - Complete the on-chain deployment of two Coin contracts (mainnet)#

Tasks

  • Complete the study of Coin-related knowledge

  • Complete the learning of My Coin and deploy it on the mainnet

  • Complete the learning of Faucet Coin and deploy it on the mainnet

  • Submit the package id for the My Coin and Faucet Coin contracts

  • Send My Coin to the address 0x7b8e0864967427679b4e129f79dc332a885c6087ec9e187b53451a9006ee15f2

Mainly refer to the article Managed Coin Case - Introduction to Sui Move

sui::coin Library#

Coin#

The Coin contract is primarily written using the structures and methods provided by the sui::coin library.

struct Coin<phantom T> has key, store {
    id: UID,
    balance: Balance<T>
}

struct Balance<phantom T> has store {
    value: u64
}

Coin has key and store capabilities, is considered an asset, and can be transferred between different addresses. Later, all Coins owned can be viewed in the block explorer.

create_currency#

public fun create_currency<T: drop>(
    witness: T,
    decimals: u8,
    symbol: vector<u8>,
    name: vector<u8>,
    description: vector<u8>,
    icon_url: Option<Url>,
    ctx: &mut TxContext
): (TreasuryCap<T>, CoinMetadata<T>) {
    // Make sure there's only one instance of the type T
    assert!(sui::types::is_one_time_witness(&witness), EBadWitness);

    // Emit Currency metadata as an event.
    event::emit(CurrencyCreated<T> {
        decimals
    });

    (
        TreasuryCap {
            id: object::new(ctx),
            total_supply: balance::create_supply(witness)
        },
        CoinMetadata {
            id: object::new(ctx),
            decimals,
            name: string::utf8(name),
            symbol: ascii::string(symbol),
            description: string::utf8(description),
            icon_url
        }
    )
}

In the parameters of the create_currency method

ParameterDescription
decimalsPrecision, the smallest unit that can be divided. 10^(-1*n)
symbolSymbol
nameName
descriptionDescription
icon_urlImage

The return value of the method is a tuple containing two values, TreasuryCap and CoinMetadata,

where TreasuryCap is an asset that is guaranteed to be a singleton object through the one-time witness pattern, and its type declaration is as follows

/// Capability allowing the bearer to mint and burn
/// coins of type `T`. Transferable
struct TreasuryCap<phantom T> has key, store {
        id: UID,
        total_supply: Supply<T>
}
/// A Supply of T. Used for minting and burning.
/// Wrapped into a `TreasuryCap` in the `Coin` module.
struct Supply<phantom T> has store {
    value: u64
}

This total_supply value tracks the total issuance of the current currency T, so only one TreasuryCap is needed. The CoinMetadata simply stores the metadata of the current currency.

Coin Contract Code#

Thanks to the completeness of the sui framework library, the code required to create a token contract with mint (minting) and burn (destroying) functionalities is minimal.

// file: my_coin.move
module new_coin::my_coin {
    use std::option;
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    struct MY_COIN has drop {}

    fun init(witness: MY_COIN, ctx: &mut TxContext) {
        let (treasury_cap, metadata) = coin::create_currency<MY_COIN>(
            witness,
            2,
            b"MY_COIN",
            b"MC",
            b"learning for letsmove, powered by alva-lin",
            option::none(),
            ctx
        );

        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
    }

    public fun mint(
        treasury_cap: &mut TreasuryCap<MY_COIN>,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury_cap, amount, recipient, ctx);
    }

    public fun burn(treasury_cap: &mut TreasuryCap<MY_COIN>, coin: Coin<MY_COIN>) {
        coin::burn(treasury_cap, coin);
    }

    #[test_only]
    public fun test_init(ctx: &mut TxContext) {
        init(MY_COIN {}, ctx);
    }
}

The my_coin module first defines a structure named MY_COIN based on the Witness pattern. Then, in the init method parameters, a MY_COIN type witness resource is added, which is automatically created after the module is pushed.

In the init method, the coin::create_currency method is called to obtain the TreasuryCap and CoinMetadata resources, and the CoinMetadata is immediately frozen.

The TreasuryCap, as a credential to control the calling of the mint and burn methods, is sent to the publisher's address.

Testing#

The test code is as follows, simulating a multi-transaction scenario to perform mint and burn operations.

#[test_only]
module new_coin::my_coin_tests {
    use new_coin::my_coin::{Self, MY_COIN};
    use sui::coin::{Coin, TreasuryCap};
    use sui::test_scenario::{Self, next_tx, ctx};

    #[test]
    fun mint_burn() {
        // Initialize a mock sender address
        let addr1 = @0xA;

        // Begins a multi transaction scenario with addr1 as the sender
        let scenario = test_scenario::begin(addr1);

        // Run the coin module init function
        {
            my_coin::test_init(ctx(&mut scenario))
        };

        // Mint a `Coin<MY_COIN>` object
        next_tx(&mut scenario, addr1);
        {
            let treasurycap = test_scenario::take_from_sender<TreasuryCap<MY_COIN>>(&scenario);
            my_coin::mint(&mut treasurycap, 100, addr1, test_scenario::ctx(&mut scenario));
            test_scenario::return_to_address<TreasuryCap<MY_COIN>>(addr1, treasurycap);
        };

        // Burn a `Coin<MY_COIN>` object
        next_tx(&mut scenario, addr1);
        {
            let coin = test_scenario::take_from_sender<Coin<MY_COIN>>(&scenario);
            let treasurycap = test_scenario::take_from_sender<TreasuryCap<MY_COIN>>(&scenario);
            my_coin::burn(&mut treasurycap, coin);
            test_scenario::return_to_address<TreasuryCap<MY_COIN>>(addr1, treasurycap);
        };

        // Cleans up the scenario object
        test_scenario::end(scenario);
    }
}

Deployment#

First, switch the network to the desired environment.

sui client switch --env mainnet

In the project folder, execute the following commands to publish the package or publish the module individually.

# publish package
sui client publish --gas-budget 30000

# or publish module as needed
# publish module
# sui client publish sources/my_coin.move --gas-budget 30000

The output after publishing includes

  • Package: located in the Object Changes > Published Objects block

  • CoinMetadata: located in the Object Changes > Created Objects block

    Its ObjectType is 0x2::coin::CoinMetadata<<Package ID>::<Module>::<Witness Type>>

  • TreasuryCap: located in the Object Changes > Created Objects

    Its ObjectType is 0x2::coin::TreasuryCap<<Package ID>::<Module>::<Witness Type>>

Record the Package ID and TreasuryCap ID:

export PACKAGE_ID=0xf9623587d620d26024868578a3539642993e2da44598c3fcaa1f384f5327f6a5
export TREASURYCAP_ID=0x97233be4acd1688c93f2c60ce81350b993a810c6b4851e8cdf214402759fad88

Minting and Burning#

Use the sui cli to call the corresponding module functions.

Note that the desired token amount = input value * 10^(-1*n), where n is the value of decimals in the previously published contract code.

In this article, the value of decimals is 2. If you want to mint 100 tokens, you need to input 10000.

# Recipient wallet address
export RECIPIENT_ADDRESS=<YOUR_WALLET_ADDRESS>
# mint
sui client call --gas-budget 3000 --package $PACKAGE_ID --module my_coin --function mint --args $TREASURYCAP_ID \"10000\" $RECIPIENT_ADDRESS

In the output, you can see the transaction details.

Alternatively, you can obtain the transaction hash and check the transaction details on SuiScan or other sui block explorers.

# The transaction hash looks similar to the following form, usually at the beginning of the output
Transaction Digest: <hash>

In the output during minting, find the Object Changes > Created Objects block where the ObjectType is 0x2::coin::Coin<<package>, <module>, <type>>, and obtain the ObjectID.

This ObjectID is the CoinID, which should be stored as a variable.

If you cannot find the coin id in the command line output, you can also find it from the block explorer.

how to find coin id

export COIN_ID=0x3460204ae7f9385df79dc963a17d8b11eb0fa7a699f7196fac80405e1777d36

Call the burn method to destroy the currency.

# Note that the gas budget needs to be increased
# burn
sui client call --gas-budget 7500000 --package $PACKAGE_ID --module joker --function burn --args $TREASURYCAP_ID $COIN_ID

After the command executes successfully, you can see that the previously minted amount has been deleted from the wallet.

After the token is destroyed, it cannot be directly searched using the COIN ID in the block explorer; it can only be found indirectly from historical transactions.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.