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 mainnetComplete the learning of
Faucet Coin
and deploy it on the mainnetSubmit the
package id
for theMy Coin
andFaucet Coin
contractsSend
My Coin
to 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 Objects
block -
CoinMetadata: located in the
Object Changes > Created Objects
blockIts 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.
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.