# 你的第一笔交易

本教程介绍了 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")?;
  ```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gushi10546.gitbook.io/aptos-kai-fa-zhe-wen-dang/kai-fa-zhe-jiao-cheng/ni-de-di-yi-bi-jiao-yi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
