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 Coinand deploy it on the mainnet
Complete the learning of
Faucet Coinand deploy it on the mainnet
Submit the
package idfor theMy CoinandFaucet Coincontracts
Send
My Cointo the address0x7b8e0864967427679b4e129f79dc332a885c6087ec9e187b53451a9006ee15f2
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
| Parameter | Description | 
|---|---|
| decimals | Precision, the smallest unit that can be divided. 10^(-1*n) | 
| symbol | Symbol | 
| name | Name | 
| description | Description | 
| icon_url | Image | 
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 Objectsblock
- 
CoinMetadata: located in the Object Changes > Created ObjectsblockIts ObjectType is 0x2::coin::CoinMetadata<<Package ID>::<Module>::<Witness Type>>
- 
TreasuryCap: located in the Object Changes > Created ObjectsIts 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.
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.
