> For the complete documentation index, see [llms.txt](https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/kai-fa-zhe-jiao-cheng/ni-de-di-yi-bi-jiao-yi.md).

# 你的第一笔交易

本教程介绍了 Aptos SDK 以及如何生成、提交和验证提交给 Aptos 区块链的交易。我们将运行 `transfer-coin` 的示例。

### 第 1 步：选择一个 SDK

* [Official Aptos Typescript SDK](https://aptos.dev/sdks/typescript-sdk)
* [Official Aptos Python SDK](https://aptos.dev/sdks/python-sdk)
* [Official Aptos Rust SDK](https://aptos.dev/sdks/rust-sdk)

### 第 2 步：运行示例代码

克隆 `aptos-core` 仓库：

```bash
git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core
```

* Typescript

  进入到 Typescript SDK 示例的路径下

  ```bash
  cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript
  ```

  安装必要的依赖：

  ```bash
  yarn install
  ```

  运行 `transfer_coin` 示例代码

  ```bash
  yarn run transfer_coin
  ```
* Python

  进入到 Python SDK 示例的路径下：

  ```
  cd ~/aptos-core/ecosystem/python/sdk
  ```

  安装必要的依赖：

  ```
  curl -sSL <https://install.python-poetry.org> | python3
  poetry update
  ```

  运行 `transfer-coin` 示例代码

  ```
  poetry run python -m examples.transfer-coin
  ```
* Rust

  进入到 Rust SDK 示例的路径下：

  ```bash
  cd ~/aptos-core/sdk
  ```

  运行 `transfer-coin` 示例代码

  ```bash
  cargo run --example transfer-coin:
  ```

### 第 3 步：程序输出解析

在你运行上述示例中的命令后，将会出现类似以下的输出：

```
=== Addresses ===
Alice: 0x0baec07bfc42f8018ea304ddc307a359c1c6ab20fbce598065b6cb19acff7043
Bob: 0xc98ceafadaa32e50d06d181842406dbbf518b6586ab67cfa2b736aaddeb7c74f

=== Initial Balances ===
Alice: 20000
Bob: 0

=== Intermediate Balances ===
Alice: 18996
Bob: 1000

=== Final Balances ===
Alice: 17992
Bob: 2000
```

上面的输出显示，`transfer-coin`例子执行了以下步骤。

* 初始化 REST 和 Faucet 客户端。
* 创建两个账户：Alice 和 Bob。
  * 通过 Faucet 接口创建 Alice 的账户，并向其注资。
  * 通过 Faucet 接口创建 Bob 的账户。
* 将 1000 个代币从 Alice 的地址转到 Bob 的地址。
* 爱丽丝支付了 4 个单位 gas 费用，以实现这一转账操作。
* 再一次从 Alice 的地址转移 1000 个代币到 Bob 的地址。
* 爱丽丝又支付了 4 个单位的 gas 费用来完成这次转账。

接下来，请看下面用于完成上述步骤的 SDK 接口的详细解释。

### 第 4 步：深入理解 SDK 原理

`transfer-coin` 示例代码使用辅助函数与 [REST API](https://fullnode.devnet.aptoslabs.com/v1/spec#/) 互动。本节将对每个调用进行回顾，并对功能进行深入分析。

* Typescript
  * 请根据[`[transfer-coin]`](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/typescript/sdk/examples/typescript/transfer_coin.ts)的完整代码，完成下面的步骤操作
* Python
  * 请根据[`[transfer-coin]`](https://github.com/aptos-labs/aptos-core/blob/main/ecosystem/python/sdk/examples/transfer-coin.py)的完整代码，完成下面的步骤操作
* Rust
  * 请根据[`[transfer-coin]`](https://github.com/aptos-labs/aptos-core/blob/main/sdk/examples/transfer-coin.rs)的完整代码，完成下面的步骤操作

####

#### 第 4.1 步：初始化客户端

第一步， `transfer-coin` 实例中初始化了 REST 和 faucet 的客户端。

* REST 客户端与 REST API 进行交互
* faucet客户端与 devnet Faucet 服务交互，用于创建账户和向账户中注入代币。
* Typescript

  ```typescript
  const client = new AptosClient(NODE_URL);
  const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
  ```

  使用API客户端，我们可以创建一个 `CoinClient`，我们用它来进行常见的代币操作，如转移代币和检查余额。

  ```
  const coinClient = new CoinClient(client);
  ```

  `common.ts` 初始化了以下的 URL 值。

  ```tsx
  export const NODE_URL = process.env.APTOS_NODE_URL || "<https://fullnode.devnet.aptoslabs.com>";
  export const FAUCET_URL = process.env.APTOS_FAUCET_URL || "<https://faucet.devnet.aptoslabs.com>";
  ```
* Python

  ```python
  rest_client = RestClient(NODE_URL)
  faucet_client = FaucetClient(FAUCET_URL, rest_client)
  ```

  [`[common.py]`](https://github.com/aptos-labs/aptos-core/tree/main/ecosystem/python/sdk/examples/common.pyhttps://github.com/aptos-labs/aptos-core/tree/main/ecosystem/python/sdk/examples/common.py) 初始化了以下配置值

  ```python
  NODE_URL = os.getenv("APTOS_NODE_URL", "<https://fullnode.devnet.aptoslabs.com/v1>")
  FAUCET_URL = os.getenv("APTOS_FAUCET_URL", "<https://faucet.devnet.aptoslabs.com>")
  ```
* Rust

  ```rust
  let rest_client = Client::new(NODE_URL.clone());
  let faucet_client = FaucetClient::new(FAUCET_URL.clone(), NODE_URL.clone());
  ```

  使用 client API，我们可以创建一个 CoinClient 对象，我们用它来进行常见的硬币操作，如转移硬币和检查余额。

  ```rust
  let coin_client = CoinClient::new(&rest_client);
  ```

  在本例中，我们按照如下方法配置测试网 URL

  ```rust
  static NODE_URL: Lazy<Url> = Lazy::new(|| {
      Url::from_str(
          std::env::var("APTOS_NODE_URL")
              .as_ref()
              .map(|s| s.as_str())
              .unwrap_or("<https://fullnode.devnet.aptoslabs.com>"),
      )
      .unwrap()
  });

  static FAUCET_URL: Lazy<Url> = Lazy::new(|| {
      Url::from_str(
          std::env::var("APTOS_FAUCET_URL")
              .as_ref()
              .map(|s| s.as_str())
              .unwrap_or("<https://faucet.devnet.aptoslabs.com>"),
      )
      .unwrap()
  });
  ```

{% hint style="info" %}
请注意： 默认情况下，两个服务的 URL 都指向 Aptos devnet 服务。它们也可以通过以下环境变量进行配置。
{% endhint %}

* `APTOS_NODE_URL`
* `APTOS_FAUCET_URL` \</aside>

#### 第 4.2 步：在本地创建地址

#### 下一步，是在本地创建两个账户。 [账户](/aptos-kai-fa-zhe-wen-dang/gai-nian/zhang-hu.md)同时代表链上和链下状态。链下状态包括一个地址和用于验证所有权的公钥、私钥对。这一步演示了如何生成链下状态。

* Typescript

  ```typescript
  const alice = new AptosAccount();
  const bob = new AptosAccount();
  ```
* Python

  ```python
  alice = Account.generate()
  bob = Account.generate()
  ```
* Rust

  ```rust
  let mut alice = LocalAccount::generate(&mut rand::rngs::OsRng);
  let bob = LocalAccount::generate(&mut rand::rngs::OsRng);
  ```

#### 第 4.3 步：创建链上账户rust

在 Aptos 网络中，每个账户都必须有一个链上表示，以支持接收代币和硬币，以及在其他 DApps 中进行互动。一个账户代表了一个存储资产的媒介，因此它必须明确地被创建。这个例子利用 Faucet 提供的接口来创建和资助 Alice 的账户；对于 Bob 的账户，我们只做创建的操作：

* Typescript

  ```typescript
  await faucetClient.fundAccount(alice.address(), 20_000);
  await faucetClient.fundAccount(bob.address(), 0);type
  ```
* Python

  ```python
  faucet_client.fund_account(alice.address(), 20_000)
  faucet_client.fund_account(bob.address(), 0)
  ```
* Rust

  ```rust
  faucet_client
      .fund(alice.address(), 20_000)
      .await
      .context("Failed to fund Alice's account")?;
  faucet_client
      .create_account(bob.address())
      .await
      .context("Failed to fund Bob's account")?;
  ```

####

#### 第 4.4 步：读取账户余额

在这一步中，我们将使用 Aptos SDK 提供的能力请求一个资源，并且读取该资源中的成员变量

* Typescript

  ```typescript
  console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
  console.log(`Bob: ${await coinClient.checkBalance(bob)}`);
  ```

  源码逻辑：TypeScript SDK 提供的 `CoinClient` 下的 `checkBalance` 函数查询了当前账户地址下所有资源，然后过滤出 Aptos 测试代币（APTOS\_COIN）所在的资源，并且读取了当前的值，即用户持有的 Aptos 测试代币的余额。

  ```typescript
  async checkBalance(
    account: AptosAccount,
    extraArgs?: {
      // The coin type to use, defaults to 0x1::aptos_coin::AptosCoin
      coinType?: string;
    },
  ): Promise<bigint> {
    const coinType = extraArgs?.coinType ?? APTOS_COIN;
    const typeTag = `0x1::coin::CoinStore<${coinType}>`;
    const resources = await this.aptosClient.getAccountResources(account.address());
    const accountResource = resources.find((r) => r.type === typeTag);
    return BigInt((accountResource!.data as any).coin.value);
  }
  ```
* Python

  ```python
  print(f"Alice: {rest_client.account_balance(alice.address())}")
  print(f"Bob: {rest_client.account_balance(bob.address())}")
  ```

  源码逻辑：Python SDK 的 account\_balance 函数直接查询了 Aptos 测试代币所在的资源，并且读取了当前的值，即当前地址下 Aptos 测试代币的余额

  ```python
  def account_balance(self, account_address: str) -> int:
      """Returns the test coin balance associated with the account"""
      return self.account_resource(
          account_address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
      )["data"]["coin"]["value"]
  ```
* Rust

  ```rust
  println!(
      "Alice: {:?}",
      coin_client
          .get_account_balance(&alice.address())
          .await
          .context("Failed to get Alice's account balance the second time")?
  );
  println!(
      "Bob: {:?}",
      coin_client
          .get_account_balance(&bob.address())
          .await
          .context("Failed to get Bob's account balance the second time")?
  );
  ```

  源码逻辑：rust SDK 的 get\_account\_resource 函数直接查询了 Aptos 测试代币所在的资源，并且读取了当前的值，即当前地址下 Aptos 测试代币的余额

  ```rust
  let balance = self
      .get_account_resource(address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
      .await?;
  ```

####

#### 第 4.5 步：转账

和 Step 4.4 一样，这是将 Aptos 代币从 Alice 的地址转移到 Bob 的地址另一个辅助步骤。对于正确生成的交易，API 将返回一个交易哈希值，可以在后续步骤中使用，以检查交易状态。Aptos 区块链在提交时执行了一些验证检查，如果其中任何一项失败，用户将得到一个错误的响应。这些验证包括交易签名，未使用的序列号，以及将交易提交给适当的链。

* Typescript

  ```typescript
  let txnHash = await coinClient.transfer(alice, bob, 1_000);
  ```

  源码逻辑：`transfer` 函数生成了一个交易的 payload 信息，并且让客户端签署，发送，最后等待交易发送的响应结果

  ```typescript
  async transfer(
    from: AptosAccount,
    to: AptosAccount,
    amount: number | bigint,
    extraArgs?: {
      // The coin type to use, defaults to 0x1::aptos_coin::AptosCoin
      coinType?: string;
      maxGasAmount?: BCS.Uint64;
      gasUnitPrice?: BCS.Uint64;
      expireTimestamp?: BCS.Uint64;
    },
  ): Promise<string> {
    const coinTypeToTransfer = extraArgs?.coinType ?? APTOS_COIN;
    const payload = this.transactionBuilder.buildTransactionPayload(
      "0x1::coin::transfer",
      [coinTypeToTransfer],
      [to.address(), amount],
    );
    return this.aptosClient.generateSignSubmitTransaction(from, payload, extraArgs);
  }
  ```

  在 `aptosClient` 中，`generateSignSubmitTransaction` 函数做了以下的事情：

  ```tsx
  const rawTransaction = await this.generateRawTransaction(sender.address(), payload, extraArgs);
  const bcsTxn = AptosClient.generateBCSTransaction(sender, rawTransaction);
  const pendingTransaction = await this.submitSignedBCSTransaction(bcsTxn);
  return pendingTransaction.hash;
  ```

  我们一步步来看代码的逻辑：

  1. `transfer` 在内部是 [Coin Move 模块](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move#L412) 中的一个`EntryFunction`，即 Move 中的一个入口函数，可以直接调用。
  2. Move函数被存储在 coin 模块上： `0x1::coin`。
  3. 因为 coin 模块可以被其他 coin 使用，所以转移时必须明确指定要转移的 coin 类型。如果没有指定`coinType`，则默认为`0x1::aptos_coin::AptosCoin`。
* Python

  ```python
  txn_hash = rest_client.transfer(alice, bob.address(), 1_000)
  ```

  源码逻辑：Python SDK 生成交易，签署并发送该交易，最后等待交易发送的响应结果。

  ```python
  def bcs_transfer(
      self, sender: Account, recipient: AccountAddress, amount: int
  ) -> str:
      transaction_arguments = [
          TransactionArgument(recipient, Serializer.struct),
          TransactionArgument(amount, Serializer.u64),
      ]

      payload = EntryFunction.natural(
          "0x1::coin",
          "transfer",
          [TypeTag(StructTag.from_str("0x1::aptos_coin::AptosCoin"))],
          transaction_arguments,
      )

      signed_transaction = self.create_single_signer_bcs_transaction(
          sender, TransactionPayload(payload)
      )
      return self.submit_bcs_transaction(signed_transaction)
  ```

  我们一步步来看代码的逻辑：

  1. `transfer` 在内部是 [Coin Move 模块](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move#L412) 中的一个`EntryFunction`，即 Move 中的一个入口函数，可以直接调用。
  2. Move函数被存储在 coin 模块上。0x1::coin。
  3. 因为 coin 模块可以被其他 coin 使用，所以转账必须明确地使用 `TypeTag` 来定义要对哪种 coin 进行转账。
  4. 交易入参必须被放入带有类型指定器（`Serializer.{type}`）的`TransactionArguments` 中，这将在交易生成时将入参序列化为适当的类型。
* Rust

  ```rust
  let txn_hash = coin_client
      .transfer(&mut alice, bob.address(), 1_000, None)
      .await
      .context("Failed to submit transaction to transfer coins")?;
  ```

  源码逻辑：Rust SDK 生成交易，签署并发送该交易，最后等待交易发送的响应结果。

  ```rust
  let chain_id = self
      .api_client
      .get_index()
      .await
      .context("Failed to get chain ID")?
      .inner()
      .chain_id;
  let transaction_builder = TransactionBuilder::new(
      TransactionPayload::EntryFunction(EntryFunction::new(
          ModuleId::new(AccountAddress::ONE, Identifier::new("coin").unwrap()),
          Identifier::new("transfer").unwrap(),
          vec![TypeTag::from_str(options.coin_type).unwrap()],
          vec![
              bcs::to_bytes(&to_account).unwrap(),
              bcs::to_bytes(&amount).unwrap(),
          ],
      )),
      SystemTime::now()
          .duration_since(UNIX_EPOCH)
          .unwrap()
          .as_secs()
          + options.timeout_secs,
      ChainId::new(chain_id),
  )
  .sender(from_account.address())
  .sequence_number(from_account.sequence_number())
  .max_gas_amount(options.max_gas_amount)
  .gas_unit_price(options.gas_unit_price);
  let signed_txn = from_account.sign_with_transaction_builder(transaction_builder);
  Ok(self
      .api_client
      .submit(&signed_txn)
      .await
      .context("Failed to submit transfer transaction")?
      .into_inner())
  ```

  我们一步步来看代码的逻辑：

  1. 首先，我们获取链的 ID，这是建立交易的有效 payload 所必需的。
  2. `transfer` 在内部是 [Coin Move 模块](https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-framework/sources/coin.move#L412) 中的一个`EntryFunction`，即 Move 中的一个入口函数，可以直接调用。
  3. Move函数被存储在 coin 模块上。0x1::coin。
  4. 因为 coin 模块可以被其他 coin 使用，所以转账必须明确地使用 `TypeTag` 来定义要对哪种 coin 进行转账。
  5. 交易参数，如 `to_account` (目标账户地址)和 `amount` (金额)，必须被编码为 BCS，以便与 `TransactionBuilder` 一起使用。

#### 第 4.6 步：等待交易结果

* Typescript

  在Typescript中，只要调用 `coinClient.transfer` 就可以等待交易完成。一旦处理完毕（无论成功与否），该函数将返回 `Transaction` 对象，如果处理超时，则抛出一个错误。

  如果你想让它在交易没有成功提交时抛出错误，你可以在调用 `transfer` 时 checkSuccess 设置为 true。

  ```tsx
  await client.waitForTransaction(txnHash);
  ```
* Python

  交易的哈希值可以用来查询该交易当前的执行状态，Python SDK 提供了等待当前交易哈希值对应交易输出最终执行结果的能力。

  ```python
  rest_client.wait_for_transaction(txn_hash)
  ```
* Rust

  交易的哈希值可以用来查询该交易当前的执行状态，Rust SDK 提供了等待当前交易哈希值对应交易输出最终执行结果的能力。

  ```rust
  rest_client
      .wait_for_transaction(&txn_hash)
      .await
      .context("Failed when waiting for the transfer transaction")?;
  ```
