🎉

【Aptos/Move】Aptosブロックチェーンで独自のコインを作成・転送してみる

2024/05/04に公開

はじめに

Aptosを手を動かしながら理解したい人のために、チュートリアルを自分で試してみて、事前に知っておいた方が良いことやつまづきポイントなどまとめました!

知っておかないと、今後のチュートリアルで理解が難しくなるAptosならではのことについてはこちらに記載しているので、詰まったら参照してみてください!
https://zenn.dev/mameta29/articles/7844978c45f8a3

今回のチュートリアルでは、Aptosブロックチェーン上で独自のコイン「MoonCoin」をコンパイル、デプロイ、ミントする方法について解説します。
https://aptos.dev/tutorials/your-first-coin

Step 1: SDKを選ぶ

本チュートリアルではSDKはTypeScript、Pythonが選べますが、TypeScriptを選択します。
インストールは下記の手順(npmyarnなど)でできますが、私はpnpmを使用していきます。
(以降チュートリアルではpnpmが使用されています。)
TypeSctipt SDKインストール
https://aptos.dev/sdks/ts-sdk/
pnpm インストール
https://pnpm.io/ja/installation

Step 2: サンプルコードを実行してみよう!

それではコードを動かしてみましょう!

  • まずgithubからコードをcloneしてきます。
git clone https://github.com/aptos-labs/aptos-ts-sdk.git
  • cloneできたらプロジェクトに移り、依存関係をインストールしてビルドします。
cd aptos-ts-sdk
pnpm install
pnpm build

私はここでこけました。なにやらESMとCJSのビルドは成功したが、DTSのビルド中(TypeScriptの型定義ファイル(.d.tsファイル)を生成するプロセス)にメモリ不足エラーが発生してしまいました。

根本的な解決にはなりませんが一旦下記でビルドまで出来ました。
(ヒープメモリの上限を8GBに設定するというフラグを追加)

node --max-old-space-size=8192 ./node_modules/.bin/tsup
  • ビルドができたらプロジェクトディレクトリへ移動します。
cd examples/typescript/
  • そこでもう一度依存関係をインストールしてプロジェクトを立ち上げます。
pnpm install
pnpm run your_coin

下記のようなアウトプットが出て来れば成功です!

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: MoonCoinの詳細

Step 4.1: MoonCoinパッケージのビルドと公開

Moveコントラクトは、パッケージとして知られるMoveモジュールのセットです。新しいパッケージをデプロイまたはアップグレードする際、コンパイラは--save-metadataオプションを付けて呼び出し、パッケージを公開する必要があります。MoonCoinの場合、以下の出力ファイルが重要です。

build/Examples/package-metadata.bcs:パッケージに関連付けられたメタデータが含まれます。
build/Examples/bytecode_modules/moon_coin.mv:moon_coin.moveモジュールのバイトコードが含まれます。

これらのファイルはサンプルコードで読み込まれ、Aptosブロックチェーンに公開されます。
TypeScriptの例では、aptos move build-publish-payloadコマンドを使用してモジュールをコンパイルしビルドしています。このコマンドは、package-metadata.bcsとmoon_coin.mvモジュールのバイトコードを含むbuildフォルダを構築します。また、公開トランザクションのペイロードを構築し、metadataBytesとbyteCodeを取得してコントラクトをチェーンに公開するためにJSONファイルに出力します。

パッケージをコンパイルする

export function compilePackage(
  packageDir: string,
  outputFile: string,
  namedAddresses: Array<{ name: string; address: AccountAddress }>,
) {
  const addressArg = namedAddresses
    .map(({ name, address }) => `${name}=${address}`)
    .join(" ");
  // Assume-yesにより、以前にコンパイルしたバージョンが自動的に上書きされます。
  // 以前のバージョンを確実に上書きしたい場合にのみ使用してください。
  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 },
]);

パッケージをチェーンに公開する

export function getPackageBytesToPublish(filePath: string) {
  // 現在の作業ディレクトリ - このリポジトリのルートフォルダ
  const cwd = process.cwd();
  // ターゲットディレクトリ - 現在の作業ディレクトリ + filePath(filePathのJSONファイルは前述のcompilePackage CLIコマンドで生成される)
  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",
);

// MoonCoinパッケージをチェーンに公開
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: MoonCoinモジュールを理解する

MoonCoinモジュールは、MoonCoin構造体(コインタイプの個別のタイプ)を定義します。さらに、init_moduleという関数が含まれています。init_module関数は、モジュールが公開されたときに呼び出されます。この場合、MoonCoinはMoonCoinコインタイプをManagedCoinとして初期化します。ManagedCoinはアカウントの所有者によって管理されます。
ManagedCoinフレームワーク:
ManagedCoinは、ユーザーが直接管理するコインのためのシンプルなコイン管理フレームワークです。ミントとバーンのための便利なラッパーを提供します。

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

Step 4.3: コインを理解する

コインにはいくつかのプリミティブがあります。

Minting:新しいコインを作成すること。
Burning:コインを削除すること。
Freezing:アカウントがCoinStoreにコインを保存できないようにすること。
Registering:アカウントにコインを保存するためのCoinStoreリソースを作成すること。
Transferring:CoinStoreへのコインの引き出しと預け入れ。

Step 4.3.1: コインを初期化する

コインタイプがAptosブロックチェーンに公開されたら、そのコインタイプを公開したエンティティはそれを初期化できます。

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>{ })
}

これにより、このコインタイプが以前に初期化されたことがないことが保証されます。10行目と15行目のチェックで、initializeを呼び出したのがこのモジュールを実際に公開したのと同じ呼び出し元であること、およびそのアカウントにCoinInfoが保存されていないことを確認しています。両方の条件がチェックされると、CoinInfoが保存され、呼び出し元はバーン、フリーズ、ミントの機能を取得します。

Step 4.3.2:コインを登録する

コインを使用するには、エンティティはそのアカウントにCoinStoreを登録する必要があります。

public entry fun registerCoinType(account: &signer) {

MoonCoinはManagedCoinを使用しており、managed_coin::registerというエントリ関数ラッパーを提供しています。登録のサンプルスクリプトは次のようになります。

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

Step 4.3.3:コインをミントする

コインのミントには、初期化時に生成されたミント機能が必要です。以下のmint関数は、その機能と量を受け取り、その量のコインを含むCoin<T>構造体を返します。コインが供給量を追跡する場合は、更新されます。

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は、managed_coin::mintというエントリ関数を提供することでこれを簡単にしています。

4.3.4:コインを転送する

Aptosは、コインの転送をサポートするためのいくつかのビルディングブロックを提供しています。

  • coin::deposit<CoinType>:すでにcoin::register<CoinType>を呼び出したアカウントにコインを預けることができます。
  • coin::withdraw<CoinType>:アカウントからコインの金額を引き出すことができます。
  • aptos_account::transfer_coins<CoinType>:特定のCoinTypeのコインを受信者に転送します。

まとめ

以上が、Aptosブロックチェーン上で独自のコインを作成、転送する方法の概要です。MoonCoinの例を通じて、コインの初期化、登録、ミント、転送の各ステップについて説明しました。
Aptosは開発者にとって使いやすいプラットフォームを目指しており、Moveプログラミング言語とSDKを通じてこれらの機能を簡単に利用できるようになっています。独自のコインの開発に興味がある方は、ぜひAptosを試してみてください。

Discussion