📌

🐶Aptosのチュヌトリアルを読んで簡易DAppを䜜っおみた🐶

2022/11/19に公開

皆さん、こんにちは

今回は、先日メむンネットがロヌンチしたばかりのブロックチェヌンであるAptosのチュヌトリアルを読んで簡易Dappを䜜っおみたのでその蚘録をたずめおいきたす

Aptosのスマヌトコントラクトは、Rust蚀語ラむクなプログラミング蚀語であるMove蚀語で開発したす

興味がある方はぜひ読み進んでみおください

゜ヌスは䞋蚘のリポゞトリにたずめおいたす

https://github.com/mashharuki/AptosRepo

目次

  1. Aptosに぀いお
  2. 今回やったチュヌトリアルの抂芁
  3. 䜜ったDAppの抂芁
  4. ゜ヌスコヌドなど
  5. その他Aptos䞊に展開されおいる気になったプロダクト
  6. 最埌に

1. Aptosに぀いお

たずは、簡単にAptosに぀いおたずめおいきたす

Aptosは、開発䞭のL1(Layer 1)ブロックチェヌンやそのプロゞェクトの総称で、高凊理胜力・䜎遅延を匷みずしおいたす。

スマヌトコントラクトを扱うこずもできるため、AvalancheやSolanaずいったチェヌンず競合するプロゞェクトです。Aptosは、Diemの元メンバヌであるMo Shaikh氏・Avery Ching氏が䞭心ずなっお開発がスタヌトしおいお、ラむバルプロゞェクトずしおは、Suiが挙げられたす。

googleずもパヌトナヌシップを提携しおおり、Web3開発環境の敎備やハッカ゜ンの開催も䌁画されおいるずいうこずでこれたでのブロックチェヌンプロゞェクトずはたた違ったアプロヌチで䞖の䞭ぞの普及を図ろうずしおいたす。

https://coinpost.jp/?p=406562

先日メむンネットがロヌンチされたしたが、テストネット公開の時点でもすでに100以䞊のプロゞェクトがAptos䞊に展開されおいたずいうこずでかなり泚目床が高かったプロックチェヌンになりたす。

䞋蚘サむトからプロゞェクトの䞀芧が確認できたす。

https://movemarketcap.com/aptos/

開発に䜿甚されおいるのは、あのDiemでも採甚されおいたずいうMove蚀語になりたす。

Move蚀語はRust蚀語をベヌスに䜜られおいるので芋た目がRustに䌌おいたすそのため、耇数タスクを䞊列凊理できるこずや安党なメモリ管理の仕組みなどRust蚀語の良い郚分も継承されおいたす。

コンセンサスアルゎリズムには、Bullshark /ブルヌシャヌクず呌ばれるものが採甚されおいたす。Bullsharkは、HotStuffずいうコンセンサスアルゎリズムをベヌスに䜜られおおり、高速性や安党性をさらに改善したものずなっおいたす。

コンセンサスアルゎリズムに぀いおはたさに今、䞖界䞭でより良いアルゎリズムが研究されおいる真っ最䞭なので今埌はさらに良いアルゎリズムが生み出されおくる可胜性がありたす

あず、特筆すべき点ずしおはセキュリティ面でも面癜い詊みをしおいたす。埓来の仕組みであれば䞀床玛倱した日秘密鍵は、䜕らかのバックアップがない堎合には埩元するこずが䞍可胜でしたが、Aptosではそれを可胜にするための仕組みを䜜ろうずしたりしおいるずのこずです。

秘密鍵の盗難防止のためにロヌテヌション機胜なども備えられおおり埓来のブロックチェヌンプロゞェクトが抱えおいた課題を解決しようずしおいたす。

Aptosに぀いおの簡単な抂芁はここたでずしたいず思いたす

2. 今回やったチュヌトリアルの抂芁

今回トラむしたチュヌトリアルですが、䞋蚘サむトを参考に順番に実斜しおいきたした

https://aptos.dev/tutorials/aptos-quickstarts

CLIのむンストヌルに始たり、トランザクションの送信や簡単なスマヌトコントラクトの実装、dappの実装ずいった具合です

たずは、CLIのむンストヌルからです

基本的には開発者向けドキュメントの指瀺に埓っお進めおいけば問題なくむンストヌルできるず思いたす

https://aptos.dev/cli-tools/aptos-cli-tool/install-aptos-cli

むンストヌルがうたくいけば、 aptos -hず入力するず次のような内容がタヌミナルに出力されるず思いたす!

Command Line Interface (CLI) for developing and interacting with the Aptos blockchain

USAGE:
    aptos <SUBCOMMAND>

OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information

SUBCOMMANDS:
    account       Tool for interacting with accounts
    config        Tool for interacting with configuration of the Aptos CLI tool
    genesis       Tool for setting up an Aptos chain Genesis transaction
    governance    Tool for on-chain governance
    help          Print this message or the help of the given subcommand(s)
    info          Show build information about the CLI
    init          Tool to initialize current directory for the aptos tool
    key           Tool for generating, inspecting, and interacting with keys
    move          Tool for Move related operations
    node          Tool for operations related to nodes
    stake         Tool for manipulating stake

Aptos SDKのむンストヌルは、次のコマンドで実行できたす

npm i aptos

さお、たずはチュヌトリアルの最初からいきたす。

Your Fisrt Transaction

https://github.com/aptos-labs/aptos-core.gitからgitをcloneしおきたす。

最初のチュヌトリアルで実行するのは、transfer_coin.tsずいうスクリプトになりたす。

䞭身も簡単に掲茉したす。

transfer_coin
import dotenv from "dotenv";
dotenv.config();

import { 
    AptosClient, 
    AptosAccount, 
    CoinClient,
    FaucetClient 
} from "aptos";
import { 
    NODE_URL, 
    FAUCET_URL 
} from "./common";


(async () => {
    // Create API and faucet clients.
    const client = new AptosClient(NODE_URL);
    const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL); 

    // Create client for working with the coin module.
    const coinClient = new CoinClient(client); 

    // Create accounts.
    const alice = new AptosAccount();
    const bob = new AptosAccount(); 

    // Print out account addresses.
    console.log("=== Addresses ===");
    console.log(`Alice: ${alice.address()}`);
    console.log(`Bob: ${bob.address()}`);
    console.log("");

    // Fund accounts.
    await faucetClient.fundAccount(alice.address(), 100_000_000);
    await faucetClient.fundAccount(bob.address(), 0); 

    // Print out initial balances.
    console.log("=== Initial Balances ===");
    console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
    console.log(`Bob: ${await coinClient.checkBalance(bob)}`); 
    console.log("");

    // Have Alice send Bob some AptosCoins. (transfer)
    let txnHash = await coinClient.transfer(alice, bob, 1_000, { 
        gasUnitPrice: BigInt(100) 
    }); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash); 

    // Print out intermediate balances.
    console.log("=== Intermediate Balances ===");
    console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
    console.log(`Bob: ${await coinClient.checkBalance(bob)}`);
    console.log("");

    // Have Alice send Bob some more AptosCoins.
    txnHash = await coinClient.transfer(alice, bob, 1_000, { 
        gasUnitPrice: BigInt(100) 
    });
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash, { checkSuccess: true });

    // Print out final balances.
    console.log("=== Final Balances ===");
    console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
    console.log(`Bob: ${await coinClient.checkBalance(bob)}`);
    console.log("");
})();

このスクリプトがうたく実行されるず次のように出力されたす。

実行しおいる内容ずしおは、クラむアント甚のモゞュヌルを読み蟌んで぀のアカりントを䜜成し、トヌクンを付䞎し転送するずいったシンプルなものです。ethersなどを䜿っおトランザクションを実行する凊理を実装したこずがある人であればなんずなく芋芚えのある蚘述になっおいるず思いたす。

=== Addresses ===
Alice: 0x14b30e5b42a94247c1dd97d4f24d01449cc5e85b02ebcb7fc0281f915fefb963
Bob: 0x121ba4c14d9fba0bc89b201e467771a274547379c03b1bd082abba751c012994

=== Initial Balances ===
Alice: 100000000
Bob: 0

=== Intermediate Balances ===
Alice: 99972800
Bob: 1000

=== Final Balances ===
Alice: 99945600
Bob: 2000

うたくトヌクンが転送されおいるこずが確認できたす

Your First NFT

次はものすごくシンプルなNFTを転送するスクリプトです
こちらもチュヌトリアルの指瀺に埓っお資材を匕っ匵っおきたしょう

ここでは、NFTのスマヌトコントラクトは実装せずにAptos偎で甚意しおくれおいるものを利甚したす
詳しい䞭身を知りたい方は䞋蚘ペヌゞを参照しおみおください

https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-token/sources/token.move

https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/mint_nft

実行するサンプルスクリプトは次のような内容です。

simple_nft.ts
import dotenv from "dotenv";
dotenv.config();

import { 
    AptosClient, 
    AptosAccount, 
    FaucetClient, 
    TokenClient, 
    CoinClient 
} from "aptos";
import { 
    NODE_URL, 
    FAUCET_URL 
} from "./common";

(async () => {
    // Create API and faucet clients.
    const client = new AptosClient(NODE_URL);
    const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL); 

    // Create client for working with the token module.
    const tokenClient = new TokenClient(client); 
    // Create a coin client for checking account balances.
    const coinClient = new CoinClient(client);

    // Create accounts.
    const alice = new AptosAccount();
    const bob = new AptosAccount(); 

    // Print out account addresses.
    console.log("=== Addresses ===");
    console.log(`Alice: ${alice.address()}`);
    console.log(`Bob: ${bob.address()}`);
    console.log("");

    // Fund accounts.
    await faucetClient.fundAccount(alice.address(), 100_000_000);
    await faucetClient.fundAccount(bob.address(), 100_000_000); 

    console.log("=== Initial Coin Balances ===");
    console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
    console.log(`Bob: ${await coinClient.checkBalance(bob)}`);
    console.log("");

    console.log("=== Creating Collection and Token ===");

    const collectionName = "Alice's";
    const tokenName = "Alice's first token";
    const tokenPropertyVersion = 0;

    const tokenId = {
        token_data_id: {
            creator: alice.address().hex(),
            collection: collectionName,
            name: tokenName,
        },
        property_version: `${tokenPropertyVersion}`,
    };

    // Create the collection.
    const txnHash1 = await tokenClient.createCollection(
        alice,
        collectionName,
        "Alice's simple collection",
        "https://alice.com",
    ); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash1, { 
        checkSuccess: true 
    });

    // Create a token in that collection.
    const txnHash2 = await tokenClient.createToken(
        alice,
        collectionName,
        tokenName,
        "Alice's simple token",
        1,
        "https://aptos.dev/img/nyan.jpeg",
    ); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash2, { 
        checkSuccess: true 
    });

    // get the collection data.
    const collectionData = await tokenClient.getCollectionData(alice.address(), collectionName);
    console.log(`Alice's collection: ${JSON.stringify(collectionData, null, 4)}`);

    // Get the token balance.
    const aliceBalance1 = await tokenClient.getToken(
        alice.address(),
        collectionName,
        tokenName,
        `${tokenPropertyVersion}`,
    );
    console.log(`Alice's token balance: ${aliceBalance1["amount"]}`);

    // Get the token data.
    const tokenData = await tokenClient.getTokenData(
        alice.address(), 
        collectionName, 
        tokenName
    );
    console.log(`Alice's token data: ${JSON.stringify(tokenData, null, 4)}`); // <:!:section_8

    // Alice offers one token to Bob.
    console.log("\n=== Transferring the token to Bob ===");
    
    // transfer
    const txnHash3 = await tokenClient.offerToken(
        alice,
        bob.address(),
        alice.address(),
        collectionName,
        tokenName,
        1,
        tokenPropertyVersion,
    ); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash3, { 
        checkSuccess: true 
    });

    // Bob claims the token Alice offered him.
    const txnHash4 = await tokenClient.claimToken(
        bob,
        alice.address(),
        alice.address(),
        collectionName,
        tokenName,
        tokenPropertyVersion,
    ); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash4, { 
        checkSuccess: true 
    });

    // get token data
    const aliceBalance2 = await tokenClient.getToken(
        alice.address(),
        collectionName,
        tokenName,
        `${tokenPropertyVersion}`,
    );
    // get balance
    const bobBalance2 = await tokenClient.getTokenForAccount(bob.address(), tokenId);

    console.log(`Alice's token balance: ${aliceBalance2["amount"]}`);
    console.log(`Bob's token balance: ${bobBalance2["amount"]}`);

    console.log("\n=== Transferring the token back to Alice using MultiAgent ===");

    // transfer token
    let txnHash5 = await tokenClient.directTransferToken(
        bob,
        alice,
        alice.address(),
        collectionName,
        tokenName,
        1,
        tokenPropertyVersion,
    ); 
    // Waiting for transaction resolution
    await client.waitForTransaction(txnHash5, { 
        checkSuccess: true 
    });

    // get token data
    const aliceBalance3 = await tokenClient.getToken(
        alice.address(),
        collectionName,
        tokenName,
        `${tokenPropertyVersion}`,
    );
    // get balance
    const bobBalance3 = await tokenClient.getTokenForAccount(bob.address(), tokenId);

    console.log(`Alice's token balance: ${aliceBalance3["amount"]}`);
    console.log(`Bob's token balance: ${bobBalance3["amount"]}`);
})();

先ほどず同じで最初にモゞュヌルのむンポヌトを行っお、アカりントの䜜成、NFTの䜜成→転送、残高をチェックずいう流れになっおいたす

泚目すべきは、最初に読み蟌んでいるtokenClientモゞュヌルでここで実装されおいるメ゜ッドを䜿っお、NFTの䜜成や付䞎、転送を行うこずができる点です

問題なく実行できるず䞋蚘のような内容がタヌミナルに出力されたす

=== Addresses ===
Alice: 0x979600b20654944c269abf3ba52d06dfae1d98c74bead66d5ad0d3561d26cd2c
Bob: 0xcfb65b98f4a3277d04e18f864219ae65e28e03c817754a72345ac6d8eaf9bdea

=== Initial Coin Balances ===
Alice: 100000000
Bob: 100000000

=== Creating Collection and Token ===
Alice's collection: {
    "description": "Alice's simple collection",
    "maximum": "18446744073709551615",
    "mutability_config": {
        "description": false,
        "maximum": false,
        "uri": false
    },
    "name": "Alice's",
    "supply": "1",
    "uri": "https://alice.com"
}
Alice's token balance: 1
Alice's token data: {
    "default_properties": {
        "map": {
            "data": []
        }
    },
    "description": "Alice's simple token",
    "largest_property_version": "0",
    "maximum": "18446744073709551615",
    "mutability_config": {
        "description": false,
        "maximum": false,
        "properties": false,
        "royalty": false,
        "uri": false
    },
    "name": "Alice's first token",
    "royalty": {
        "payee_address": "0x979600b20654944c269abf3ba52d06dfae1d98c74bead66d5ad0d3561d26cd2c",
        "royalty_points_denominator": "0",
        "royalty_points_numerator": "0"
    },
    "supply": "1",
    "uri": "https://aptos.dev/img/nyan.jpeg"
}

=== Transferring the token to Bob ===
Alice's token balance: 0
Bob's token balance: 1

=== Transferring the token back to Alice using MultiAgent ===
Alice's token balance: 1
Bob's token balance: 0

Your First Coin

ちょっず順番が前埌したすが、dappだけ重いので最初にこっちからたずめおいきたす

https://aptos.dev/tutorials/your-first-coin

チュヌトリアルに埓っおcloneしおきたす

実行するスクリプトは、my_coin.tsずいうものです

my_coin.ts
// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0
import assert from "assert";
import fs from "fs";
import path from "path";
import { 
    NODE_URL, 
    FAUCET_URL 
} from "./common";
import { 
    AptosAccount, 
    AptosClient, 
    TxnBuilderTypes, 
    MaybeHexString, 
    HexString, 
    FaucetClient 
} from "aptos";
import { unstable_deprecatedPropType } from "@mui/utils";

/**
 * This example depends on the MoonCoin.move module having already been published to the destination blockchain.
 * Open another terminal and `aptos move compile --package-dir ~/aptos-core/aptos-move/move-examples/moon_coin --save-metadata --named-addresses MoonCoin=<Alice address from above step>`.
 */

/**
 * readline function
 */
const readline = require("readline").createInterface({
    input: process.stdin,
    output: process.stdout,
});

/**
 * CoinClient class
 */
class CoinClient extends AptosClient {

    constructor() {
        super(NODE_URL);
    }

    /**
     * registerCoin function
     */
    async registerCoin(
        coinTypeAddress: HexString, 
        coinReceiver: AptosAccount
    ): Promise<string> {
        // create Tx data
        const rawTxn = await this.generateTransaction(coinReceiver.address(), {
            function: "0x1::managed_coin::register",
            type_arguments: [`${coinTypeAddress.hex()}::moon_coin::MoonCoin`],
            arguments: [],
        });
    
        // sign
        const bcsTxn = await this.signTransaction(coinReceiver, rawTxn);
        // submit
        const pendingTxn = await this.submitTransaction(bcsTxn);

        return pendingTxn.hash;
    }

    /**
     * Mint function
     */
    async mintCoin(
        minter: AptosAccount, 
        receiverAddress: HexString, 
        amount: number | bigint
    ): Promise<string> {
        // create Tx data
        const rawTxn = await this.generateTransaction(minter.address(), {
            function: "0x1::managed_coin::mint",
            type_arguments: [`${minter.address()}::moon_coin::MoonCoin`],
            arguments: [receiverAddress.hex(), amount],
        });

        // sign
        const bcsTxn = await this.signTransaction(minter, rawTxn);
        // submit
        const pendingTxn = await this.submitTransaction(bcsTxn);

        return pendingTxn.hash;
    }

    /** 
     * get balance function
     */
    async getBalance(
        accountAddress: MaybeHexString, 
        coinTypeAddress: HexString
    ): Promise<string | number> {
        try {
            // get balance
            const resource = await this.getAccountResource(
                accountAddress,
                `0x1::coin::CoinStore<${coinTypeAddress.hex()}::moon_coin::MoonCoin>`,
            );

            return parseInt((resource.data as any)["coin"]["value"]);
        } catch (_) {
            return 0;
        }
    }

    /**
     * transfer function
     */
    async transferCoin(
        minter: AptosAccount, 
        receiverAddress: HexString, 
        amount: number | bigint
    ): Promise<string> {
        // create Tx data
        const rawTxn = await this.generateTransaction(minter.address(), {
            function: "0x1::coin::transfer",
            type_arguments: [`${minter.address()}::moon_coin::MoonCoin`],
            arguments: [minter.address().hex(), receiverAddress.hex(), amount],
        });

        // sign
        const bcsTxn = await this.signTransaction(minter, rawTxn);
        // submit
        const pendingTxn = await this.submitTransaction(bcsTxn);

        return pendingTxn.hash;
    }
}

/**
 * main function
 */
async function main() {
    assert(process.argv.length == 3, "Expecting an argument that points to the moon_coin directory.");
    // create clinet instance
    const client = new CoinClient();
    const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);

    // Create two accounts
    const alice = new AptosAccount();
    const bob = new AptosAccount();

    console.log("\n=== Addresses ===");
    console.log(`Alice: ${alice.address()}`);
    console.log(`Bob: ${bob.address()}`);

    await faucetClient.fundAccount(alice.address(), 100_000_000);
    await faucetClient.fundAccount(bob.address(), 100_000_000);

    await new Promise<void>((resolve) => {
        readline.question("Update the module with Alice's address, compile, and press enter.", () => {
            resolve();
            readline.close();
        });
    });

    // module path (  ../move-example/moon_coin)
    const modulePath = process.argv[2];
    const packageMetadata = fs.readFileSync(path.join(modulePath, "build", "Examples", "package-metadata.bcs"));
    const moduleData = fs.readFileSync(path.join(modulePath, "build", "Examples", "bytecode_modules", "moon_coin.mv"));

    console.log("Publishing MoonCoin package.");

    // publish module by alice
    let txnHash = await client.publishPackage(
        alice, 
        new HexString(packageMetadata.toString("hex")).toUint8Array(), 
        [
            new TxnBuilderTypes.Module(new HexString(moduleData.toString("hex")).toUint8Array()),
        ]
    );
    // check transaction status
    await client.waitForTransaction(txnHash, { checkSuccess: true }); 

    console.log("Bob registers the newly created coin so he can receive it from Alice");
    // call register coin
    txnHash = await client.registerCoin(alice.address(), bob);
    await client.waitForTransaction(txnHash, { checkSuccess: true });
    console.log(`Bob's initial MoonCoin balance: ${await client.getBalance(bob.address(), alice.address())}.`);

    console.log("Alice mints Bob some of the new coin.");
    // mint coin
    txnHash = await client.mintCoin(alice, bob.address(), 100);
    await client.waitForTransaction(txnHash, { checkSuccess: true });
    console.log(`Bob's updated MoonCoin balance: ${await client.getBalance(bob.address(), alice.address())}.`);

    /*
    // transfer coin
    txnHash = await client.transferCoin(bob, alice.address(), 100);
    await client.waitForTransaction(txnHash, { checkSuccess: true });
    console.log(`alice's updated MoonCoin balance: ${await client.getBalance(alice.address(), bob.address())}.`);
    */
}

if (require.main === module) {
    main().then((resp) => console.log(resp));
}

コむンを受け取るメ゜ッド、mintするメ゜ッド、残高を確認するメ゜ッド、転送するメ゜ッドが実装されおおり、それらを䜿っおmain()の䞭で実行しおいたす。

実行する内容ずしおは、アカりントの䜜成⇚ スマコンのコンパむル・デプロむ→ コむンの発行 → 残高確認ずいう流れになっおいたす。
※ transferの郚分はバグがあっおただ動かせおいたせん。

たず最初にスクリプトを実行するず次のような内容が出力されたす。

=== Addresses ===
Alice: 0x5cf018f581409a22b93036ba13e4c26a9c2be954f0194ad06b303e6413f4dc93
Bob: 0xe336bc5aa5c060538c5a89a2e039509dad7011be7de67ad1cc88d4dcb0233c17
Update the module with Alice's address, compile, and press enter.

ここたできたら別のタヌミナルを立ち䞊げおスマコンをコンパむル・デプロむしたす

チュヌトリアルの指瀺に埓っお、今回コンパむルするMoonCoin.moveファむルが栌玍されおいるフォルダに移動し、Move.tomlファむルがあるフォルダ配䞋で、䞋蚘コマンドを実行したしょう アドレスには、䞊で出力されおいるAliceのアドレスを入れおみおください

aptos move compile --named-addresses MoonCoin=<Alice's Address> --save-metadata

うたく完了するず䞋蚘のように出力されたす

 {
   "Result": [
     "5cf018f581409a22b93036ba13e4c26a9c2be954f0194ad06b303e6413f4dc93::moon_coin"
   ]
 }
 ✹  Done in 2.60s.

これが完了したら元のタヌミナルに戻っおetnerを抌したしょう トヌクンが発行され、残高が取埗できるようになるはずです

Publishing MoonCoin package.
Bob registers the newly created coin so he can receive it from Alice
Bob's initial MoonCoin balance: 0.
Alice mints Bob some of the new coin.
Bob's updated MoonCoin balance: 100.
undefined
✹  Done in 166.96s.

今回コンパむルしたMoonCoinモゞュヌルですが、aptos_frameworkのmanaged_coinを継承しおおりMintなどの関数の実態はこっちに実装されおいたす。実態はmanaged_coin.moveやcoin.moveファむルを参照しおください。
※ 私も絶賛勉匷䞭です

ちなみにコンパむルしたMoveCoin.move自䜓はaptos_framework を利甚しおいるおかげでものすごくシンプルなコヌドになっおいたす。

MoonCoin.move
/**
 * MoonCoin
 */
module MoonCoin::moon_coin {
    // Mooncoin
    struct MoonCoin {}

    /**
     * init function
     */
    fun init_module(sender: &signer) {
        aptos_framework::managed_coin::initialize<MoonCoin>(
            sender,
            b"Moon Coin",
            b"MOON",
            6,
            false,
        );
    }
}

Rustを曞いたこずがある人であれば、芋芚えのある構成になっおいるず思いたす

ちなみに蚭定ファむルは次のずおりです

Move.toml
[package]
name = "Examples"
version = "0.0.0"

[addresses]
MoonCoin = "_"

[dependencies]
AptosFramework = { local = "../../framework/aptos-framework" }

3. 䜜ったDAppの抂芁

さぁ、チュヌトリアルの最埌は、サンプルDappの構築です
チュヌトリアルに埓っおたずはWalletをブラりザの拡匵機胜にむンストヌルしたしょう Metamaskず同じような流れです

https://aptos.dev/guides/install-petra-wallet-extension

テンプレを䜜っお動くこずを確認したしょう

npx create-react-app first-dapp --template typescript
cd first-dapp
npm start

クラむアントからAptos䞊のスマヌトコントラクトを操䜜する堎合には、window.aptosずいうオブゞェクトに含たれおいる情報に埓っお操䜜したす。

次に、スマヌトコントラクトをブロックチェヌン䞊にデプロむしたしょう
たず、アカりントを生成したす。

aptos init

次に、スマヌトコントラクトをコンパむルしたす。スマヌトコントラクトのあるフォルダたでのパスずアドレスの情報は個人の環境に合うように適宜倉曎しおいたす。

aptos move compile --package-dir ./../move-example/hello_blockchain/ --named-addresses hello_blockchain=0xa7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647

コンパむル結果

{
  "Result": [
    "a7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647::message"
  ]
}

次にスマヌトコントラクトのテストを実行したす。

aptos move test --package-dir ./../move-example/hello_blockchain/ --named-addresses hello_blockchain=0xa7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647

テスト結果

INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING Examples
Running Move unit tests
[ PASS    ] 0xa7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647::message_tests::sender_can_set_message
[ PASS    ] 0xa7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647::message::sender_can_set_message
Test result: OK. Total tests: 2; passed: 2; failed: 0
{
  "Result": "Success"
}

そしおスマヌトコントラクトをデプロむしたす。

aptos move publish --package-dir ./../move-example/hello_blockchain/ --named-addresses hello_blockchain=0xa7bea4f79092c5f315e4ddf40af2637ace9e0438d22e64587bd3bfcb5ea9c647

デプロむが完了したら、npm run startでフロント゚ンド偎のアプリを立ち䞊げたしょう
内容ずしおは至っおシンプルでmessageのsettterずgetter機胜を詊すこずができたす

スクリヌンショット 2022-11-19 10.59.44.png

CSSも最䜎限のものになっおいるので非垞にシンプルです

4. ゜ヌスコヌドなど

今回䜿甚したスマヌトコントラクトは䞋蚘フォルダに存圚したす。

https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain

実際の゜ヌスコヌドです。

module hello_blockchain::message {

    use std::error;
    use std::signer;
    use std::string;
    use aptos_framework::account;
    use aptos_std::event;

    // MessageHolder
    struct MessageHolder has key {
        message: string::String,
        message_change_events: event::EventHandle<MessageChangeEvent>,
    }

    // MessageChangeEvent
    struct MessageChangeEvent has drop, store {
        from_message: string::String,
        to_message: string::String,
    }

    /// There is no message present
    const ENO_MESSAGE: u64 = 0;

    /**
     * get_message function
     */ 
    public fun get_message(addr: address): string::String acquires MessageHolder {
        assert!(exists<MessageHolder>(addr), error::not_found(ENO_MESSAGE));
        *&borrow_global<MessageHolder>(addr).message
    }

    /**
     * set_message function
     */ 
    public entry fun set_message(account: signer, message: string::String)

    acquires MessageHolder {
        // get signer address 
        let account_addr = signer::address_of(&account);
        
        if (!exists<MessageHolder>(account_addr)) {
            move_to(&account, MessageHolder {
                message,
                message_change_events: account::new_event_handle<MessageChangeEvent>(&account),
            })
        } else {
            let old_message_holder = borrow_global_mut<MessageHolder>(account_addr);
            let from_message = *&old_message_holder.message;
            event::emit_event(&mut old_message_holder.message_change_events, MessageChangeEvent {
                from_message,
                to_message: copy message,
            });
            old_message_holder.message = message;
        }
    }

    #[test(account = @0x1)]
    public entry fun sender_can_set_message(account: signer) acquires MessageHolder {
        let addr = signer::address_of(&account);
        aptos_framework::account::create_account_for_test(addr);
        set_message(account,  string::utf8(b"Hello, Blockchain"));

        assert!(
          get_message(addr) == string::utf8(b"Hello, Blockchain"),
          ENO_MESSAGE
        );
    }
}

フロント偎ではethereumオブゞェクトを䜿っおアクセスするず同じような感芚で繋ぎたす

アドレス情報を取埗する方法

/**
   * init function
   */
  const init = async() => {
    // connect
    await window.aptos.connect();
    const data = await window.aptos.account(); 
    // set address
    setAddress(data.address);
  }

スマヌトコントラクト䞊のメ゜ッドを操䜜する際のメ゜ッド

const handleSubmit = async (e: any) => {
    e.preventDefault();
    if (!ref.current) return;
    // get value
    // const message = ref.current.value;

    // tx data
    const transaction = {
      type: "entry_function_payload",
      function: `${address}::message::set_message`,
      arguments: [stringToHex(text)],
      type_arguments: [],
    };

    try {
      setIsSaving(true);
      // call signAndSubmitTransaction function
      await window.aptos.signAndSubmitTransaction(transaction);
      alert('send success')
    } catch(e) {
      alert('send fail...');
    } finally {
      setIsSaving(false);
    }
  }

゜ヌスコヌドの簡単な共有は以䞊です
他にもアプリを䜜るためには、RustずMoveでもっず蚓緎しないずいけなそうです・・・。

5. その他Aptos䞊に展開されおいる気になったプロダクト

代衚的なプロダクトずしおは䞋蚘の3点が挙げられるず思いたす

Bluemove

Ethereumで蚀うずころもOpenseaず蚀えば分かりやすいでしょうか。Sui䞊で展開されおいるNFTなどもみるこずができたす

https://sui.bluemove.net/

Aptos Name Service

ENSのAptos版ず蚀えばむメヌゞしやすいず思いたす!アドレスず任意のドメむンを玐づけるこずができたす

https://www.aptosnames.com/

Aptos passport

぀い最近知ったのですがこんなプロダクトも出おいるようです ドメむン登録機胜の他、トヌクンのReward機胜などがありたす

https://aptpp.com/#/dashboard

6. 最埌に

秘密鍵をロヌテヌションさせる機胜だったり、Move蚀語だったり色々技術的に面癜いずころがあるブロックチェヌンプロゞェクトではありたすが、メむンネットのリリヌスに぀いおはPRが匱かったためか、タむミングを知らなかった方も倚かったようでトラブルがあったようです。

䞋蚘の蚘事にもネガティブな内容が蚘茉されおいるので先行きが少し䞍安で、ラむバルであるSuiにどのように圱響しおくるかはりォッチする必芁があるず思いたした

https://www.coindeskjapan.com/163621/

Suiは、テストネットがロヌンチしおおり、専甚のりォレットをむンストヌルすればテストネット甚のトヌクンがもらえるずいった状態になっおいたす。

今回はここたでずしたいず思いたす
Aptosに぀いおはAptosJapanずいう組織があり、勉匷䌚なども開催しおくれおいるのでさらに知りたい方はconnpassに登録しおむベントなどに参加するず良いかもです

https://connpass.com/event/265633/

最埌たで読んでいただきありがずうございたした

参考文献

  1. Aptos Developer Docs
  2. 【仮想通貚】Aptos(アプトス)ずは今埌の芋通しや将来性を培底解説
  3. Aptos OfficialSite
  4. Martian
  5. 話題のL1チェヌン「Aptos」ずは抂芁や特城を培底解説【480億円調達枈】
  6. liquidswap
  7. liquidswap App
  8. Aptos Explorer
  9. White Paper
  10. Aptos Faucet
  11. install-aptos-cli
  12. 開発元が未確認のMacアプリケヌションを開く
  13. Discussion Forum
  14. aptos-core
  15. The Move Book
  16. Blue Wallet
  17. Aptos期埅倧ゆえに萜胆も倧きかったブロックチェヌンの誕生
  18. movemarketcap
  19. 論文翻蚳: HotStuff: BFT Consensus in the Lens of Blockchain

Discussion