搭建本地测试网

学习材料

https://aptos.dev/zh/build/cli/running-a-local-network

下载及编译

# 下载源码
$ git@github.com:aptos-labs/aptos-core.git

# 安装依赖
$ ./dev_setup.sh  
  
# 编译
$ cargo build --release

# 查看版本
$ ./target/release/aptos -V
aptos 4.1.0

启动本地网络

$ ./target/release/aptos node run-local-testnet --with-indexer-api

Image postgres:14.11 not found, pulling it now...
Image hasura/graphql-engine:v2.40.2-ce not found, pulling it now...

Readiness endpoint: http://127.0.0.1:8070/

Indexer API is starting, please wait...
Node API is starting, please wait...
Faucet is starting, please wait...
Transaction stream is starting, please wait...
Postgres is starting, please wait...

Completed generating configuration:
        Log file: "/root/.aptos/testnet/validator.log"
        Test dir: "/root/.aptos/testnet"
        Aptos root key path: "/root/.aptos/testnet/mint.key"
        Waypoint: 0:2c7523c0f215144d249ac1d115cf583060f02309d54bf6eaf36b391579f2804d
        ChainId: 4
        REST API endpoint: http://127.0.0.1:8080
        Metrics endpoint: http://127.0.0.1:9101/metrics
        Aptosnet fullnode network endpoint: /ip4/0.0.0.0/tcp/6181
        Indexer gRPC node stream endpoint: 127.0.0.1:50051

Aptos is running, press ctrl-c to exit

Node API is ready. Endpoint: http://127.0.0.1:8080/
Postgres is ready. Endpoint: postgres://postgres@127.0.0.1:5433/local_testnet
Transaction stream is ready. Endpoint: http://127.0.0.1:50051/
Faucet is ready. Endpoint: http://127.0.0.1:8081/
Indexer API is ready. Endpoint: http://127.0.0.1:8090/

Applying post startup steps...

Setup is complete, you can now use the localnet!

image-20240907174826196

运行服务

当本地网络启动成功后,将会启动以下服务:

服务描述端点
节点API直接在节点上运行的 REST API。它支持核心写功能,如交易提交,以及有限的读功能,如读取帐户资源或 Move 模块信息http://127.0.0.1:8080/
索引器API提供对索引区块链数据丰富读取访问的 GraphQL API。通过点击 URL,可以访问 Hasura 控制台,一个帮助您查询索引器 GraphQL API 的 Web UIhttp://127.0.0.1:8090/
交易流服务索引器 API 使用的 gRPC 交易流http://127.0.0.1:50051/
Postgres索引器处理器写入的数据库。索引器 API 从此数据库读取。postgres://postgres@127.0.0.1:5433/local_testnet
水龙头可用于在本地网络中为帐户提供资金的服务http://127.0.0.1:8081/

重置本地网络

当需要将本地网络重置回初始状态时执行该命令,以应对:

  • 对Move模块进行了不能向后兼容的更改
  • 正在构建一个自定义索引处理器,希望使用一个全新的网络进行索引
  • 想清除所有链上状态,例如账户、对象等
$ ./target/release/aptos node run-local-testnet --force-restart
Are you sure you want to delete the existing localnet data? [yes/no] >
yes

Readiness endpoint: http://127.0.0.1:8070/

Node API is starting, please wait...
Transaction stream is starting, please wait...
Faucet is starting, please wait...

Completed generating configuration:
        Log file: "/root/.aptos/testnet/validator.log"
        Test dir: "/root/.aptos/testnet"
        Aptos root key path: "/root/.aptos/testnet/mint.key"
        Waypoint: 0:0cb18010ecdfabbfb2e4d0faa91f49e5e1dcb6ef5d106e8ad86032fd0eec7b73
        ChainId: 4
        REST API endpoint: http://127.0.0.1:8080
        Metrics endpoint: http://127.0.0.1:9101/metrics
        Aptosnet fullnode network endpoint: /ip4/0.0.0.0/tcp/6181
        Indexer gRPC node stream endpoint: 127.0.0.1:50051

Aptos is running, press ctrl-c to exit

Node API is ready. Endpoint: http://127.0.0.1:8080/
Transaction stream is ready. Endpoint: http://127.0.0.1:50051/
Faucet is ready. Endpoint: http://127.0.0.1:8081/

Applying post startup steps...

Setup is complete, you can now use the localnet!

image-20240907180055305

API文档

http://127.0.0.1:8080/v1/spec#/operations/get_ledger_info

image-20240907183055085

开发第一个Aptos Move合约:Todo list

工程创建

$ mkdir aptos_todo_list && cd aptos_todo_list

$ aptos move init --name  aptos_todo_list
{
  "Result": "Success"
}

合约开发

代码来自:https://learn.aptoslabs.com/zh/code-examples/todo-list

module todo_list_addr::todo_list {
    use std::bcs;
    use std::signer;
    use std::vector;
    use std::string::String;
    use aptos_std::string_utils;
    use aptos_framework::object;

    /// Todo list does not exist
    const E_TODO_LIST_DOSE_NOT_EXIST: u64 = 1;
    /// Todo does not exist
    const E_TODO_DOSE_NOT_EXIST: u64 = 2;
    /// Todo is already completed
    const E_TODO_ALREADY_COMPLETED: u64 = 3;

    struct UserTodoListCounter has key {
        counter: u64,
    }

    struct TodoList has key {
        owner: address,
        todos: vector<Todo>,
    }

    struct Todo has store, drop, copy {
        content: String,
        completed: bool,
    }

    // This function is only called once when the module is published for the first time.
    // init_module is optional, you can also have an entry function as the initializer.
    fun init_module(_module_publisher: &signer) {
        // nothing to do here
    }

    // ======================== Write functions ========================

    public entry fun create_todo_list(sender: &signer) acquires UserTodoListCounter {
        let sender_address = signer::address_of(sender);
        let counter = if (exists<UserTodoListCounter>(sender_address)) {
            let counter = borrow_global<UserTodoListCounter>(sender_address);
            counter.counter
        } else {
            let counter = UserTodoListCounter { counter: 0 };
            // store the UserTodoListCounter resource directly under the sender
            move_to(sender, counter);
            0
        };
        // create a new object to hold the todo list, use the contract_addr_counter as seed
        let obj_holds_todo_list = object::create_named_object(
            sender,
            construct_todo_list_object_seed(counter),
        );
        let obj_signer = object::generate_signer(&obj_holds_todo_list);
        let todo_list = TodoList {
            owner: sender_address,
            todos: vector::empty(),
        };
        // store the TodoList resource under the newly created object
        move_to(&obj_signer, todo_list);
        // increment the counter
        let counter = borrow_global_mut<UserTodoListCounter>(sender_address);
        counter.counter = counter.counter + 1;
    }

    public entry fun create_todo(sender: &signer, todo_list_idx: u64, content: String) acquires TodoList {
        let sender_address = signer::address_of(sender);
        let todo_list_obj_addr = object::create_object_address(
            &sender_address,
            construct_todo_list_object_seed(todo_list_idx)
        );
        assert_user_has_todo_list(todo_list_obj_addr);
        let todo_list = borrow_global_mut<TodoList>(todo_list_obj_addr);
        let new_todo = Todo {
            content,
            completed: false
        };
        vector::push_back(&mut todo_list.todos, new_todo);
    }

    public entry fun complete_todo(sender: &signer, todo_list_idx: u64, todo_idx: u64) acquires TodoList {
        let sender_address = signer::address_of(sender);
        let todo_list_obj_addr = object::create_object_address(
            &sender_address,
            construct_todo_list_object_seed(todo_list_idx)
        );
        assert_user_has_todo_list(todo_list_obj_addr);
        let todo_list = borrow_global_mut<TodoList>(todo_list_obj_addr);
        assert_user_has_given_todo(todo_list, todo_idx);
        let todo_record = vector::borrow_mut(&mut todo_list.todos, todo_idx);
        assert!(todo_record.completed == false, E_TODO_ALREADY_COMPLETED);
        todo_record.completed = true;
    }

    // ======================== Read Functions ========================

    // Get how many todo lists the sender has, return 0 if the sender has none.
    #[view]
    public fun get_todo_list_counter(sender: address): u64 acquires UserTodoListCounter {
        if (exists<UserTodoListCounter>(sender)) {
            let counter = borrow_global<UserTodoListCounter>(sender);
            counter.counter
        } else {
            0
        }
    }

    #[view]
    public fun get_todo_list_obj_addr(sender: address, todo_list_idx: u64): address {
        object::create_object_address(&sender, construct_todo_list_object_seed(todo_list_idx))
    }

    #[view]
    public fun has_todo_list(sender: address, todo_list_idx: u64): bool {
        let todo_list_obj_addr = get_todo_list_obj_addr(sender, todo_list_idx);
        exists<TodoList>(todo_list_obj_addr)
    }

    #[view]
    public fun get_todo_list(sender: address, todo_list_idx: u64): (address, u64) acquires TodoList {
        let todo_list_obj_addr = get_todo_list_obj_addr(sender, todo_list_idx);
        assert_user_has_todo_list(todo_list_obj_addr);
        let todo_list = borrow_global<TodoList>(todo_list_obj_addr);
        (todo_list.owner, vector::length(&todo_list.todos))
    }

    #[view]
    public fun get_todo_list_by_todo_list_obj_addr(todo_list_obj_addr: address): (address, u64) acquires TodoList {
        let todo_list = borrow_global<TodoList>(todo_list_obj_addr);
        (todo_list.owner, vector::length(&todo_list.todos))
    }

    #[view]
    public fun get_todo(sender: address, todo_list_idx: u64, todo_idx: u64): (String, bool) acquires TodoList {
        let todo_list_obj_addr = get_todo_list_obj_addr(sender, todo_list_idx);
        assert_user_has_todo_list(todo_list_obj_addr);
        let todo_list = borrow_global<TodoList>(todo_list_obj_addr);
        assert!(todo_idx < vector::length(&todo_list.todos), E_TODO_DOSE_NOT_EXIST);
        let todo_record = vector::borrow(&todo_list.todos, todo_idx);
        (todo_record.content, todo_record.completed)
    }

    // ======================== Helper Functions ========================

    fun assert_user_has_todo_list(user_addr: address) {
        assert!(
            exists<TodoList>(user_addr),
            E_TODO_LIST_DOSE_NOT_EXIST
        );
    }

    fun assert_user_has_given_todo(todo_list: &TodoList, todo_id: u64) {
        assert!(
            todo_id < vector::length(&todo_list.todos),
            E_TODO_DOSE_NOT_EXIST
        );
    }

    fun get_todo_list_obj(sender: address, todo_list_idx: u64): object::Object<TodoList> {
        let addr = get_todo_list_obj_addr(sender, todo_list_idx);
        object::address_to_object(addr)
    }

    fun construct_todo_list_object_seed(counter: u64): vector<u8> {
        // The seed must be unique per todo list creator
        //Wwe add contract address as part of the seed so seed from 2 todo list contract for same user would be different
        bcs::to_bytes(&string_utils::format2(&b"{}_{}", @todo_list_addr, counter))
    }

    // ======================== Unit Tests ========================

    #[test_only]
    use std::string;
    #[test_only]
    use aptos_framework::account;
    #[test_only]
    use aptos_std::debug;

    #[test(admin = @0x100)]
    public entry fun test_end_to_end(admin: signer) acquires TodoList, UserTodoListCounter {
        let admin_addr = signer::address_of(&admin);
        let todo_list_idx = get_todo_list_counter(admin_addr);
        assert!(todo_list_idx == 0, 1);
        account::create_account_for_test(admin_addr);
        assert!(!has_todo_list(admin_addr, todo_list_idx), 2);
        create_todo_list(&admin);
        assert!(get_todo_list_counter(admin_addr) == 1, 3);
        assert!(has_todo_list(admin_addr, todo_list_idx), 4);

        create_todo(&admin, todo_list_idx, string::utf8(b"New Todo"));
        let (todo_list_owner, todo_list_length) = get_todo_list(admin_addr, todo_list_idx);
        debug::print(&string_utils::format1(&b"todo_list_owner: {}", todo_list_owner));
        debug::print(&string_utils::format1(&b"todo_list_length: {}", todo_list_length));
        assert!(todo_list_owner == admin_addr, 5);
        assert!(todo_list_length == 1, 6);

        let (todo_content, todo_completed) = get_todo(admin_addr, todo_list_idx, 0);
        debug::print(&string_utils::format1(&b"todo_content: {}", todo_content));
        debug::print(&string_utils::format1(&b"todo_completed: {}", todo_completed));
        assert!(!todo_completed, 7);
        assert!(todo_content == string::utf8(b"New Todo"), 8);

        complete_todo(&admin, todo_list_idx, 0);
        let (_todo_content, todo_completed) = get_todo(admin_addr, todo_list_idx, 0);
        debug::print(&string_utils::format1(&b"todo_completed: {}", todo_completed));
        assert!(todo_completed, 9);
    }

    #[test(admin = @0x100)]
    public entry fun test_end_to_end_2_todo_lists(admin: signer) acquires TodoList, UserTodoListCounter {
        let admin_addr = signer::address_of(&admin);
        create_todo_list(&admin);
        let todo_list_1_idx = get_todo_list_counter(admin_addr) - 1;
        create_todo_list(&admin);
        let todo_list_2_idx = get_todo_list_counter(admin_addr) - 1;

        create_todo(&admin, todo_list_1_idx, string::utf8(b"New Todo"));
        let (todo_list_owner, todo_list_length) = get_todo_list(admin_addr, todo_list_1_idx);
        assert!(todo_list_owner == admin_addr, 1);
        assert!(todo_list_length == 1, 2);

        let (todo_content, todo_completed) = get_todo(admin_addr, todo_list_1_idx, 0);
        assert!(!todo_completed, 3);
        assert!(todo_content == string::utf8(b"New Todo"), 4);

        complete_todo(&admin, todo_list_1_idx, 0);
        let (_todo_content, todo_completed) = get_todo(admin_addr, todo_list_1_idx, 0);
        assert!(todo_completed, 5);

        create_todo(&admin, todo_list_2_idx, string::utf8(b"New Todo"));
        let (todo_list_owner, todo_list_length) = get_todo_list(admin_addr, todo_list_2_idx);
        assert!(todo_list_owner == admin_addr, 6);
        assert!(todo_list_length == 1, 7);

        let (todo_content, todo_completed) = get_todo(admin_addr, todo_list_2_idx, 0);
        assert!(!todo_completed, 8);
        assert!(todo_content == string::utf8(b"New Todo"), 9);

        complete_todo(&admin, todo_list_2_idx, 0);
        let (_todo_content, todo_completed) = get_todo(admin_addr, todo_list_2_idx, 0);
        assert!(todo_completed, 10);
    }

    #[test(admin = @0x100)]
    #[expected_failure(abort_code = E_TODO_LIST_DOSE_NOT_EXIST, location = Self)]
    public entry fun test_todo_list_does_not_exist(admin: signer) acquires TodoList, UserTodoListCounter {
        let admin_addr = signer::address_of(&admin);
        account::create_account_for_test(admin_addr);
        let todo_list_idx = get_todo_list_counter(admin_addr);
        // account cannot create todo on a todo list (that does not exist
        create_todo(&admin, todo_list_idx, string::utf8(b"New Todo"));
    }

    #[test(admin = @0x100)]
    #[expected_failure(abort_code = E_TODO_DOSE_NOT_EXIST, location = Self)]
    public entry fun test_todo_does_not_exist(admin: signer) acquires TodoList, UserTodoListCounter {
        let admin_addr = signer::address_of(&admin);
        account::create_account_for_test(admin_addr);
        let todo_list_idx = get_todo_list_counter(admin_addr);
        create_todo_list(&admin);
        // can not complete todo that does not exist
        complete_todo(&admin, todo_list_idx, 1);
    }

    #[test(admin = @0x100)]
    #[expected_failure(abort_code = E_TODO_ALREADY_COMPLETED, location = Self)]
    public entry fun test_todo_already_completed(admin: signer) acquires TodoList, UserTodoListCounter {
        let admin_addr = signer::address_of(&admin);
        account::create_account_for_test(admin_addr);
        let todo_list_idx = get_todo_list_counter(admin_addr);
        create_todo_list(&admin);
        create_todo(&admin, todo_list_idx, string::utf8(b"New Todo"));
        complete_todo(&admin, todo_list_idx, 0);
        // can not complete todo that is already completed
        complete_todo(&admin, todo_list_idx, 0);
    }
}

环境初始化

$ aptos init
Configuring for profile default
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
testnet
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

No key given, generating key...
Account 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022 doesn't exist, creating it and funding it with 100000000 Octas
Account 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022 funded successfully

---
Aptos CLI is now set up for account 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022 as profile default!
 See the account here: https://explorer.aptoslabs.com/account/0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022?network=testnet
 Run `aptos --help` for more information about commands
{
  "Result": "Success"
}

注:生成的地址0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022就是账户地址,别名是default

执行上述命令,将在当前工程目录生成以下文件:

$ cat .aptos/config.yaml 
---
profiles:
  default:
    network: Testnet
    private_key: "0x2a88d0....0b45"
    public_key: "0xd6a31c....27ea6"
    account: cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022
    rest_url: "https://fullnode.testnet.aptoslabs.com"
    faucet_url: "https://faucet.testnet.aptoslabs.com"

领水

$ aptos account fund-with-faucet --account default
# 等价命令
$ aptos account fund-with-faucet --account 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022

{
  "Result": "Added 100000000 Octas to account 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022"
}

合约编译

$ aptos move build
# 等价命令
$ aptos move compile
Compiling, may take a little while to download git dependencies...
UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos_todo_list
{
  "Result": [
    "cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list"
  ]
}

注:如果Move.toml中未定义address,需使用命令:

$ aptos move compile --named-addresses todo_list_addr=default

执行单测

$ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos_todo_list
Running Move unit tests
[debug] "todo_list_owner: @0x100"
[debug] "todo_list_length: 1"
[debug] "todo_content: \"New Todo\""
[debug] "todo_completed: false"
[debug] "todo_completed: true"
[ PASS    ] 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::test_end_to_end
[ PASS    ] 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::test_end_to_end_2_todo_lists
[ PASS    ] 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::test_todo_already_completed
[ PASS    ] 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::test_todo_does_not_exist
[ PASS    ] 0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::test_todo_list_does_not_exist
Test result: OK. Total tests: 5; passed: 5; failed: 0
{
  "Result": "Success"
}

合约部署

$ aptos move publish
Compiling, may take a little while to download git dependencies...
UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING aptos_todo_list
package size 4576 bytes

Do you want to submit a transaction for a range of [274000 - 411000] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x0b7b5b75a81040352741c1342db44a57faea5b8492f2248bab54577b6fd146a3?network=testnet
{
  "Result": {
    "transaction_hash": "0x0b7b5b75a81040352741c1342db44a57faea5b8492f2248bab54577b6fd146a3",
    "gas_used": 2740,
    "gas_unit_price": 100,
    "sender": "cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1725804736732089,
    "version": 5954581859,
    "vm_status": "Executed successfully"
  }
}

合约调用

create_todo_list

$ aptos move run \
  --function-id 'default::todo_list::create_todo_list' 
Do you want to submit a transaction for a range of [138600 - 207900] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x4aad67f5976cb875e7aa4a10f6210fca79dbacaaccc004a3184eafe7b72a5fe2?network=testnet
{
  "Result": {
    "transaction_hash": "0x4aad67f5976cb875e7aa4a10f6210fca79dbacaaccc004a3184eafe7b72a5fe2",
    "gas_used": 1386,
    "gas_unit_price": 100,
    "sender": "cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
    "sequence_number": 3,
    "success": true,
    "timestamp_us": 1725807418268333,
    "version": 5954728316,
    "vm_status": "Executed successfully"
  }
}

create_todo

$ aptos move run \
  --function-id 'default::todo_list::create_todo' \
  --args 'u64:0' 'string:study'
Do you want to submit a transaction for a range of [800 - 1200] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x0559761e6e0e537832a7293a9afc02779a501534052a5ba1b8632c1804cbe6d3?network=testnet
{
  "Result": {
    "transaction_hash": "0x0559761e6e0e537832a7293a9afc02779a501534052a5ba1b8632c1804cbe6d3",
    "gas_used": 8,
    "gas_unit_price": 100,
    "sender": "cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
    "sequence_number": 4,
    "success": true,
    "timestamp_us": 1725807739852836,
    "version": 5954745955,
    "vm_status": "Executed successfully"
  }
}

image-20240908230348639

complete_todo

$ aptos move run \
  --function-id 'default::todo_list::complete_todo' \
  --args 'u64:0' 'u64:0'
Do you want to submit a transaction for a range of [600 - 900] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x27bddde7703199720412a67854e4f04bac62c0213b1836f7bb289625ef93f503?network=testnet
{
  "Result": {
    "transaction_hash": "0x27bddde7703199720412a67854e4f04bac62c0213b1836f7bb289625ef93f503",
    "gas_used": 6,
    "gas_unit_price": 100,
    "sender": "cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
    "sequence_number": 5,
    "success": true,
    "timestamp_us": 1725807879910254,
    "version": 5954753634,
    "vm_status": "Executed successfully"
  }
}

image-20240908230558475

学习材料

https://aptos.dev/en/build/guides/first-move-module

TypeScript SDK Quickstart

学习材料

https://aptos.dev/en/build/sdks/ts-sdk/quickstart

工程初始化

$ mkdir ts_sdk_quickstart && cd ts_sdk_quickstart

$ pnpm init && \
    pnpm add -D typescript @types/node ts-node && \
    npx tsc --init && \
    mkdir src && \
    echo 'async function example() { console.log("Running example!")}; example()' > src/quickstart.ts
  • 测试
$ pnpx ts-node src/quickstart.ts
Running example!

安装 Aptos TS SDK

$ pnpm add @aptos-labs/ts-sdk

$ cat package.json
{
  "name": "ts_sdk_quickstart",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^22.5.4",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.4"
  },
  "dependencies": {
    "@aptos-labs/ts-sdk": "^1.27.1"
  }
}

获取用户创建模块信息

import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";

async function example() {
  const config = new AptosConfig({ network: Network.TESTNET });
  const aptos = new Aptos(config);

  const modules = await aptos.getAccountModules({
    accountAddress:
      "0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
  });

  console.log(JSON.stringify(modules, null, 2));
}

example();

创建账号并领水

import {
  Account,
  Aptos,
  AptosConfig,
  Network,
  AccountAddress,
} from "@aptos-labs/ts-sdk";

const APTOS_COIN = "0x1::aptos_coin::AptosCoin";
const COIN_STORE = `0x1::coin::CoinStore<${APTOS_COIN}>`;
const ALICE_INITIAL_BALANCE = 100_000_000;
const BOB_INITIAL_BALANCE = 100;

async function createAndFundAccount(aptos: Aptos, initialBalance: number) {
  const account: Account = Account.generate();

  await aptos.fundAccount({
    accountAddress: account.accountAddress,
    amount: initialBalance,
  });

  return account;
}

async function getAccountBalance(aptos: Aptos, accountAddress: AccountAddress) {
  const accountBalance = await aptos.getAccountResource({
    accountAddress: accountAddress,
    resourceType: COIN_STORE,
  });
  return Number(accountBalance.coin.value);
}

async function example() {
  const config = new AptosConfig({ network: Network.TESTNET });
  const aptos = new Aptos(config);

  const alice = await createAndFundAccount(aptos, ALICE_INITIAL_BALANCE);
  const bob = await createAndFundAccount(aptos, BOB_INITIAL_BALANCE);
  console.log(`alice account: ${alice.accountAddress}`);
  console.log(`bob account: ${bob.accountAddress}`);

  console.log("\n=== Balances ===");
  const aliceBalance = await getAccountBalance(aptos, alice.accountAddress);
  console.log(`Alice's balance is: ${aliceBalance}`);

  const bobBalance = await getAccountBalance(aptos, bob.accountAddress);
  console.log(`Bob's balance is: ${bobBalance}`);
}

example();

// Output:
// alice account: 0x7c36a18406f432fc752df9542d95c08ae299d5a8175614381c7778eafe0954e6
// bob account: 0xc6705ad2cc5435103e59dc9144d5c1ea7b0d99d535d5a4a10bc1c0b32cf52337

// === Balances ===
// Alice's balance is: 100000000
// Bob's balance is: 100

Aptos DApp 开发入门

学习材料

https://aptos.dev/en/build/create-aptos-dapp

执行创建命令

$ pnpx create-aptos-dapp@latest

   ###    ########  ########  #######   ######
  ## ##   ##     ##    ##    ##     ## ##    ##
 ##   ##  ##     ##    ##    ##     ## ##
##     ## ########     ##    ##     ##  ######
######### ##           ##    ##     ##       ##
##     ## ##           ##    ##     ## ##    ##
##     ## ##           ##     #######   ######

Welcome to the create-aptos-dapp wizard 🌐
✔ Enter a new project name … js-aptos-dapp
? Choose how to start › - Use arrow-keys. Return to submit.
❯   Boilerplate Template - A Boilerplate template to start an Aptos dapp with
    NFT minting dapp
    Token minting dapp
    Token staking dapp
? Choose your network › - You can change this later
    Mainnet
❯   Testnet
    Devnet
✔ Help us improve create-aptos-dapp by collection anonymous data … no
✔ Do you want to make any changes to your selections? (Default is No) … no
✔ Scaffolding project in /root/Study/MDBook/intensive-colearning-aptos/src/codes/hello_aptos_dapp/js-aptos-dapp
Need to install dependencies, this might take a while - in the meantime:
📖 Visit the Boilerplate Template docs: https://aptos.dev/en/build/create-aptos-dapp/templates/boilerplate
⠋ Installing the dependencies...
✔ Scaffolding project in /root/Study/MDBook/intensive-colearning-aptos/src/codes/hello_aptos_dapp/js-aptos-dapp
Success! You're ready to start building your dapp on Aptos.
Next steps:
Run: cd js-aptos-dapp
Open in your favorite IDE && follow the README file
  • 创建的dapp模板工程目录结构
js-aptos-dapp/
├── LICENSE
├── README.md
├── components.json
├── contract                —— 合约代码
├── frontend                —— 前端代码
├── index.html
├── package.json
├── postcss.config.js
├── public                  —— 静态资源
├── scripts                 —— 脚本代码
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite-env.d.ts
└── vite.config.ts

初始化账户

$ aptos init
Configuring for profile default
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
testnet
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

No key given, generating key...
Account 0x3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d doesn't exist, creating it and funding it with 100000000 Octas
Account 0x3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d funded successfully

---
Aptos CLI is now set up for account 0x3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d as profile default!
 See the account here: https://explorer.aptoslabs.com/account/0x3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d?network=testnet
 Run `aptos --help` for more information about commands
{
  "Result": "Success"
}

部署合约

$ aptos move publish --named-addresses message_board_addr=default
Compiling, may take a little while to download git dependencies...
UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING MessageBoard
package size 2084 bytes
Do you want to submit a transaction for a range of [268700 - 403000] Octas at a gas unit price of 100 Octas? [yes/no] >

Do you want to submit a transaction for a range of [268700 - 403000] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x112a06807e1efb3c26972b017d4b9c1ee2eba94fc5868093427206babffa968f?network=testnet
{
  "Result": {
    "transaction_hash": "0x112a06807e1efb3c26972b017d4b9c1ee2eba94fc5868093427206babffa968f",
    "gas_used": 2687,
    "gas_unit_price": 100,
    "sender": "3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1725967632715580,
    "version": 5963473442,
    "vm_status": "Executed successfully"
  }
}

添加地址到.env文件

PROJECT_NAME=js-aptos-dapp
VITE_APP_NETWORK=testnet
VITE_MODULE_ADDRESS=0x3bd29c1df0312353e7cc1598d019c0716bee9dc10be949f8182856468c570f7d

启动服务

$ npm run dev

页面交互

image-20240910193200677

image-20240910193214946

官方 Todolist 前端交互体验

发布官方Todolist合约

初始化账户

$ aptos init
Configuring for profile default
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
testnet
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

No key given, generating key...
Account 0x09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433 doesn't exist, creating it and funding it with 100000000 Octas
Account 0x09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433 funded successfully

---
Aptos CLI is now set up for account 0x09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433 as profile default!
 See the account here: https://explorer.aptoslabs.com/account/0x09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433?network=testnet
 Run `aptos --help` for more information about commands
{
  "Result": "Success"
}

发布合约

$ aptos move publish --named-addresses todolist_addr=default
Compiling, may take a little while to download git dependencies...
UPDATING GIT DEPENDENCY https://github.com/aptos-labs/aptos-core.git
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_todo_list
Do you want to submit a transaction for a range of [190300 - 285400] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x18f2969ac50c1ad5b0c0e70bd666db729f71d62d2d27d88ac2bf28b918b34d13?network=testnet
{
  "Result": {
    "transaction_hash": "0x18f2969ac50c1ad5b0c0e70bd666db729f71d62d2d27d88ac2bf28b918b34d13",
    "gas_used": 1903,
    "gas_unit_price": 100,
    "sender": "09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1726052998054573,
    "version": 5968275918,
    "vm_status": "Executed successfully"
  }
}

修改前端代码

  • 修改前
export const aptos = new Aptos();
// change this to be your module account address
export const moduleAddress = "0xcbddf398841353776903dbab2fdaefc54f181d07e114ae818b1a67af28d1b018";
  • 修改后
export const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
// change this to be your module account address
export const moduleAddress =
  "0x09222b2046de179884197d7bba84656e16876a7630aec3c523a4d2491e909433";

启动前端服务

$ pnpm i
$ pnpm start

功能交互

创建新的Todolist

image-20240911192942419

创建新的待办

image-20240911193053929

完成待办

image-20240911193136532

image-20240911193151650

TypeScript SDK 基本使用

项目创建

$ mkdir ts_sdk_basic_usage && cd ts_sdk_basic_usage
$ pnpm init
$ pnpm add @aptos-labs/ts-sdk

Account

import {
  Account,
  Ed25519PrivateKey,
  SigningSchemeInput,
} from "@aptos-labs/ts-sdk";

function createAccount() {
  const account1 = Account.generate(); // defaults to Legacy Ed25519
  console.log(`account1: ${account1.accountAddress.toString()}`);

  account1.privateKey.toString;

  const account2 = Account.generate({
    scheme: SigningSchemeInput.Secp256k1Ecdsa,
  }); // Single Sender Secp256k1
  console.log(`account2: ${account2.accountAddress.toString()}`);

  const account3 = Account.generate({
    scheme: SigningSchemeInput.Ed25519,
    legacy: false,
  }); // Single Sender Ed25519
  console.log(`account3: ${account3.accountAddress.toString()}`);

  return account1;
}

function createAccountFromPrivateKey(privateKeyBytes: string) {
  const privateKey = new Ed25519PrivateKey(privateKeyBytes);
  const account = Account.fromPrivateKey({ privateKey });
  console.log(
    `account1 from private key: ${account.accountAddress.toString()}`
  );
}

async function main() {
  const account = createAccount();
  createAccountFromPrivateKey(account.privateKey.toString());
}

main();

// OUTPUT:
// account1: 0x2d7b14974554446317f614737f1f3df32a285c48b44ee3f520d2885ad9ade31a
// account2: 0xd49f49ee44ade9a30dcded76f2b805d683ad9117613ccd95e38b30d586351dbf
// account3: 0xc04102d0a5fdb19ea0ba31c8d7215625a76b1b778167bf58297cdbec1b0cd8e6
// account1 from private key: 0x2d7b14974554446317f614737f1f3df32a285c48b44ee3f520d2885ad9ade31a

fetch data

import {
  Account,
  Aptos,
  AptosConfig,
  Ed25519PrivateKey,
  Network,
  SigningSchemeInput,
} from "@aptos-labs/ts-sdk";

async function fetch_data() {
  const aptosConfig = new AptosConfig({ network: Network.TESTNET });
  const aptos = new Aptos(aptosConfig);

  const account = Account.generate();
  console.log(`>>> create account: ${account.accountAddress.toString()}`);

  const transaction = await aptos.fundAccount({
    accountAddress: account.accountAddress,
    amount: 100,
  });
  console.log(`\n>>> transaction: ${JSON.stringify(transaction)}`);

  const fund = await aptos.getAccountInfo({
    accountAddress: account.accountAddress,
  });
  console.log(`\n>>>fund: ${JSON.stringify(fund)}`);

  const modules = await aptos.getAccountModules({
    accountAddress: account.accountAddress,
  });
  console.log(`\n>>> modules: ${JSON.stringify(modules)}`);

  const tokens = await aptos.getAccountOwnedTokens({
    accountAddress: account.accountAddress,
  });
  console.log(`\n>>> tokens: ${JSON.stringify(tokens)}`);

  type Coin = { coin: { value: string } };

  const resource = await aptos.getAccountResource<Coin>({
    accountAddress: account.accountAddress,
    resourceType: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
  });
  console.log(`\n>>> resource: ${JSON.stringify(resource)}`);

  const value = resource.coin.value;
  console.log(`\n>>> coin value: ${value}`);
}

async function main() {
  fetch_data();
}

main();

// >>> create account: 0x06a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d

// >>> transaction: {"version":"5974082853","hash":"0x3308cd348384fa5623986b2c6aa82cb93a4df3fb4d8d11fa21e7e18c07dc369c","state_change_hash":"0x9df9233fe8a13c6e790cf588b2c7160587d5ce0f33cd87e0b92434361dbe34eb","event_root_hash":"0x5efdc1806cd0bc1968a417db06bf204d175291443a0f20535b45ead9fb1f168a","state_checkpoint_hash":null,"gas_used":"1001","success":true,"vm_status":"Executed successfully","accumulator_root_hash":"0xedb82d4e1287e47350edc723dc470ccd41794b831d24ae35e409df205820c967","changes":[{"address":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","state_key_hash":"0x8ff336347df3a198a09afdaaad8c8434ded97110b4c398aa4ffcfb0d60c65e35","data":{"type":"0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>","data":{"coin":{"value":"100"},"deposit_events":{"counter":"1","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"2"}}},"frozen":false,"withdraw_events":{"counter":"0","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"3"}}}}},"type":"write_resource"},{"address":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","state_key_hash":"0x3f92d599ad51f3a459fb7dbc993208a7ac55aa8ff7b529ee318eb71d683d4b8f","data":{"type":"0x1::account::Account","data":{"authentication_key":"0x06a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","coin_register_events":{"counter":"1","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"0"}}},"guid_creation_num":"4","key_rotation_events":{"counter":"0","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"1"}}},"rotation_capability_offer":{"for":{"vec":[]}},"sequence_number":"0","signer_capability_offer":{"for":{"vec":[]}}}},"type":"write_resource"},{"address":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","state_key_hash":"0xc1b87f42046e685dfc675966f239376b4dad0bddb08c5d4710d87d631381be1d","data":{"type":"0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>","data":{"coin":{"value":"328418492900"},"deposit_events":{"counter":"2381638","guid":{"id":{"addr":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","creation_num":"2"}}},"frozen":false,"withdraw_events":{"counter":"2381630","guid":{"id":{"addr":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","creation_num":"3"}}}}},"type":"write_resource"},{"address":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","state_key_hash":"0xa64c3e06541e5b27ab82cec64706a40eb629a67a9218f13cd7459f5baa9978ff","data":{"type":"0x1::account::Account","data":{"authentication_key":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","coin_register_events":{"counter":"1","guid":{"id":{"addr":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","creation_num":"0"}}},"guid_creation_num":"4","key_rotation_events":{"counter":"0","guid":{"id":{"addr":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","creation_num":"1"}}},"rotation_capability_offer":{"for":{"vec":[]}},"sequence_number":"2381638","signer_capability_offer":{"for":{"vec":[]}}}},"type":"write_resource"},{"state_key_hash":"0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19","handle":"0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca","key":"0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935","value":"0x4d784ea77b0cbd940100000000000000","data":null,"type":"write_table_item"}],"sender":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12","sequence_number":"2381637","max_gas_amount":"500000","gas_unit_price":"100","expiration_timestamp_secs":"1726153296","payload":{"code":{"bytecode":"0xa11ceb0b0500000008010008020804030c150421020523100733500883012006a30114000000010002000301050800030403010002060105010001070002000008000200010403060c050301050001060c01080001030d6170746f735f6163636f756e740a6170746f735f636f696e04636f696e067369676e65720a616464726573735f6f66094170746f73436f696e0762616c616e6365046d696e74087472616e7366657200000000000000000000000000000000000000000000000000000000000000010308a0860100000000000308ffffffffffffffff000001170a0011000c030a03380007010a02170700172304120a000b030a0207001611020b000b010b02110302","abi":{"name":"main","visibility":"public","is_entry":true,"is_view":false,"generic_type_params":[],"params":["&signer","address","u64"],"return":[]}},"type_arguments":[],"arguments":["0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","100"],"type":"script_payload"},"signature":{"public_key":"0x7a58e569925baaba11db29b72e2f4975a0262ec6f9502d50e3b1a5da1d85ae04","signature":"0xf9fe2ff5efe90ee8ec1f1b0117c58bc7b4e5202b385b1d044d60c3395625a16902a763298433ea4c66babf1c6bc618af0f06d6fb9ad977ecef12d25478905f0a","type":"ed25519_signature"},"events":[{"guid":{"creation_number":"2","account_address":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12"},"sequence_number":"2381637","type":"0x1::coin::DepositEvent","data":{"amount":"100100"}},{"guid":{"creation_number":"0","account_address":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d"},"sequence_number":"0","type":"0x1::account::CoinRegisterEvent","data":{"type_info":{"account_address":"0x1","module_name":"0x6170746f735f636f696e","struct_name":"0x4170746f73436f696e"}}},{"guid":{"creation_number":"3","account_address":"0x19e2c9d62d5d5cc16f0de87e7e05f5161f9e820932341e82740cdeed1ba63d12"},"sequence_number":"2381629","type":"0x1::coin::WithdrawEvent","data":{"amount":"100"}},{"guid":{"creation_number":"2","account_address":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d"},"sequence_number":"0","type":"0x1::coin::DepositEvent","data":{"amount":"100"}},{"guid":{"creation_number":"0","account_address":"0x0"},"sequence_number":"0","type":"0x1::transaction_fee::FeeStatement","data":{"execution_gas_units":"6","io_gas_units":"7","storage_fee_octas":"98800","storage_fee_refund_octas":"0","total_charge_gas_units":"1001"}}],"timestamp":"1726153281890208","type":"user_transaction"}

// >>>fund: {"sequence_number":"0","authentication_key":"0x06a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d"}

// >>> modules: []

// >>> tokens: []

// >>> resource: {"coin":{"value":"100"},"deposit_events":{"counter":"1","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"2"}}},"frozen":false,"withdraw_events":{"counter":"0","guid":{"id":{"addr":"0x6a90df7117beb151d0483002ee0138b84c88c9b691610bdfc3edfe52bcfb85d","creation_num":"3"}}}}

// >>> coin value: 100

transaction

import {
  Account,
  Aptos,
  AptosConfig,
  Ed25519PrivateKey,
  Network,
  SigningSchemeInput,
} from "@aptos-labs/ts-sdk";

async function transaction() {
  console.log(
    "This example will create two accounts (Alice and Bob) and send a transaction transfering APT to Bob's account."
  );

  // 0. Setup the client and test accounts
  const config = new AptosConfig({ network: Network.TESTNET });
  const aptos = new Aptos(config);

  let alice = Account.generate();
  let bob = Account.generate();

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

  console.log("\n=== Funding accounts ===\n");
  await aptos.fundAccount({
    accountAddress: alice.accountAddress,
    amount: 100_000_000,
  });
  await aptos.fundAccount({
    accountAddress: bob.accountAddress,
    amount: 100,
  });
  console.log("Funded Alice and Bob's accounts!");

  // 1. Build
  console.log("\n=== 1. Building the transaction ===\n");
  const transaction = await aptos.transaction.build.simple({
    sender: alice.accountAddress,
    data: {
      // All transactions on Aptos are implemented via smart contracts.
      function: "0x1::aptos_account::transfer",
      functionArguments: [bob.accountAddress, 100],
    },
  });
  console.log("Built the transaction!");

  // 2. Simulate (Optional)
  console.log("\n === 2. Simulating Response (Optional) === \n");
  const [userTransactionResponse] = await aptos.transaction.simulate.simple({
    signerPublicKey: alice.publicKey,
    transaction,
  });
  console.log(userTransactionResponse);

  // 3. Sign
  console.log("\n=== 3. Signing transaction ===\n");
  const senderAuthenticator = aptos.transaction.sign({
    signer: alice,
    transaction,
  });
  console.log("Signed the transaction!");

  // 4. Submit
  console.log("\n=== 4. Submitting transaction ===\n");
  const submittedTransaction = await aptos.transaction.submit.simple({
    transaction,
    senderAuthenticator,
  });

  console.log(`Submitted transaction hash: ${submittedTransaction.hash}`);

  // 5. Wait for results
  console.log("\n=== 5. Waiting for result of transaction ===\n");
  const executedTransaction = await aptos.waitForTransaction({
    transactionHash: submittedTransaction.hash,
  });
  console.log(executedTransaction);
}

async function main() {
  transaction();
}

main();

// $ ts-node src/03_transaction.ts
// This example will create two accounts (Alice and Bob) and send a transaction transfering APT to Bob's account.
// === Addresses ===

// Alice's address is: 0x001e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852
// Bob's address is: 0xa52522c9a1d0d6695e09be57b9605168056173e50a56e2f355385270ab7070e1

// === Funding accounts ===

// Funded Alice and Bob's accounts!

// === 1. Building the transaction ===

// Built the transaction!

//  === 2. Simulating Response (Optional) ===

// {
//   version: '5979042043',
//   hash: '0xa3e7b041ce44d6d4aba88fa76a23ead97ecd064d327608bef3a585b5c5c1de22',
//   state_change_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
//   event_root_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
//   state_checkpoint_hash: null,
//   gas_used: '9',
//   success: true,
//   vm_status: 'Executed successfully',
//   accumulator_root_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
//   changes: [
//     {
//       address: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//       state_key_hash: '0x7f4ad20eb420245733b8128041b91e3723ac16a4105a7d42584484207aa31186',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       address: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//       state_key_hash: '0x940f9bd6c4a7c61733c53d99c83007bbaea16ccb3a9a638f573d1ab7d3647d85',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       address: '0xa52522c9a1d0d6695e09be57b9605168056173e50a56e2f355385270ab7070e1',
//       state_key_hash: '0x820cf5d86aa00653ace8db87a11885e143a73dd04688fa8dcc23556ba3bd23da',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       state_key_hash: '0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19',
//       handle: '0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca',
//       key: '0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935',
//       value: '0x65b3a042e529be940100000000000000',
//       data: null,
//       type: 'write_table_item'
//     }
//   ],
//   sender: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//   sequence_number: '0',
//   max_gas_amount: '200000',
//   gas_unit_price: '100',
//   expiration_timestamp_secs: '1726236062',
//   payload: {
//     function: '0x1::aptos_account::transfer',
//     type_arguments: [],
//     arguments: [
//       '0xa52522c9a1d0d6695e09be57b9605168056173e50a56e2f355385270ab7070e1',
//       '100'
//     ],
//     type: 'entry_function_payload'
//   },
//   signature: {
//     public_key: '0x0da1e7c9f856094993233fa9b9d0ce434aa192964c2d2a7f9ac0e98db7bef244',
//     signature: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
//     type: 'ed25519_signature'
//   },
//   events: [
//     {
//       guid: [Object],
//       sequence_number: '0',
//       type: '0x1::coin::WithdrawEvent',
//       data: [Object]
//     },
//     {
//       guid: [Object],
//       sequence_number: '1',
//       type: '0x1::coin::DepositEvent',
//       data: [Object]
//     },
//     {
//       guid: [Object],
//       sequence_number: '0',
//       type: '0x1::transaction_fee::FeeStatement',
//       data: [Object]
//     }
//   ],
//   timestamp: '1726236042647926'
// }

// === 3. Signing transaction ===

// Signed the transaction!

// === 4. Submitting transaction ===

// Submitted transaction hash: 0xab2aee974223a22ea6a1c24754a939c26a02bc417ac2e595aff8df6bb4e38f58

// === 5. Waiting for result of transaction ===

// {
//   version: '5979042103',
//   hash: '0xab2aee974223a22ea6a1c24754a939c26a02bc417ac2e595aff8df6bb4e38f58',
//   state_change_hash: '0xb983e92f607a266695468412bbbecbb52922aecd8d7b7f2681d0b4e977b1a6a6',
//   event_root_hash: '0xc6c4ffa097c7cf46570289b33ac3f2cd77fa41c72f767781f8122abe0014b583',
//   state_checkpoint_hash: null,
//   gas_used: '9',
//   success: true,
//   vm_status: 'Executed successfully',
//   accumulator_root_hash: '0xb9ddc32e3353a440762f727a57929a9722e87b27398924a3aff6ebc322ff00e5',
//   changes: [
//     {
//       address: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//       state_key_hash: '0x7f4ad20eb420245733b8128041b91e3723ac16a4105a7d42584484207aa31186',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       address: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//       state_key_hash: '0x940f9bd6c4a7c61733c53d99c83007bbaea16ccb3a9a638f573d1ab7d3647d85',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       address: '0xa52522c9a1d0d6695e09be57b9605168056173e50a56e2f355385270ab7070e1',
//       state_key_hash: '0x820cf5d86aa00653ace8db87a11885e143a73dd04688fa8dcc23556ba3bd23da',
//       data: [Object],
//       type: 'write_resource'
//     },
//     {
//       state_key_hash: '0x6e4b28d40f98a106a65163530924c0dcb40c1349d3aa915d108b4d6cfc1ddb19',
//       handle: '0x1b854694ae746cdbd8d44186ca4929b2b337df21d1c74633be19b2710552fdca',
//       key: '0x0619dc29a0aac8fa146714058e8dd6d2d0f3bdf5f6331907bf91f3acd81e6935',
//       value: '0x93d3fb1fe629be940100000000000000',
//       data: null,
//       type: 'write_table_item'
//     }
//   ],
//   sender: '0x1e858caee9d67b50a97f441b1590c170419905f2fb8d2140803c7e8e22c852',
//   sequence_number: '0',
//   max_gas_amount: '200000',
//   gas_unit_price: '100',
//   expiration_timestamp_secs: '1726236062',
//   payload: {
//     function: '0x1::aptos_account::transfer',
//     type_arguments: [],
//     arguments: [
//       '0xa52522c9a1d0d6695e09be57b9605168056173e50a56e2f355385270ab7070e1',
//       '100'
//     ],
//     type: 'entry_function_payload'
//   },
//   signature: {
//     public_key: '0x0da1e7c9f856094993233fa9b9d0ce434aa192964c2d2a7f9ac0e98db7bef244',
//     signature: '0x7cc94bc08806e4c1f71d1a2098b29d950dbc7ff29239bc6c1a628f036059ff7a4702b3a2ec4be64c2777be2e3ca61d5c1d058ae9d56e3d74e9340ac9c5e47802',
//     type: 'ed25519_signature'
//   },
//   events: [
//     {
//       guid: [Object],
//       sequence_number: '0',
//       type: '0x1::coin::WithdrawEvent',
//       data: [Object]
//     },
//     {
//       guid: [Object],
//       sequence_number: '1',
//       type: '0x1::coin::DepositEvent',
//       data: [Object]
//     },
//     {
//       guid: [Object],
//       sequence_number: '0',
//       type: '0x1::transaction_fee::FeeStatement',
//       data: [Object]
//     }
//   ],
//   timestamp: '1726236043771659',
//   type: 'user_transaction'
// }

Aptos CLI

命令总览

$ aptos -h
Command Line Interface (CLI) for developing and interacting with the Aptos blockchain

Usage: aptos <COMMAND>

Commands:
  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
  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 smart contract related operations
  multisig    Tool for interacting with multisig accounts
  node        Tool for operations related to nodes
  stake       Tool for manipulating stake and stake pools
  update      Update the CLI or other tools it depends on
  help        Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

info - 查看客户端信息

$ aptos info
{
  "Result": {
    "build_branch": "main",
    "build_cargo_version": "cargo 1.78.0 (54d8815d0 2024-03-26)",
    "build_clean_checkout": "true",
    "build_commit_hash": "4a202570804db6d27ba1ca6cb374228052be5bcb",
    "build_is_release_build": "true",
    "build_os": "linux-x86_64",
    "build_pkg_version": "4.1.0",
    "build_profile_name": "release",
    "build_rust_channel": "1.78.0-x86_64-unknown-linux-gnu",
    "build_rust_version": "rustc 1.78.0 (9b00956e5 2024-04-29)",
    "build_tag": "",
    "build_time": "2024-09-06 12:07:47 +00:00",
    "build_using_tokio_unstable": "true"
  }
}

config - 配置相关

命令总览

$ aptos config -h
Tool for interacting with configuration of the Aptos CLI tool

Usage: aptos config <COMMAND>

Commands:
  delete-profile              Delete the specified profile
  generate-shell-completions  Generate shell completion files
  rename-profile              Rename the specified profile
  set-global-config           Set global configuration settings
  show-global-config          Shows the properties in the global config
  show-private-key            Show the private key for the given profile
  show-profiles               Shows the current profiles available

show-private-key - 查看私钥

$ aptos config show-private-key --profile default
{
  "Result": "0x2a88.....................80020b45"
}

> 读取的是这个文件中的私钥
$ cat .aptos/config.yaml
---
profiles:
  default:
    network: Testnet
    private_key: "0x2a88.....................80020b45"
    public_key: "0xd6a31c9c8c707d9f2e0b936a05b9427527c879bcebcbdcab1339b7daea327ea6"
    account: cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022
    rest_url: "https://fullnode.testnet.aptoslabs.com"
    faucet_url: "https://faucet.testnet.aptoslabs.com"

init - 初始化本地配置并创建账户

  • 创建默认 profile
$ aptos init
Configuring for profile default
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
testnet
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

No key given, generating key...
Account 0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97 doesn't exist, creating it and funding it with 100000000 Octas
Account 0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97 funded successfully

---
Aptos CLI is now set up for account 0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97 as profile default!
 See the account here: https://explorer.aptoslabs.com/account/0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97?network=testnet
 Run `aptos --help` for more information about commands
{
  "Result": "Success"
}
  • 创建指定名称 profile
$ aptos init --profile jason
Configuring for profile jason
Choose network from [devnet, testnet, mainnet, local, custom | defaults to devnet]
mainnet
Enter your private key as a hex literal (0x...) [Current: None | No input: Generate new key (or keep one if present)]

No key given, generating key...
Account 0x7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334 does not exist, you will need to create and fund the account by transferring funds from another account

---
Aptos CLI is now set up for account 0x7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334 as profile jason!
 See the account here: https://explorer.aptoslabs.com/account/0x7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334?network=mainnet
 Run `aptos --help` for more information about commands
{
  "Result": "Success"
}
  • 命令执行后,生成内容将会写到文件:.aptos/config.yaml
 cat .aptos/config.yaml
---
profiles:
  default:
    network: Testnet
    private_key: "0x9e974b31cabc31b19ed0e1debe97efb0a1f7db88cc6735cc1b326a7d1460e8f1"
    public_key: "0x71bbee6b99ef2805d3f900a0b9ca1faa2a2c863076aa1d6d4d0ec0e13e71126b"
    account: 47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97
    rest_url: "https://fullnode.testnet.aptoslabs.com"
    faucet_url: "https://faucet.testnet.aptoslabs.com"
  jason:
    network: Mainnet
    private_key: "0x065700c413f3bf6d9632ace4dc841d422a846bac4949b4c1eb2483fdc855ced8"
    public_key: "0xc31ae7a8fb5b794a22f902e47fb7b956c3448a2ecfcb3e3303c637e872e118fb"
    account: 7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334
    rest_url: "https://fullnode.mainnet.aptoslabs.com"

account - 账户相关

命令总览

$ aptos account -h
Tool for interacting with accounts

Usage: aptos account <COMMAND>

Commands:
  create                           Create a new account on-chain
  create-resource-account          Create a resource account on-chain
  derive-resource-account-address  Derive the address for a resource account
  fund-with-faucet                 Fund an account with tokens from a faucet
  balance                          Show the account's balance of different coins
  list                             List resources, modules, or balance owned by an address
  lookup-address                   Lookup the account address through the on-chain lookup table
  rotate-key                       Rotate an account's authentication key
  transfer                         Transfer APT between accounts

create - 创建链上账户

$ aptos account create --account jason
Do you want to submit a transaction for a range of [99700 - 149500] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x2492804e7610ac92601496e9bc549e506f4aa59dad482d041f853fadb00b987e?network=testnet
{
  "Result": {
    "transaction_hash": "0x2492804e7610ac92601496e9bc549e506f4aa59dad482d041f853fadb00b987e",
    "gas_used": 997,
    "gas_unit_price": 100,
    "sender": "47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
    "sequence_number": 0,
    "success": true,
    "timestamp_us": 1726410280917653,
    "version": 5989303061,
    "vm_status": "Executed successfully"
  }
}

list

List resources, modules, or balance owned by an address

Usage: aptos account list [OPTIONS]

Options:
      --account <ACCOUNT>
          Address of the account you want to list resources/modules/balance for
      --query <QUERY>
          Type of items to list: [balance, resources, modules] [default: resources] [possible values: balance, modules,
          resources]
      --url <URL>
          URL to a fullnode on the network
      --connection-timeout-secs <CONNECTION_TIMEOUT_SECS>
          Connection timeout in seconds, used for the REST endpoint of the fullnode [default: 30]
      --node-api-key <NODE_API_KEY>
          Key to use for ratelimiting purposes with the node API. This value will be used as `Authorization: Bearer
          <key>`. You may also set this with the NODE_API_KEY environment variable [env: NODE_API_KEY=]
      --profile <PROFILE>
          Profile to use from the CLI config

领水

$ aptos account fund-with-faucet --account default
{
  "Result": "Added 100000000 Octas to account 0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97"
}

查看账户的余额和转账事件

$ aptos account list --query balance --account default
{
  "Result": [
    {
      "coin": {
        "value": "99900300"
      },
      "deposit_events": {
        "counter": "1",
        "guid": {
          "id": {
            "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
            "creation_num": "2"
          }
        }
      },
      "frozen": false,
      "withdraw_events": {
        "counter": "0",
        "guid": {
          "id": {
            "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
            "creation_num": "3"
          }
        }
      }
    }
  ]
}

列出帐户中的资源

$  aptos account list --query resources --account default
{
  "Result": [
    {
      "0x1::account::Account": {
        "authentication_key": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
        "coin_register_events": {
          "counter": "1",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "0"
            }
          }
        },
        "guid_creation_num": "4",
        "key_rotation_events": {
          "counter": "0",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "1"
            }
          }
        },
        "rotation_capability_offer": {
          "for": {
            "vec": []
          }
        },
        "sequence_number": "1",
        "signer_capability_offer": {
          "for": {
            "vec": []
          }
        }
      }
    },
    {
      "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>": {
        "coin": {
          "value": "99900300"
        },
        "deposit_events": {
          "counter": "1",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "2"
            }
          }
        },
        "frozen": false,
        "withdraw_events": {
          "counter": "0",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "3"
            }
          }
        }
      }
    }
  ]
}

列出账户模块

$ aptos account list --query modules
{
  "Result": [
    {
      "bytecode": "0xa11ceb0b060000000c01000c020c1a03266d04930106059901900107a902910408ba064006fa064910c307c9020a8c0a160ca20a97040db90e0a000001010102010301040105000607000007080000080800040d0700021407010001022202000009000100000a020100000b030100000c040500000e060100000f070100001008090000110a0a000012020a00001302040000150a0b0000160a020000170a0c000018070100031e070200021f0f020005201112020202012113050100022316170002241819000225021e0108100a1112141d020608010300010503060c03030103010a0203060c03080301060c0305030302080301020503010b040108010101010800050305070801050708000206050a02020a02080303060a020900090101080301060900040800050708010507030307080208050c05080102060c0a0201080501060805010c0306080105060800020608010501060801010801010b0401090009746f646f5f6c69737403626373066f626a656374067369676e657206737472696e670c737472696e675f7574696c7304546f646f08546f646f4c6973741355736572546f646f4c697374436f756e7465721a6173736572745f757365725f6861735f676976656e5f746f646f196173736572745f757365725f6861735f746f646f5f6c6973740d636f6d706c6574655f746f646f1f636f6e7374727563745f746f646f5f6c6973745f6f626a6563745f7365656406537472696e670b6372656174655f746f646f106372656174655f746f646f5f6c697374086765745f746f646f0d6765745f746f646f5f6c697374236765745f746f646f5f6c6973745f62795f746f646f5f6c6973745f6f626a5f61646472156765745f746f646f5f6c6973745f636f756e746572064f626a656374116765745f746f646f5f6c6973745f6f626a166765745f746f646f5f6c6973745f6f626a5f616464720d6861735f746f646f5f6c6973740b696e69745f6d6f64756c6507636f6e74656e7409636f6d706c65746564056f776e657205746f646f7307636f756e7465720a616464726573735f6f66156372656174655f6f626a6563745f6164647265737307666f726d61743208746f5f62797465730e436f6e7374727563746f72526566136372656174655f6e616d65645f6f626a6563740f67656e65726174655f7369676e657211616464726573735f746f5f6f626a656374cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc4902200000000000000000000000000000000000000000000000000000000000000010308030000000000000003080200000000000000030801000000000000000a0206057b7d5f7b7d0520cb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022126170746f733a3a6d657461646174615f7631b4020301000000000000001a455f544f444f5f4c4953545f444f53455f4e4f545f455849535418546f646f206c69737420646f6573206e6f74206578697374020000000000000015455f544f444f5f444f53455f4e4f545f455849535413546f646f20646f6573206e6f74206578697374030000000000000018455f544f444f5f414c52454144595f434f4d504c4554454419546f646f20697320616c726561647920636f6d706c657465640006086765745f746f646f0101000d6765745f746f646f5f6c6973740101000d6861735f746f646f5f6c697374010100156765745f746f646f5f6c6973745f636f756e746572010100166765745f746f646f5f6c6973745f6f626a5f61646472010100236765745f746f646f5f6c6973745f62795f746f646f5f6c6973745f6f626a5f616464720101000002021908031a010102021b051c0a08000202011d0300000000010a0b010b001000410d2304070509070127020100000001070b002901040405060702270202010401010e280b00110e0c040e040b011103110f0c060a0611010b062a010c050a050a020c032e0b0311000b050f000b02430d0c070a071001140921041f05230b0701070027080b070f01150203000000100a07030c010e0107040b0038000c020e02380102040104010114160b00110e0c040e040b011103110f0c060a0611010b062a010c050b020912000c030b050f000b03440d020501040102152f0a00110e0c060a062902040c0a062b021002140c0105120a0006000000000000000012022d020600000000000000000c010b010c020b000b02110311120c040e0411130c050a06400d000000000000000012010c070e050b072d010b062a020c030a03100214060100000000000000160b030f02150206010001011a200b000b01110b0c040a0411010b042b010c030a020a031000410d23041005140b03010701270b0310000b02420d0c050a051003140b051001140207010001011b100b000b01110b0c030a0311010b032b010c020a021004140b021000410d0208010001011c0a0b002b010c010a011004140b011000410d020901000102040d0a00290204090b002b021002140c01050b0600000000000000000c010b01020a00000001050b000b01110b3802020b01000001050e000b011103110f020c01000001050b000b01110b2901020d0000000101020101000102000000010000",
      "abi": {
        "address": "0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022",
        "name": "todo_list",
        "friends": [],
        "exposed_functions": [
          {
            "name": "complete_todo",
            "visibility": "public",
            "is_entry": true,
            "is_view": false,
            "generic_type_params": [],
            "params": [
              "&signer",
              "u64",
              "u64"
            ],
            "return": []
          },
          {
            "name": "create_todo",
            "visibility": "public",
            "is_entry": true,
            "is_view": false,
            "generic_type_params": [],
            "params": [
              "&signer",
              "u64",
              "0x1::string::String"
            ],
            "return": []
          },
          {
            "name": "create_todo_list",
            "visibility": "public",
            "is_entry": true,
            "is_view": false,
            "generic_type_params": [],
            "params": [
              "&signer"
            ],
            "return": []
          },
          {
            "name": "get_todo",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address",
              "u64",
              "u64"
            ],
            "return": [
              "0x1::string::String",
              "bool"
            ]
          },
          {
            "name": "get_todo_list",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address",
              "u64"
            ],
            "return": [
              "address",
              "u64"
            ]
          },
          {
            "name": "get_todo_list_by_todo_list_obj_addr",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address"
            ],
            "return": [
              "address",
              "u64"
            ]
          },
          {
            "name": "get_todo_list_counter",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address"
            ],
            "return": [
              "u64"
            ]
          },
          {
            "name": "get_todo_list_obj_addr",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address",
              "u64"
            ],
            "return": [
              "address"
            ]
          },
          {
            "name": "has_todo_list",
            "visibility": "public",
            "is_entry": false,
            "is_view": true,
            "generic_type_params": [],
            "params": [
              "address",
              "u64"
            ],
            "return": [
              "bool"
            ]
          }
        ],
        "structs": [
          {
            "name": "Todo",
            "is_native": false,
            "is_event": false,
            "abilities": [
              "copy",
              "drop",
              "store"
            ],
            "generic_type_params": [],
            "fields": [
              {
                "name": "content",
                "type": "0x1::string::String"
              },
              {
                "name": "completed",
                "type": "bool"
              }
            ]
          },
          {
            "name": "TodoList",
            "is_native": false,
            "is_event": false,
            "abilities": [
              "key"
            ],
            "generic_type_params": [],
            "fields": [
              {
                "name": "owner",
                "type": "address"
              },
              {
                "name": "todos",
                "type": "vector<0xcb3303e6b96a1a666347bd06e27d24cc0cb09e204fedb6223a7bc9091dc49022::todo_list::Todo>"
              }
            ]
          },
          {
            "name": "UserTodoListCounter",
            "is_native": false,
            "is_event": false,
            "abilities": [
              "key"
            ],
            "generic_type_params": [],
            "fields": [
              {
                "name": "counter",
                "type": "u64"
              }
            ]
          }
        ]
      }
    }
  ]
}

转账

$ aptos account balance --account default
{
  "Result": [
    {
      "asset_type": "coin",
      "coin_type": "0x1::aptos_coin::AptosCoin",
      "balance": 199900300
    }
  ]
}

$ aptos account balance --account jason
{
  "Result": [
    {
      "asset_type": "coin",
      "coin_type": "0x1::aptos_coin::AptosCoin",
      "balance": 0
    }
  ]
}

$ aptos account transfer --account jason --amount 10000
Do you want to submit a transaction for a range of [900 - 1300] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
Transaction submitted: https://explorer.aptoslabs.com/txn/0x2f24a1685e47cc6abca41e28696dd1a75f87c92c97142d6a67cf96042e15e682?network=testnet
{
  "Result": {
    "gas_unit_price": 100,
    "gas_used": 9,
    "balance_changes": {
      "47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97": {
        "coin": {
          "value": "199889400"
        },
        "deposit_events": {
          "counter": "2",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "2"
            }
          }
        },
        "frozen": false,
        "withdraw_events": {
          "counter": "1",
          "guid": {
            "id": {
              "addr": "0x47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
              "creation_num": "3"
            }
          }
        }
      },
      "7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334": {
        "coin": {
          "value": "10000"
        },
        "deposit_events": {
          "counter": "1",
          "guid": {
            "id": {
              "addr": "0x7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334",
              "creation_num": "2"
            }
          }
        },
        "frozen": false,
        "withdraw_events": {
          "counter": "0",
          "guid": {
            "id": {
              "addr": "0x7ac59d3368d1eb46c599ed1fb57d6f51d2b926f2d12b4573f6954fcde602c334",
              "creation_num": "3"
            }
          }
        }
      }
    },
    "sender": "47946eb78dcc85eda351217d42288e6d4669159113c7efbe7ec87bf6c07f5e97",
    "success": true,
    "version": 5989333990,
    "vm_status": "Executed successfully",
    "transaction_hash": "0x2f24a1685e47cc6abca41e28696dd1a75f87c92c97142d6a67cf96042e15e682"
  }
}

$ aptos account balance --account jason
{
  "Result": [
    {
      "asset_type": "coin",
      "coin_type": "0x1::aptos_coin::AptosCoin",
      "balance": 10000
    }
  ]
}

参考资料

https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/aptos-cli/shi-yong-aptos-cli


配置开发环境

A beautifully styled message.
A beautifully styled message.
A beautifully styled message.
A beautifully styled message.
A plain note.

示例学习:transfer_coin.ts

示例说明

本示例会创建两个账号(Alice 和 Bob),并向 Alice 账号领水,然后 Alice 向 Bob 进行转账。

示例来源

https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/transfer_coin.ts

运行效果

$ npm run transfer_coin

> ts-test@1.0.0 transfer_coin
> ts-node transfer_coin.ts

This example will create two accounts (Alice and Bob), fund Alice, and transfer between them using transferCoinTransaction.
=== Addresses ===

Alice's address is: 0x740b7c81913e7ebff0ae610d468b6db46ba04267685234cc792c3c335b64f64c
Bob's address is: 0xc7ac915a9ebd1f0a1bda1b682b2127c549036de015f07044641095bb23f544ea

=== Funding accounts ===


=== Initial Balances ===

Alice's balance is: 100000000
Bob's balance is: 0

=== Transfer 1000000 from Alice to Bob ===

Committed transaction: 0x19c20fa1364ef1f7fe0b95644ec24984054196f286496fa3d71e7bacca54873b

=== Balances after transfer ===

Alice's balance is: 98900100
Bob's balance is: 1000000

SDK 学习

(1) 初始化客户端

import {
  Account,
  AccountAddress,
  Aptos,
  AptosConfig,
  Network,
  NetworkToNetworkName,
} from "@aptos-labs/ts-sdk";

const ALICE_INITIAL_BALANCE = 100_000_000;
const BOB_INITIAL_BALANCE = 0;
const TRANSFER_AMOUNT = 1_000_000;

// Setup the client
const APTOS_NETWORK: Network =
  NetworkToNetworkName[process.env.APTOS_NETWORK ?? Network.TESTNET];
const config = new AptosConfig({ network: APTOS_NETWORK });
const aptos = new Aptos(config);

(2) 创建测试账号

// Create two accounts
const alice = Account.generate();
const bob = Account.generate();

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

(3) Alice 账号领水

// Fund the accounts
console.log("\n=== Funding accounts ===\n");

// Fund alice account
await aptos.fundAccount({
  accountAddress: alice.accountAddress,
  amount: ALICE_INITIAL_BALANCE,
});

// Show the balances
console.log("\n=== Initial Balances ===\n");
const aliceBalance = await balance("Alice", alice.accountAddress);
const bobBalance = await balance("Bob", bob.accountAddress);

if (aliceBalance !== ALICE_INITIAL_BALANCE)
  throw new Error("Alice's balance is incorrect");
if (bobBalance !== BOB_INITIAL_BALANCE)
  throw new Error("Bob's balance is incorrect");

(4) Alice 向 Bob 转账

// Transfer between users
console.log(`\n=== Transfer ${TRANSFER_AMOUNT} from Alice to Bob ===\n`);
const transaction = await aptos.transferCoinTransaction({
  sender: alice.accountAddress,
  recipient: bob.accountAddress,
  amount: TRANSFER_AMOUNT,
});

const pendingTxn = await aptos.signAndSubmitTransaction({
  signer: alice,
  transaction,
});
const response = await aptos.waitForTransaction({
  transactionHash: pendingTxn.hash,
});
console.log(`Committed transaction: ${response.hash}`);

(5) 验证转账结果

console.log("\n=== Balances after transfer ===\n");
const newAliceBalance = await balance(
  "Alice",
  alice.accountAddress,
  BigInt(response.version)
);
const newBobBalance = await balance("Bob", bob.accountAddress);

// Bob should have the transfer amount
if (newBobBalance !== TRANSFER_AMOUNT + BOB_INITIAL_BALANCE)
  throw new Error("Bob's balance after transfer is incorrect");

// Alice should have the remainder minus gas
if (newAliceBalance >= ALICE_INITIAL_BALANCE - TRANSFER_AMOUNT)
  throw new Error("Alice's balance after transfer is incorrect");

(6) 查询账户余额方法封装

const balance = async (aptos: Aptos, name: string, address: AccountAddress) => {
  type Coin = { coin: { value: string } };
  const resource = await aptos.getAccountResource<Coin>({
    accountAddress: address,
    resourceType: COIN_STORE,
  });
  const amount = Number(resource.coin.value);

  console.log(`${name}'s balance is: ${amount}`);
  return amount;
};

示例学习:keyless.ts

示例说明

本示例用于展示 Aptos 无密钥帐户的创建和转账。

Aptos Keyless 允许用户使用他们现有 OpenID Connect (OIDC) 帐户(即他们在 Google、GitHub 或 Apple 等 OIDC 提供商的 Web2 帐户)进行保护,而不是使用传统的密钥或助记词。

示例来源

https://github.com/aptos-labs/aptos-ts-sdk/blob/main/examples/typescript/keyless.ts

运行效果

$ ts-node examples/typescript/keyless.ts

=== Keyless Account Example ===

https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=openid&access_type=offline&service=lso&o2v=2&theme=glif&flowName=GeneralOAuthFlow&nonce=17467935072969263488480768409145005791958785027130051114412344202040832217364

1. Open the link above
2. Log in with your Google account
3. Click 'Exchange authorization code for tokens
4. Copy the 'id_token' - (toggling 'Wrap lines' option at the bottom makes this easier)

Paste the JWT (id_token) token here and press enter: eyJhbGciOiJSUzI1NiIsImtpZCI6IjVhYWZmNDdjMjFkMDZlMjY2Y2NlMzk1YjIxNDVjN2M2ZDQ3MzBlYTUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI0MDc0MDg3MTgxOTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDYzOTI3OTA2ODg4MDcyMjE5ODkiLCJhdF9oYXNoIjoieFVZN3JDVWxuRm8wTDl6dF9aN3dmdyIsIm5vbmNlIjoiMTc0Njc5MzUwNzI5NjkyNjM0ODg0ODA3Njg0MDkxNDUwMDU3OTE5NTg3ODUwMjcxMzAwNTExMTQ0MTIzNDQyMDIwNDA4MzIyMTczNjQiLCJpYXQiOjE3MjczNDc0OTAsImV4cCI6MTcyNzM1MTA5MH0.eAhUw2upTky71FkPwdM1AM3ZUbVvZB4ZI491QSF1DSNk7IcVTLiqzpApW786LA1b9_iFgVK0CCec-n6qWmt1iHUK2nC9ljP7w9A77gQXlhCUBig3lVBx-OE74AUTcGkZ-6tkTOKXEHo7O2jtlCCEX_X8Cm-h_z7WyM5119KWIup1roX8r9ASnX9XEeVB93fCkRIxFTrDOmoHEkyknG_0zlfUP-OILPuqdudP5lTyoGIN48epSdYrx1bPVDiy-qfiv0TEQ1i7fdV51vzgzDXKjGvHLx4K8qWucPHsD1WgVxasFbRcjXbWEjACn-c_-L5ec3XlHffjJu9IKDTTTZHGuA
=== Addresses ===

Alice's keyless account address is: 0x6312a314941e675c71b491ffb8b8bd1a799bb57ee276b8f58f2213f5ae6a298c
Alice's nonce is: 17467935072969263488480768409145005791958785027130051114412344202040832217364
Alice's ephem pubkey is: 0x0020208f671cecff3f94fd01f0d492eadac3ed660e344e0cd6e0844019e0c4779c1b
Bob's address is: 0x563ab13d7709797e67cd2c07442d1ebfa7ed8eb457fcbc85102d6d52aa5d8b70

=== Funding accounts ===


=== Balances ===

Alice's balance is: 199985900
Bob's balance is: 100
Committed transaction: 0x87e430811df0537e25327fd296cfe8efc596dc31ebc6758587a3571287c4b7bb

=== Balances after transfer ===

Alice's balance is: 199971800
Bob's balance is: 10100

SDK 学习

(1) 初始化客户端

import {
  Account,
  AccountAddress,
  Aptos,
  AptosConfig,
  EphemeralKeyPair,
  Network,
} from "@aptos-labs/ts-sdk";
import * as readlineSync from "readline-sync";

const COIN_STORE = "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>";
const ALICE_INITIAL_BALANCE = 100_000_000;
const BOB_INITIAL_BALANCE = 100;
const TRANSFER_AMOUNT = 10_000;

const config = new AptosConfig({ network: Network.TESTNET });
const aptos = new Aptos(config);

(2) 创建 Alice 临时密钥

// Generate the ephemeral (temporary) key pair that will be used to sign transactions.
const aliceEphem = EphemeralKeyPair.generate();

(3) 登陆 Alice 谷歌账户获取 JWT

const link = `https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=https%3A%2F%2Fdevelopers.google.com%2Foauthplayground&prompt=consent&response_type=code&client_id=407408718192.apps.googleusercontent.com&scope=openid&access_type=offline&service=lso&o2v=2&theme=glif&flowName=GeneralOAuthFlow&nonce=${aliceEphem.nonce}`;
console.log(`${link}\n`);

console.log("1. Open the link above");
console.log("2. Log in with your Google account");
console.log("3. Click 'Exchange authorization code for tokens");
console.log(
  "4. Copy the 'id_token' - (toggling 'Wrap lines' option at the bottom makes this easier)\n"
);

function inputJwt(): string {
  const jwt: string = readlineSync.question(
    "Paste the JWT (id_token) token here and press enter: ",
    {
      hideEchoBack: false,
    }
  );
  return jwt;
}

const jwt = inputJwt();

(4) 使用 JWT 及 Alice 临时密钥创建 Keyless 账户密钥对

// Derive the Keyless Account from the JWT and ephemeral key pair.
const alice = await aptos.deriveKeylessAccount({
  jwt,
  ephemeralKeyPair: aliceEphem,
});

console.log("=== Addresses ===\n");
console.log(`Alice's keyless account address is: ${alice.accountAddress}`);
console.log(`Alice's nonce is: ${aliceEphem.nonce}`);
console.log(`Alice's ephem pubkey is: ${aliceEphem.getPublicKey().toString()}`);

(5) 创建传统 Bob 账号并分别领水

const bob = Account.generate();
console.log(`Bob's address is: ${bob.accountAddress}`);

// Fund the accounts
console.log("\n=== Funding accounts ===\n");

await aptos.fundAccount({
  accountAddress: alice.accountAddress,
  amount: ALICE_INITIAL_BALANCE,
});

await aptos.fundAccount({
  accountAddress: bob.accountAddress,
  amount: BOB_INITIAL_BALANCE,
  options: { waitForIndexer: false },
});

console.log("\n=== Balances ===\n");
const aliceBalance = await balance(aptos, "Alice", alice.accountAddress);
const bobBalance = await balance(aptos, "Bob", bob.accountAddress);

(6) Alice 给 Bob 进行转账

// Transfer between users
const transaction = await aptos.transferCoinTransaction({
  sender: alice.accountAddress,
  recipient: bob.accountAddress,
  amount: TRANSFER_AMOUNT,
});

const committedTxn = await aptos.signAndSubmitTransaction({
  signer: alice,
  transaction,
});

await aptos.waitForTransaction({ transactionHash: committedTxn.hash });
console.log(`Committed transaction: ${committedTxn.hash}`);

(7) 查看转账后余额

console.log("\n=== Balances after transfer ===\n");
const newAliceBalance = await balance(aptos, "Alice", alice.accountAddress);
const newBobBalance = await balance(aptos, "Bob", bob.accountAddress);

// Bob should have the transfer amount
if (TRANSFER_AMOUNT !== newBobBalance - bobBalance)
  throw new Error("Bob's balance after transfer is incorrect");

// Alice should have the remainder minus gas
if (TRANSFER_AMOUNT >= aliceBalance - newAliceBalance)
  throw new Error("Alice's balance after transfer is incorrect");