Skip to main content

Your First Coin

This tutorial details how to deploy and manage a new Coin. The steps are:

  1. Deploy moon_coin module. Let's call it moon_coin.
  2. Initialize MoonCoin via the standard Coin framework module.
  3. Register a recipient account to receive MoonCoin.
  4. 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:

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.

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

For Typescript:
  • 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

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 address MoonCoinType='_' in moon_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).

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;
}

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>:

/** 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;
}

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.

/** 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;
}

Step 5) Check Bob's balance of MoonCoin

/** 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;
}
}

The data can be verified by visiting either a REST interface or the explorer: