Your First Coin
This tutorial details how to deploy and manage a new Coin. The steps are:
- Deploy moon_coin module. Let's call it moon_coin.
- Initialize MoonCoin via the standard Coin framework module.
- Register a recipient account to receive MoonCoin.
- Mint MoonCoin to the recipient as the owner of the MoonCoin.
This tutorial builds on Your first transaction as a library for this example. The following tutorial contains example code that can be downloaded in its entirety below:
- Typescript
- Python
- Rust
For this tutorial, will be focusing on first_coin.ts
and re-using the first_transaction.ts
library from the previous tutorial.
You can find the typescript project here.
For this tutorial, will be focusing on first_coin.py
and re-using the first_transaction.py
library from the previous tutorial.
You can find the python project here.
For this tutorial, will be focusing on first_coin/src
and re-using the first_transaction/src
library from the previous tutorial.
You can find the rust project here.
Step 1) Deploy moon_coin module
Step 1.1) Download Aptos-core
For the simplicity of this exercise, Aptos-core has a move-examples
directory that makes it easy to build and test Move modules without downloading additional resources. Over time, we will expand this section to describe how to leverage Move tools for development.
For now, download and prepare Aptos-core:
git clone https://github.com/aptos-labs/aptos-core.git
cd aptos-core
./scripts/dev_setup.sh
source ~/.cargo/env
git checkout origin/devnet
Install Aptos Command line tool. Learn more about the Aptos command line tool.
cargo install --git https://github.com/aptos-labs/aptos-core.git aptos
Step 1.2) Review the Module
In this terminal, change directories to aptos-move/move-examples/moon_coin
. Keep this terminal window for the rest of this tutorial- we will refer to it later as the "Move Window". The rest of this section will review the file sources/MoonCoinType.move
.
This module enables users to create a new MoonCoinType::moon_coin::MoonCoin that can be used to register with the framework Coin module (0x1::coin) to create a standard Coin. Developers can write their own functionalities in the moon_coin module if they want to do more than what's provided by the standard 0x1::coin or 0x1::managed_coin (adds mint/burn functionalities).
module MoonCoinType::moon_coin {
struct MoonCoin {}
}
The code is very simple as we are not adding more functionalities to MoonCoin beyond the standard ones provided by the framework Coin (transfer, deposit, withdraw, mint, burn). The most important part is struct MoonCoin, which defines a new type of coin that can be registered with 0x1::coin.
Step 1.3) Deploying the Move module containing MoonCoin type
- Typescript
- Python
- Rust
- Download the example project.
- Open your favorite terminal and navigate to where you downloaded the above example project
- Install the required libraries:
yarn install
- Execute the example:
yarn first_coin moon_coin.mv
- Download the example project.
- Open your favorite terminal and navigate to where you downloaded the above example project
- Install the required libraries:
pip3 install -r requirements.txt
. - Execute the example:
python3 first_coin.py moon_coin.mv
- Download the example project.
- Open your favorite terminal and navigate to where you downloaded the above example project
- Execute the example:
cargo run --bin first_coin -- moon_coin.mv
Step 1.4) Verify output
- After a few moments it will mention that "Update the module with Alice's address, build, copy to the provided path, and press enter."
- In the "Move Window" terminal, and for the Move file we had previously looked at:
- Copy Alice's address
- Compile the modules with Alice's address by
aptos move compile --package-dir . --named-addresses MoonCoinType=0x{alice_address_here}
. Here, we replace the generic named addressMoonCoinType='_'
inmoon_coin/move.toml
with Alice's Address - Copy
build/Examples/bytecode_modules/MoonCoin.mv
to the same folder as this tutorial project code
- Return to your other terminal window, and press "enter" at the prompt to continue executing the rest of the code
The output should look like the following:
=== Addresses ===
Alice: 11c32982d04fbcc79b694647edff88c5b5d5b1a99c9d2854039175facbeefb40
Bob: 7ec8f962139943bc41c17a72e782b7729b1625cf65ed7812152a5677364a4f88
Update the module with Alice's address, build, copy to the provided path, and press enter.
Step 2) Initialize MoonCoin
The moon_coin module has alreayd been deployed. The next step is to initialize MoonCoin. In this example, we'll be using 0x1::managed_coin::initialize since we want the ability to mint/burn our new MoonCoin. This adds standard functionalities to MoonCoin such as transfer, mint, burn and standard events (register, deposit, withdraw).
- Typescript
- Python
- Rust
const client = new AptosClient(NODE_URL);
/** Initializes the new coin */
async function initializeCoin(accountFrom: AptosAccount, coinTypeAddress: HexString): Promise<string> {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(`${coinTypeAddress.hex()}::moon_coin::MoonCoin`),
);
const serializer = new BCS.Serializer();
serializer.serializeBool(false);
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
"0x1::managed_coin",
"initialize",
[token],
[BCS.bcsSerializeStr("Moon Coin"), BCS.bcsSerializeStr("MOON"), BCS.bcsSerializeUint64(6), serializer.getBytes()],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(accountFrom.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(accountFrom.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
def initialize_coin(self, account_from: Account) -> Optional[str]:
""" Initialize a new coin with the given coin type. """
payload = {
"type": "script_function_payload",
"function": "0x1::managed_coin::initialize",
"type_arguments": [f"0x{account_from.address()}::moon_coin::MoonCoin"],
"arguments": [
"Moon Coin".encode("utf-8").hex(),
"MOON".encode("utf-8").hex(),
"6",
False
]
}
res = self.execute_transaction_with_payload(account_from, payload)
return str(res["hash"])
/// Initializes the new coin.
pub fn initialize_coin(&self, account_from: &mut Account) -> String {
let payload = serde_json::json!({
"type": "script_function_payload",
"function": "0x1::managed_coin::initialize",
"type_arguments": [format!("0x{}::moon_coin::MoonCoin", account_from.address())],
"arguments": [
hex::encode("Moon Coin".as_bytes()),
hex::encode("MOON".as_bytes()),
"6",
false,
]
});
self.rest_client
.execution_transaction_with_payload(account_from, payload)
}
Step 3) Register a recipient account to receive MoonCoin
In other networks, since tokens/coins are just balance numbers in a contract, anyone can "send" anyone else a random coin, even if the recipient doesn't want it. In Aptos, a user needs to explicitly register to receive a Coin<RandomCoin>
before it can be sent to them.
To register, the recipient just needs to call 0x1::coins::register<CoinType>
:
- Typescript
- Python
- Rust
/** Receiver needs to register the coin before they can receive it */
async function registerCoin(coinReceiver: AptosAccount, coinTypeAddress: HexString): Promise<string> {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(`${coinTypeAddress.hex()}::moon_coin::MoonCoin`),
);
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural("0x1::coins", "register", [token], []),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(coinReceiver.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(coinReceiver.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(coinReceiver, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
def register_coin(self, account_receiver: Account, coin_type_address: str) -> str:
""" Register the receiver account to receive transfers for the new coin. """
payload = {
"type": "script_function_payload",
"function": "0x1::coins::register",
"type_arguments": [f"0x{coin_type_address}::moon_coin::MoonCoin"],
"arguments": []
}
res = self.execute_transaction_with_payload(account_receiver, payload)
return str(res["hash"])
/// Receiver needs to register the coin before they can receive it.
pub fn register_coin(&self, account_receiver: &mut Account, coin_type_address: &str) -> String {
let payload = serde_json::json!({
"type": "script_function_payload",
"function": "0x1::coins::register",
"type_arguments": [format!("0x{}::moon_coin::MoonCoin", coin_type_address)],
"arguments": []
});
self.rest_client
.execution_transaction_with_payload(account_receiver, payload)
}
Step 4) Mint MoonCoin to the recipient as the owner of the MoonCoin
When initializing a new Coin (Step 2), the owning account receives capabilities to mint/burn the new coin. The owner account can mint MoonCoin by calling 0x1::managed_coin::mint.
- Typescript
- Python
- Rust
/** Mints the newly created coin to a specified receiver address */
async function mintCoin(
coinOwner: AptosAccount,
coinTypeAddress: HexString,
receiverAddress: HexString,
amount: number,
): Promise<string> {
const token = new TxnBuilderTypes.TypeTagStruct(
TxnBuilderTypes.StructTag.fromString(`${coinTypeAddress.hex()}::moon_coin::MoonCoin`),
);
const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
TxnBuilderTypes.ScriptFunction.natural(
"0x1::managed_coin",
"mint",
[token],
[BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(receiverAddress.hex())), BCS.bcsSerializeUint64(amount)],
),
);
const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
client.getAccount(coinOwner.address()),
client.getChainId(),
]);
const rawTxn = new TxnBuilderTypes.RawTransaction(
TxnBuilderTypes.AccountAddress.fromHex(coinOwner.address()),
BigInt(sequenceNumber),
scriptFunctionPayload,
1000n,
1n,
BigInt(Math.floor(Date.now() / 1000) + 10),
new TxnBuilderTypes.ChainId(chainId),
);
const bcsTxn = AptosClient.generateBCSTransaction(coinOwner, rawTxn);
const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
return pendingTxn.hash;
}
def mint_coin(
self,
account_coin_owner: Account,
receiver_address: str,
amount: int
) -> str:
""" Register the receiver account to receive transfers for the new coin. """
payload = {
"type": "script_function_payload",
"function": "0x1::managed_coin::mint",
"type_arguments": [f"0x{account_coin_owner.address()}::moon_coin::MoonCoin"],
"arguments": [
receiver_address,
f"{amount}"
]
}
res = self.execute_transaction_with_payload(account_coin_owner, payload)
return str(res["hash"])
/// Receiver needs to register the coin before they can receive it.
pub fn mint_coin(
&self,
account_owner: &mut Account,
receiver_address: &str,
amount: u64,
) -> String {
let payload = serde_json::json!({
"type": "script_function_payload",
"function": "0x1::managed_coin::mint",
"type_arguments": [format!("0x{}::moon_coin::MoonCoin", account_owner.address())],
"arguments": [
receiver_address,
amount.to_string(),
]
});
self.rest_client
.execution_transaction_with_payload(account_owner, payload)
}
Step 5) Check Bob's balance of MoonCoin
- Typescript
- Python
- Rust
/** Return the balance of the newly created coin */
async function getBalance(accountAddress: MaybeHexString, coinTypeAddress: HexString): Promise<string | number> {
try {
const resource = await client.getAccountResource(
accountAddress,
`0x1::coin::CoinStore<${coinTypeAddress.hex()}::moon_coin::MoonCoin>`,
);
return parseInt((resource.data as any)["coin"]["value"]);
} catch (_) {
return 0;
}
}
def get_balance(
self,
account_address: str,
coin_type_address: str,
) -> str:
""" Returns the coin balance of the given account """
balance = self.account_resource(account_address, f"0x1::coin::CoinStore<0x{coin_type_address}::moon_coin::MoonCoin>")
return balance["data"]["coin"]["value"]
/// Receiver needs to register the coin before they can receive it.
pub fn get_balance(&self, account_address: &str, coin_type_address: &str) -> u64 {
let module_type = format!(
"0x1::coin::CoinStore<0x{}::moon_coin::MoonCoin>",
coin_type_address,
);
self.rest_client
.account_resource(account_address, &module_type)
.map(|value| {
value["data"]["coin"]["value"]
.as_str()
.unwrap()
.to_string()
.parse::<u64>()
.unwrap()
})
.unwrap()
}
The data can be verified by visiting either a REST interface or the explorer:
- Alice's account via the REST interface.
- Bob's account on the explorer.