Skip to content

Your First Coin

This tutorial introduces how you can compile, deploy, and mint your own coin (as defined here), named MoonCoin.

Install your preferred SDK from the below list:


Install the precompiled binary for the Aptos CLI.


Clone the aptos-ts-sdk repo and build it:

Terminal window
git clone https://github.com/aptos-labs/aptos-ts-sdk.git
cd aptos-ts-sdk
pnpm install
pnpm build

Navigate to the TypeScript examples directory:

Terminal window
cd examples/typescript/

Install the necessary dependencies:

Terminal window
pnpm install

Run the TypeScript your_coin example:

Terminal window
pnpm run your_coin

The application will complete, printing:

Terminal window
Bob's initial MoonCoin balance: 0.
Alice mints herself 100 MoonCoin.
Alice transfers 100 MoonCoin to Bob.
Bob's updated MoonCoin balance: 100.

Step 4.1: Building and publishing the MoonCoin package

Section titled “Step 4.1: Building and publishing the MoonCoin package”

Move contracts are effectively a set of Move modules known as a package. When deploying or upgrading a new package, the compiler must be invoked with --save-metadata to publish the package. In the case of MoonCoin, the following output files are critical:

  • build/Examples/package-metadata.bcs: Contains the metadata associated with the package.
  • build/Examples/bytecode_modules/moon_coin.mv: Contains the bytecode for the moon_coin.move module.

These are read by the example and published to the Aptos blockchain:

In the TypeScript example, we use aptos move build-publish-payload command to compile and build the module. That command builds the build folder that contains the package-metadata.bcs and the bytecode for the moon_coin.mv module. The command also builds a publication transaction payload and stores it in a JSON output file that we can later read from to get the metadataBytes and byteCode to publish the contract to chain with.

Compile the package:

export function compilePackage(
packageDir: string,
outputFile: string,
namedAddresses: Array<{ name: string; address: AccountAddress }>,
) {
const addressArg = namedAddresses
.map(({ name, address }) => `${name}=${address}`)
.join(" ");
// Assume-yes automatically overwrites the previous compiled version, only do this if you are sure you want to overwrite the previous version.
const compileCommand = `aptos move build-publish-payload --json-output-file ${outputFile} --package-dir ${packageDir} --named-addresses ${addressArg} --assume-yes`;
execSync(compileCommand);
}
compilePackage("move/moonCoin", "move/moonCoin/moonCoin.json", [
{ name: "MoonCoin", address: alice.accountAddress },
]);

Publish the package to chain:

export function getPackageBytesToPublish(filePath: string) {
// current working directory - the root folder of this repo
const cwd = process.cwd();
// target directory - current working directory + filePath (filePath JSON file is generated with the previous, compilePackage, CLI command)
const modulePath = path.join(cwd, filePath);
const jsonData = JSON.parse(fs.readFileSync(modulePath, "utf8"));
const metadataBytes = jsonData.args[0].value;
const byteCode = jsonData.args[1].value;
return { metadataBytes, byteCode };
}
const { metadataBytes, byteCode } = getPackageBytesToPublish(
"move/moonCoin/moonCoin.json",
);
// Publish MoonCoin package to chain
const transaction = await aptos.publishPackageTransaction({
account: alice.accountAddress,
metadataBytes,
moduleBytecode: byteCode,
});
const pendingTransaction = await aptos.signAndSubmitTransaction({
signer: alice,
transaction,
});
await aptos.waitForTransaction({ transactionHash: pendingTransaction.hash });

Step 4.2: Understanding the MoonCoin module

Section titled “Step 4.2: Understanding the MoonCoin module”

The MoonCoin module defines the MoonCoin struct, or the distinct type of coin type. In addition, it contains a function called init_module. The init_module function is called when the module is published. In this case, MoonCoin initializes the MoonCoin coin type as a ManagedCoin, which is maintained by the owner of the account.

module MoonCoin::moon_coin {
struct MoonCoin {}
fun init_module(sender: &signer) {
aptos_framework::managed_coin::initialize<MoonCoin>(
sender,
b"Moon Coin",
b"MOON",
6,
false,
);
}
}

Coins have several primitives:

  • Minting: Creating new coins.
  • Burning: Deleting coins.
  • Freezing: Preventing an account from storing coins in CoinStore.
  • Registering: Creating a CoinStore resource on an account for storing coins.
  • Transferring: Withdrawing and depositing coins into CoinStore.

Once a coin type has been published to the Aptos blockchain, the entity that published that coin type can initialize it:

module 0x1::coin {
public fun initialize<CoinType>(
account: &signer,
name: string::String,
symbol: string::String,
decimals: u8,
monitor_supply: bool,
): (BurnCapability<CoinType>, FreezeCapability<CoinType>, MintCapability<CoinType>) {
let account_addr = signer::address_of(account);
assert!(
coin_address<CoinType>() == account_addr,
error::invalid_argument(ECOIN_INFO_ADDRESS_MISMATCH),
);
assert!(
!exists<CoinInfo<CoinType>>(account_addr),
error::already_exists(ECOIN_INFO_ALREADY_PUBLISHED),
);
let coin_info = CoinInfo<CoinType> {
name,
symbol,
decimals,
supply: if (monitor_supply) { option::some(optional_aggregator::new(MAX_U128, false)) } else { option::none() },
};
move_to(account, coin_info);
(BurnCapability<CoinType>{ }, FreezeCapability<CoinType>{ }, MintCapability<CoinType>{ })
}
}

This ensures that this coin type has never been initialized before. Notice the check on lines 10 and 15 to ensure that the caller to initialize is the same one that actually published this module, and that there is no CoinInfo stored on their account. If both those conditions check, then a CoinInfo is stored and the caller obtains capabilities for burning, freezing, and minting.


To use a coin, an entity must register a CoinStore for it on their account:

public entry fun registerCoinType(account: &signer) {

MoonCoin uses ManagedCoin that provides an entry function wrapper: managed_coin::register. Here is an example script for registration:

script {
fun register(account: &signer) {
aptos_framework::managed_coin::register<MoonCoin::moon_coin::MoonCoin>(account)
}
}

Minting coins requires the mint capability that was produced during initialization. the function mint (see below) takes in that capability and an amount, and returns back a Coin<T> struct containing that amount of coins. If the coin tracks supply, it will be updated.

module 0x1::coin {
public fun mint<CoinType>(
amount: u64,
_cap: &MintCapability<CoinType>,
): Coin<CoinType> acquires CoinInfo {
if (amount == 0) {
return zero<CoinType>()
};
let maybe_supply = &mut borrow_global_mut<CoinInfo<CoinType>>(coin_address<CoinType>()).supply;
if (option::is_some(maybe_supply)) {
let supply = option::borrow_mut(maybe_supply);
optional_aggregator::add(supply, (amount as u128));
};
Coin<CoinType> { value: amount }
}
}

ManagedCoin makes this easier by providing an entry function managed_coin::mint.


Aptos provides several building blocks to support coin transfers:

  • coin::deposit<CoinType>: Allows any entity to deposit a coin into an account that has already called coin::register<CoinType>.
  • coin::withdraw<CoinType>: Allows any entity to extract a coin amount from their account.
  • aptos_account::transfer_coins<CoinType>: Transfer coins of specific CoinType to a receiver.