# Aptos+Move->快速上手指南

By [lolieatapple](https://paragraph.com/@lolieatapple-2) · 2022-08-31

---

1\. 概述
------

APTOS公链可视为Meta（原Facebook）Libra（后更名Diem）计划搁浅后的续篇。

今年1月底，Diem被Meta出售，一些核心成员出走，组建团队基于Diem的开源代码进行Aptos公链的开发。

其号称在理想状态下，每秒可处理16万笔交易。

链节点使用Rust语言开发。

链上智能合约的开发语言是Move，是 [Diem 项目](https://diem.com/) 专门为区块链开发的一种安全可靠的智能合约编程语言，其语法与Rust语言类似。

2\. 常用链接
--------

*   官网：[https://aptoslabs.com/](https://aptoslabs.com/)
    
*   devnet浏览器：[https://explorer.devnet.aptos.dev/](https://explorer.devnet.aptos.dev/)
    

​ (浏览器搜索功能不好使，可以直接使用URL：[https://explorer.devnet.aptos.dev/account/0x3e8bc3b97d4710c3cf1e89890756f7872ead687dbd4d9d4f9118b885bf81c5da](https://explorer.devnet.aptos.dev/account/0x3e8bc3b97d4710c3cf1e89890756f7872ead687dbd4d9d4f9118b885bf81c5da) )

*   TypeScript SDK: [https://www.npmjs.com/package/aptos](https://www.npmjs.com/package/aptos)
    
*   MOVE语言教程：[https://diem.github.io/move/introduction.html](https://diem.github.io/move/introduction.html)
    
*   MOVE语言教程（中文、不完整）：[https://move-book.com/cn/index.html](https://move-book.com/cn/index.html)
    
*   Move语言白皮书：[https://diem-developers-components.netlify.app/papers/diem-move-a-language-with-programmable-resources/2020-05-26.pdf](https://diem-developers-components.netlify.app/papers/diem-move-a-language-with-programmable-resources/2020-05-26.pdf)
    
*   devnet API\_URL: [https://fullnode.devnet.aptoslabs.com/v1](https://fullnode.devnet.aptoslabs.com/v1) (在SDK中使用)
    
*   devnet FAUCET: [https://faucet.devnet.aptoslabs.com](https://faucet.devnet.aptoslabs.com) (在SDK中使用，最多申请1000000)
    
*   testnet FAUCET: (浏览器打开)
    
    [https://aptoslabs.com/testnet-faucet](https://aptoslabs.com/testnet-faucet)
    
*   官方插件钱包：[Petra (Aptos Wallet) Extension v0.1.6](https://github.com/aptos-labs/aptos-core/releases/tag/wallet-v0.1.6)
    
*   Ed25519签名算法：[https://blog.csdn.net/sinat\_34070003/article/details/79462557](https://blog.csdn.net/sinat_34070003/article/details/79462557)
    
    (波卡、Cardano、NEAR 和 Solana 等区块链也在使用Ed25519签名算法：[https://blog.csdn.net/mutourend/article/details/121264777](https://blog.csdn.net/mutourend/article/details/121264777))
    
    (从Aptos标准库代码上看，它支持3种曲线签名算法: ed25519, secp256k1, bls12381，但发送交易时默认是使用ed25519)
    
*   testnet faucet
    
    [https://aptoslabs.com/testnet-faucet](https://aptoslabs.com/testnet-faucet)
    

3\. 基础操作
--------

（ 基于ts sdk，示例代码：[https://github.com/aptos-labs/aptos-core/blob/main/developer-docs-site/static/examples/typescript/first\_transaction.ts](https://github.com/aptos-labs/aptos-core/blob/main/developer-docs-site/static/examples/typescript/first_transaction.ts) ）

**1）账户创建与测试币申请**

从代码中看APTOS的decimals是8位，但目前在钱包中并没有使用小数，全部按wei在显示和使用，包括钱包转账，gas fee，浏览器展示等。

每个账户地址都关联了一个Authentication Key。这个Authentication Key实际上是钱包私钥的公钥。

但账户可以通过rotate的接口来更新这个Authentication Key，从而达到地址不变，但控制私钥变更的目的。

可以使用浏览器插件钱包创建地址和申请测试币。

也可以使用sdk+js脚本，脚本如下：

    export const NODE_URL = "https://fullnode.devnet.aptoslabs.com";
    export const FAUCET_URL = "https://faucet.devnet.aptoslabs.com";
    
    /** AptosAccount provides methods around addresses, key-pairs */
    import { AptosAccount, TxnBuilderTypes, BCS, MaybeHexString } from "aptos";
    /** Wrappers around the Aptos Node and Faucet API */
    import { AptosClient, FaucetClient } from "aptos";
    
    const client = new AptosClient(NODE_URL);
    const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
    
    // 创建钱包账户
    const alice = new AptosAccount();
    const bob = new AptosAccount();
    console.log(`Alice: ${alice.address()} Key Seed: ${Buffer.from(alice.signingKey.secretKey).toString("hex")}`);
    console.log(`Bob: ${bob.address()} Key Seed: ${Buffer.from(bob.signingKey.secretKey).toString("hex")}`);
    
    // 申请Faucet测试币，最多1_000_000
    await faucetClient.fundAccount(alice.address(), 5_000);
    await faucetClient.fundAccount(bob.address(), 0);
    

测试币申请完成后，可以在浏览器中查看余额。

**2）账户余额查询**

因为APTOS账户余额以状态数据的形式存储在用户地址中，因此需要对数据进行解析。

    export async function accountBalance(accountAddress: MaybeHexString): Promise<number | null> {
      const resource = await client.getAccountResource(accountAddress, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>");
      if (resource == null) {
        return null;
      }
    
      return parseInt((resource.data as any)["coin"]["value"]);
    }
    

在浏览器中查看地址信息的时候，也是JSON样式的状态数据：

!\\image-20220817182221463\\

**3）发送转账交易**

BCS是一种序列化的格式：[Binary Canonical Serialization (BCS)](https://docs.rs/bcs/latest/bcs/#binary-canonical-serialization-bcs)

APTOS默认使用BCS格式发送交易。

APTOS使用使用块高度的同时，还使用区块链状态数据库的VERSION版本号。我理解每一笔导致状态变化的交易就会得到一个新的version，而一个块高度内可以包含多个version。（所以version可以理解为总交易序号？）

使用交易中的执行脚本实现用户账户状态数据的变更。

\*注意：代码中的TransactionPayloadScriptFunction已经变更，请参考最新的example代码。

    /**
     * Transfers a given coin amount from a given accountFrom to the recipient's account address.
     * Returns the transaction hash of the transaction used to transfer.
     */
    async function transfer(accountFrom: AptosAccount, recipient: MaybeHexString, amount: number): Promise<string> {
        // 获取token信息，这里是原生币0x1::aptos_coin::AptosCoin
      const token = new TxnBuilderTypes.TypeTagStruct(TxnBuilderTypes.StructTag.fromString("0x1::aptos_coin::AptosCoin"));
    
      // 构建转账脚本
      const scriptFunctionPayload = new TxnBuilderTypes.TransactionPayloadScriptFunction(
        TxnBuilderTypes.ScriptFunction.natural(
          "0x1::coin",
          "transfer",
          [token],
          [BCS.bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(recipient)), BCS.bcsSerializeUint64(amount)],
        ),
      );
    
      // 获取nonce和chainId
      const [{ sequence_number: sequenceNumber }, chainId] = await Promise.all([
        client.getAccount(accountFrom.address()),
        client.getChainId(),
      ]);
    
      // 生成rawTransaction, 填写gasPrice, maxGas和超时信息
      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),
      );
    
      // 生成BCS格式的交易
      const bcsTxn = AptosClient.generateBCSTransaction(accountFrom, rawTxn);
      // 广播BCS格式的交易
      const pendingTxn = await client.submitSignedBCSTransaction(bcsTxn);
    
      return pendingTxn.hash;
    }
    

**4）使用DApp网页钱包发送普通交易**

普通交易转账实际上是对预置合约的调用。

    const transaction = {
      type: "entry_function_payload",
      function: `0x1::coin::transfer`,
      arguments: ['0x0aa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471', '200'],
      type_arguments: [`0x1::aptos_coin::AptosCoin`],
    };
    
    console.log('transaction', transaction);
    let ret = await window.aptos.signAndSubmitTransaction(transaction);
    

*   type: "entry\_function\_payload" 交易类型：带参数的合约调用。
    
*   function: `0x1::coin::transfer`调用接口：0x1是合约地址，coin是合约名，transfer是合约方法。
    
*   type\_arguments: \[`0x1::aptos_coin::AptosCoin`\] 模板类型。（不同的模板类型会使操作作用在本地的不同状态数据）
    
    *   arguments: \['0x0aa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471', '200'\],合约参数。
        
*   目前插件钱包只支持以下6个接口函数：
    
          CONNECT: 'connect',
          DISCONNECT: 'disconnect',
          GET_ACCOUNT_ADDRESS: 'getAccountAddress',
          IS_CONNECTED: 'is_connected',
          SIGN_AND_SUBMIT_TRANSACTION: 'signAndSubmitTransaction',
          SIGN_TRANSACTION: 'signTransaction',
        
    
    根据社区用户lyn建议，使用下面这个转账接口，才会自动注册账号，上面的不会自动注册。
    
        "payload": {
                "function": "0x1::aptos_account::transfer",
                "type_arguments": [],
                "arguments": [
                    "0x6f755f59b2dac9ee2b7b03f6332fd6bb0721b4d99cb385685f7e4adffc359b3c",
                    "333"
                ],
                "type": "entry_function_payload"
            }
        
    
    4\. 智能合约Move Module
    -------------------
    
    Aptos上的智能合约称做Move Module，合约发布后有一个合约地址，这个合约地址就是发布人的账户地址。
    
    编译合约需要用的命令行工具下载地址：[https://github.com/aptos-labs/aptos-core/releases/](https://github.com/aptos-labs/aptos-core/releases/)
    
    （使用代码直接编译此工具会报错，建议直接下载编译好的二进制文件）
    
    * * *
    
    \*注意，Aptos接口变化极快，script\_function\_payload类型已经不可用，变成了entry\_function\_payload
    
    当前支持3种交易类型：
    
    *   entry\_function\_payload：调用module接口的交易
        
    *   script\_payload：脚本执行交易
        
    *   module\_bundle\_payload：module部署
        
    
    随时检查API手册，观察接口变化：[https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/encode\_submission](https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/encode_submission)
    
    其中entry\_function类似于一种内置在module中的脚本，在函数定义时使用public entry fun xxxx() { }.
    
    scritp是链下编写，编译成bytecode后，随交易一起执行的脚本。
    
    module是链上的合约。
    
    * * *
    
    ### 1）命令行工具初始化
    
    对命令行工具初始化后，即可使用它来部署合约。首先用插件钱包创建一个地址，导出私钥后，执行如下初始化指令：
    
        $ aptos init
        # 按照提示，选择网络（可默认），然后输入导出的私钥
        
    
    然后使用如下指令，即可查看地址的状态数据：
    
        $ aptos account list
        
    
    ### 2）插件钱包注入对象
    
    为方便web3开发，插件钱包启用后，会在浏览器内注入一个**window.aptos**对象，在DApp中使用这个对象可以与插件钱包交互。
    
    例如获取地址信息：
    
        await window.aptos.connect()
        await window.aptos.account()
        
    
    ### 3）部署示例合约
    
    下载示例合约代码： [the](https://github.com/aptos-labs/aptos-core/tree/main/aptos-move/move-examples/hello_blockchain) `hello_blockchain` package.
    
    使用命令行发布合约代码，填写账户地址在发布指令中:
    
        $ aptos move publish --package-dir /path/to/hello_blockchain/ --named-addresses HelloBlockchain=<address>
        
    
    （这里的路径需要写绝对路径，相对路径好像不支持）
    
    其中的\*\*--named-addresses\*\*表示命名替换，将module中的
    
        module HelloBlockchain::Message {
        
    
    替换成
    
        module 0x5af503b5c379bd69f46184304975e1ef1fa57f422dd193cdad67dc139d532481::Message {
        
    
    在状态数据存储中，只能看到这个地址，看不到之前的Module名字。通过这种方式，将合约发布到账户地址上。
    
    在浏览器上查看此地址，即可在account modules下方看到此合约的bytecode和开放的ABI接口信息，包括名称、函数、数据结构等。
    
    ### 4）使用web插件钱包调用合约写数据
    
    web钱包需要先connect，然后才能使用。
    
        const address = '0xaa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471';
        const message = 'Hello Wanchain';
        const transaction = {
            type: "entry_function_payload",
            function: `${address}::message::set_message`,
            arguments: [stringToHex(message)],
            type_arguments: [],
        };
        await window.aptos.connect();
        let ret = await window.aptos.signAndSubmitTransaction(transaction);
        console.log('ret', ret);
        
    
    其中address是部署合约的account地址，同时也是合约地址。
    
    可以更换不同的地址来调用此合约，可以在浏览器中看到，修改的是每个account自己的状态数据。
    
    示例代码中的set\_message函数需要两个参数，函数声明如下：
    
        public(script) fun set_message(account: signer, message_bytes: vector<u8>)
        
    
    第一个参数signer由钱包自动提供。用户只需要提供第二个参数即可。
    
    ### 5) 读取合约数据
    
    使用getAccountResources接口读取全部的状态数据信息，从中挑选需要读取的项。
    
        const client = new AptosClient('https://fullnode.devnet.aptoslabs.com/v1');
        console.log('account', await client.getAccount('0xaa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471'));
        console.log('modules', await client.getAccountModules('0xaa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471'));
        console.log('module', await client.getAccountModule('0xaa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471', 'message'));
        console.log('resources', await client.getAccountResources('0xaa630f7e14a5bc07d822f35007a5e316cee9f1b359e4bec3a13a0721ff5f471'));
        
    
    ### 6) 创建一个代币(coin)
    
    **1\. 部署合约**
    
    在Aptos连的标准框架中，有一个专用的代币合约：**0x1::coin**，使用它可以创建一个标准代币。
    
    （此外，标准框架中**0x1::managed\_coin**是带有mint/burn方法的标准代币）
    
    （在浏览器中查看0x1地址的详细信息，可以看到所有的标准框架合约信息：[https://explorer.devnet.aptos.dev/account/0x1）](https://explorer.devnet.aptos.dev/account/0x1%EF%BC%89)
    
    自定义代币的合约代码如下：(示例代码: aptos-move/move-examples/moon\_coin)
    
        module MoonCoinType::moon_coin {
            struct MoonCoin {}
        }
        
    
    将这个数据注册到标准代币合约**0x1::coin**中，即可得到一个自定义代币。
    
    首先使用第3）节的指令部署合约：
    
        $ aptos init
        $ aptos move publish --package-dir XXXX --named-addresses MoonCoinType=0xXXXXX
        
    
    每个不同的代码目录，都需要重新init一次，init的信息回保存到当前目录下的\*\*.aptos/config.yaml\*\*文件中，用完记得删除以保证私钥安全。
    
    **2\. 初始化代币**
    
    接下来使用预置合约**0x1::managed\_coin**对我们部署的合约进行初始化**_initialize_**。
    
    managed\_coin比coin多了mint、burn接口。
    
    初始化时，填写代币的name, symbol, decimals, monitor\_supply等4个信息。
    
    我理解的初始化，是使用0x1::managed\_coin合约的initialize方法对本地状态数据进行配置和变更。
    
    使用Web钱包对自定义代币初始化代码如下：
    
        const transaction = {
          type: "entry_function_payload",
          function: `0x1::managed_coin::initialize`,
          arguments: [stringToHex('Moon Coin'), stringToHex('MOON'), 6, false],
          type_arguments: [`${address}::moon_coin::MoonCoin`],
        };
        
        let ret = await window.aptos.signAndSubmitTransaction(transaction);
        
    
    初始化完成后，可以在浏览器中看到，自定义的代币decimals, name, symbol信息已经有了：
    
        {
          "decimals": 6,
          "name": "Moon Coin",
          "supply": {
            "vec": []
          },
          "symbol": "MOON"
        }
        
    
    在初始化后，调用初始化的地址自动成为代币合约的owner。
    
    **3\. 注册接收人**
    
    在Aptos链，一个用户想要接收除APTOS默认币以外的代币时，必须先由接受者主动明确的注册register接收这个代币，然后才能接收。（不能随意空投代币）
    
    接受者通过调用预置合约的此接口来接收预置代币：
    
        0x1::coins::register<CoinType>:
        
    
    注册代码:
    
        const transaction = {
          type: "entry_function_payload",
          function: `0x1::coins::register`,
          arguments: [],
          type_arguments: [`${address}::moon_coin::MoonCoin`],
        };
        
        let ret = await window.aptos.signAndSubmitTransaction(transaction);
        
    
    注册完成之后，可以在浏览器上看到，在地址的0x1::coin::CoinStore下面多了一个自定义代币类型的数据段：
    
        0x1::coin::CoinStore<0xb2e77a8f90524cf30f6f6540530b67a66b7b5fb511ec0d0319668aa9bd3a106d::moon_coin::MoonCoin>
        ...
        
    
    这个字段与原生币类似，只是尖括号内的类型不同，其它的数据字段和事件完全相同。原生币：
    
        0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>
        
    
    **4.铸造代币**
    
    铸币代码：
    
        const transaction = {
          type: "entry_function_payload",
          function: `0x1::managed_coin::mint`,
          arguments: ['0xb2e77a8f90524cf30f6f6540530b67a66b7b5fb511ec0d0319668aa9bd3a106d', '1000000'],
          type_arguments: [`${address}::moon_coin::MoonCoin`],
        };
        
        let ret = await window.aptos.signAndSubmitTransaction(transaction);
        
    
    **5\. 代币转账**
    
    自定义转账的方法与原生币基本相同。代码如下：（接收地址需要先注册才能接收，否则交易会上链但链上执行失败）
    
        const transaction = {
          type: "entry_function_payload",
          function: `0x1::coin::transfer`,
          arguments: ['0x8ded6d8821f7e9a4ea9ecdd953cabf29f263866a0e50a0d66cc0561ef5a99db6', '1000'],
          type_arguments: [`${address}::moon_coin::MoonCoin`],
        };
        
        let ret = await window.aptos.signAndSubmitTransaction(transaction);
        
    
    5\. 其它
    ------
    
    **rotate：**
    
    APTOS的地址可以更换auth key，实现在账户地址不变的前提下更换(rotate)签名私钥。可以通过这种方式迁移或释放地址权限。
    
    **地址类型：**
    
    地址类型有两种，一种是普通地址，另一种是资源地址resource account，可以由合约或脚本创建，不与创建者地址相同。
    
    **状态访问接口：**
    
    内置的访问状态数据的函数一共有5个：
    
    move\_to: 创建资源
    
    move\_from: 删除资源
    
    borrow\_global: 获取只读引用
    
    borrow\_global\_mut: 获取可写入引用
    
    exists: 判断资源是否存在
    
    这四个函数只能访问本module内的结构体数据，但可以通过封装公开的函数接口，提供外部访问本地状态数据的权限。
    
    !\\image-20220830163511359\\
    
    **宏定义函数：**
    
    函数后面带!符号的是宏定义函数，例如assert!()
    
    **函数访问权限：**
    
    Module中，函数访问权限有3种，一种是公开public，一种是不带public，默认只有module内部可以访问，最后一种是public(friend) 只有定义成friend的module有权限访问。因此，原生APT coin转账不需要接收人主动注册(register)，因为原生APT coin的module有friend权限，可以自动帮接收地址注册(register)。
    
    **合约升级：**
    
    有权限的地址，可以对部署好的module升级。有3种升级策略：强制(arbitrary)，兼容(compatible)，不可升级(immutable)。通过在Move.toml文件中可以配置，默认是兼容模式。
    
        [package]
        name = "MyApp"
        version = "0.0.1"
        upgrade_policy = "compatible"
        ...
        
    
    [https://aptos.dev/guides/move-guides/upgrading-move-code/](https://aptos.dev/guides/move-guides/upgrading-move-code/)
    
    部署为兼容模式的module，可以更改为不可升级，反之则不可。
    
    兼容模式要求所有旧的全局都不可更改，所有旧的公开api接口定义不可变更。
    
    **钱包签名：**
    
    module函数入口参数的第一个&signer类型参数可省略不填，SDK会自动填写当前钱包。
    
    **合约event发送：**
    
    event的创建需要首先使用 `account::new_event_handle<T>(sender) 接口来创建event handler，然后使用` `event::emit_event<T>()`接口来发送event信息。
    
    在查询时，有2种形式，一种是通过handler查询，一种是通过guid来查询。
    
    使用handler查询时，需要输入地址，主存储结构体名称，event handler所属字段名称。
    
    使用guid查询时，使用 creation\_num + account的形式，creation\_num是event种的数字，以小端模式uint64数字存储。例如create\_num是4时：0x0400000000000000166e192a37e1b17dfdb201a2bc8c4cbc25a6e24c5059c05b8f5188fd9ccf4c76
    
    查询条件的start是event的序号，每个event序号从0开始递增。
    
        /// Event emitted when some amount of a coin is deposited into an account.
        struct DepositEvent has drop, store {
            amount: u64,
        }
        ...
        let handler = account::new_event_handle<T>(sender)
        ...
        
        event::emit_event<T>(
            &mut handler,
            DepositEvent { amount: coin.value },
        );
        
    
    **地址校验**：
    
    正则表达式："^(0x)\[0-9A-Fa-f\]{64}$"
    
    **常用cli指令：**
    
    aptos move init 初始化工程目录（Move.toml文件中AptosFramework的rev设置为devnet，并运行clean）
    
    aptos move clean 清理缓存（更最新新标准库）
    
    aptos move compile 编译
    
    aptos move publish 发布module
    
    aptos move run 发交易调用module的entry函数
    
    编译命令：
    
        $ aptos move compile --package-dir . --named-addresses HelloBlockchain=0x{alice_address_here}
        
    
    这里编译的示例Move代码中的HelloBlockchain：aptos-core/aptos-move/move-examples/hello\_blockchain
    
    这里的--named-addresses后面的地址，是提前生成好的钱包地址。
    
    使用这个地址来部署这个合约。同时，这个合约地址也是部署者的地址。
    
    其它用户地址访问这个合约时，修改的是自己地址下的状态数据。
    
    HelloBlockchain示例代码可以为调用者地址增加一个MessageHolder字段，并存储设置的message信息。
    
    调用成功后，在浏览器中可以查看调用者地址状态数据时，即可看到新增的数据字段：
    
    !\\image-20220818114723563\\
    
    使用aptos move run 指令执行时，args类型支持：\[u8, u64, u128, bool, hex, string, address, raw\]。
    
    查询event时，可以使用序号或guid，使用序号的方式为：
    
        curl --request GET \
          --url https://fullnode.devnet.aptoslabs.com/v1/accounts/address/events/creation_number \
          --header 'Content-Type: application/json'

---

*Originally published on [lolieatapple](https://paragraph.com/@lolieatapple-2/aptos-move)*
