# 您的第一笔交易

本教程详细介绍了如何生成、提交和验证提交到 Aptos 区块链的交易。 步骤如下：

* 创建一个帐户的表达形式
* 围绕 REST 接口准备一个包装类
* 在 Faucet 接口周围准备一个包装类
* 将它们组合成一个应用程序，执行并验证

{% hint style="info" %}
请从下面的代码仓库中 clone 最新代码！
{% endhint %}

以下是示例代码，可以从 github 下载：

{% tabs %}
{% tab title="Python" %}
对于本教程，将重点关注 **first\_transaction.py**。

可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/python)找到代码
{% endtab %}

{% tab title="Rust" %}
对于本教程，将重点关注 **first\_transaction.py**。

可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/rust)找到代码
{% endtab %}

{% tab title="Typescript" %}
对于本教程，将重点关注 **first\_transaction.py**。

可以在[这里](https://github.com/aptos-labs/aptos-core/tree/main/developer-docs-site/static/examples/typescript)找到代码
{% endtab %}
{% endtabs %}

### Step 1) 创建一个账户的表达形式[​](https://aptos.dev/tutorials/your-first-transaction#step-1-create-a-representation-of-an-account) <a href="#step-1-create-a-representation-of-an-account" id="step-1-create-a-representation-of-an-account"></a>

每个 Aptos 帐户都有一个唯一的帐户地址。 该帐户的所有者拥有映射到 Aptos 帐户地址的公钥、私钥对，以及存储在该帐户中的身份验证密钥。 在帐户基础中可以查看更多信息。 以下片段演示了该部分中描述的内容。

{% tabs %}
{% tab title="Python" %}

```python
class Account:
    """Represents an account as well as the private, public key-pair for the Aptos blockchain."""

    def __init__(self, seed: bytes = None) -> None:
        if seed is None:
            self.signing_key = SigningKey.generate()
        else:
            self.signing_key = SigningKey(seed)

    def address(self) -> str:
        """Returns the address associated with the given account"""

        return self.auth_key()

    def auth_key(self) -> str:
        """Returns the auth_key for the associated account"""

        hasher = hashlib.sha3_256()
        hasher.update(self.signing_key.verify_key.encode() + b'\x00')
        return hasher.hexdigest()

    def pub_key(self) -> str:
        """Returns the public key for the associated account"""

        return self.signing_key.verify_key.encode().hex()
```

{% endtab %}

{% tab title="Rust" %}

```rust
pub struct Account {
    signing_key: SecretKey,
}

impl Account {
    /// Represents an account as well as the private, public key-pair for the Aptos blockchain.
    pub fn new(priv_key_bytes: Option<Vec<u8>>) -> Self {
        let signing_key = match priv_key_bytes {
            Some(key) => SecretKey::from_bytes(&key).unwrap(),
            None => SecretKey::generate(&mut rand::rngs::StdRng::from_seed(OsRng.gen())),
        };

        Account { signing_key }
    }
    /// Returns the address associated with the given account
    pub fn address(&self) -> String {
        self.auth_key()
    }

    /// Returns the auth_key for the associated account
    pub fn auth_key(&self) -> String {
        let mut sha3 = Sha3::v256();
        sha3.update(PublicKey::from(&self.signing_key).as_bytes());
        sha3.update(&vec![0u8]);

        let mut output = [0u8; 32];
        sha3.finalize(&mut output);
        hex::encode(output)
    }

    /// Returns the public key for the associated account
    pub fn pub_key(&self) -> String {
        hex::encode(PublicKey::from(&self.signing_key).as_bytes())
    }
}
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
/** A subset of the fields of a TransactionRequest, for this tutorial */
export type TxnRequest = Record<string, any> & { sequence_number: string };

/** Represents an account as well as the private, public key-pair for the Aptos blockchain */
export class Account {
  signingKey: Nacl.SignKeyPair;

  constructor(seed?: Uint8Array | undefined) {
    if (seed) {
      this.signingKey = Nacl.sign.keyPair.fromSeed(seed);
    } else {
      this.signingKey = Nacl.sign.keyPair();
    }
  }

  /** Returns the address associated with the given account */
  address(): string {
    return this.authKey();
  }

  /** Returns the authKey for the associated account */
  authKey(): string {
    let hash = SHA3.sha3_256.create();
    hash.update(Buffer.from(this.signingKey.publicKey));
    hash.update("\x00");
    return hash.hex();
  }

  /** Returns the public key for the associated account */
  pubKey(): string {
    return Buffer.from(this.signingKey.publicKey).toString("hex");
  }
}
```

{% endtab %}
{% endtabs %}

### Step 2) REST 接口 <a href="#step-2-rest-interface" id="step-2-rest-interface"></a>

Aptos 公开了一个用于与区块链交互的 [REST 接口](https://fullnode.devnet.aptoslabs.com/spec.html)。 虽然可以直接读取来自 REST 接口的数据，但以下代码片段演示了一种更优雅的方法。下一组代码片段演示了如何使用 REST 接口从 FullNode 检索分类帐数据，包括帐户和帐户资源数据。它还演示了如何使用 REST 接口来构建由 JSON 格式表示的签名交易。[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface)

{% tabs %}
{% tab title="Python" %}

```python
class RestClient:
    """A wrapper around the Aptos-core Rest API"""

    def __init__(self, url: str) -> None:
        self.url = url

```

{% endtab %}

{% tab title="Rust" %}

```rust
#[derive(Clone)]
pub struct RestClient {
    url: String,
}

impl RestClient {
    /// A wrapper around the Aptos-core Rest API
    pub fn new(url: String) -> Self {
        Self { url }
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
/** A wrapper around the Aptos-core Rest API */
export class RestClient {
  url: string;

  constructor(url: string) {
    this.url = url;
  }

```

{% endtab %}
{% endtabs %}

#### Step 2.1) 读取账户[​](https://aptos.dev/tutorials/your-first-transaction#step-21-reading-an-account) <a href="#step-21-reading-an-account" id="step-21-reading-an-account"></a>

以下是用于查询帐户数据的包装类。[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface)

{% tabs %}
{% tab title="Python" %}

```python
    def account(self, account_address: str) -> Dict[str, str]:
        """Returns the sequence number and authentication key for an account"""

        response = requests.get(f"{self.url}/accounts/{account_address}")
        assert response.status_code == 200, f"{response.text} - {account_address}"
        return response.json()

    def account_resources(self, account_address: str) -> Dict[str, Any]:
        """Returns all resources associated with the account"""

        response = requests.get(f"{self.url}/accounts/{account_address}/resources")
        assert response.status_code == 200, response.text
        return response.json()

```

{% endtab %}

{% tab title="Rust" %}

```rust
    /// Returns the sequence number and authentication key for an account
    pub fn account(&self, account_address: &str) -> serde_json::Value {
        let res =
            reqwest::blocking::get(format!("{}/accounts/{}", self.url, account_address)).unwrap();

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                account_address,
            );
        }

        res.json().unwrap()
    }

    /// Returns all resources associated with the account
    pub fn account_resources(&self, account_address: &str) -> serde_json::Value {
        let res = reqwest::blocking::get(format!(
            "{}/accounts/{}/resources",
            self.url, account_address
        ))
        .unwrap();

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                account_address,
            );
        }

        res.json().unwrap()
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
  /** Returns the sequence number and authentication key for an account */
  async account(accountAddress: string): Promise<Record<string, string> & { sequence_number: string }> {
    const response = await fetch(`${this.url}/accounts/${accountAddress}`, {method: "GET"});
    if (response.status != 200) {
      assert(response.status == 200, await response.text());
    }
    return await response.json();
  }

  /** Returns all resources associated with the account */
  async accountResources(accountAddress: string): Promise<Record<string, any> & { type: string }> {
    const response = await fetch(`${this.url}/accounts/${accountAddress}/resources`, {method: "GET"});
    if (response.status != 200) {
      assert(response.status == 200, await response.text());
    }
    return await response.json();
  }

```

{% endtab %}
{% endtabs %}

#### Step 2.2) 提交一笔交易[​](https://aptos.dev/tutorials/your-first-transaction#step-22-submitting-a-transaction) <a href="#step-22-submitting-a-transaction" id="step-22-submitting-a-transaction"></a>

下面演示了构建、签名和等待交易的核心功能。[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface)

{% tabs %}
{% tab title="Python" %}

```python
    def generate_transaction(self, sender: str, payload: Dict[str, Any]) -> Dict[str, Any]:
        """Generates a transaction request that can be submitted to produce a raw transaction that
        can be signed, which upon being signed can be submitted to the blockchain. """

        account_res = self.account(sender)
        seq_num = int(account_res["sequence_number"])
        txn_request = {
            "sender": f"0x{sender}",
            "sequence_number": str(seq_num),
            "max_gas_amount": "1000",
            "gas_unit_price": "1",
            "gas_currency_code": "XUS",
            "expiration_timestamp_secs": str(int(time.time()) + 600),
            "payload": payload,
        }
        return txn_request

    def sign_transaction(self, account_from: Account, txn_request: Dict[str, Any]) -> Dict[str, Any]:
        """Converts a transaction request produced by `generate_transaction` into a properly signed
        transaction, which can then be submitted to the blockchain."""

        res = requests.post(f"{self.url}/transactions/signing_message", json=txn_request)
        assert res.status_code == 200, res.text
        to_sign = bytes.fromhex(res.json()["message"][2:])
        signature = account_from.signing_key.sign(to_sign).signature
        txn_request["signature"] = {
            "type": "ed25519_signature",
            "public_key": f"0x{account_from.pub_key()}",
            "signature": f"0x{signature.hex()}",
        }
        return txn_request

    def submit_transaction(self, txn: Dict[str, Any]) -> Dict[str, Any]:
        """Submits a signed transaction to the blockchain."""

        headers = {'Content-Type': 'application/json'}
        response = requests.post(f"{self.url}/transactions", headers=headers, json=txn)
        assert response.status_code == 202, f"{response.text} - {txn}"
        return response.json()

    def transaction_pending(self, txn_hash: str) -> bool:
        response = requests.get(f"{self.url}/transactions/{txn_hash}")
        if response.status_code == 404:
            return True
        assert response.status_code == 200, f"{response.text} - {txn_hash}"
        return response.json()["type"] == "pending_transaction"

    def wait_for_transaction(self, txn_hash: str) -> None:
        """Waits up to 10 seconds for a transaction to move past pending state."""

        count = 0
        while self.transaction_pending(txn_hash):
            assert count < 10, f"transaction {txn_hash} timed out"
            time.sleep(1)
            count += 1
        /// Generates a transaction request that can be submitted to produce a raw transaction that can be signed, which upon being signed can be submitted to the blockchain.
    pub fn generate_transaction(
        &self,
        sender: &str,
        payload: serde_json::Value,
    ) -> serde_json::Value {
        let account_res = self.account(sender);

        let seq_num = account_res
            .get("sequence_number")
            .unwrap()
            .as_str()
            .unwrap()
            .parse::<u64>()
            .unwrap();

        // Unix timestamp, in seconds + 10 minutes
        let expiration_time_secs = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs()
            + 600;

        serde_json::json!({
            "sender": format!("0x{}", sender),
            "sequence_number": seq_num.to_string(),
            "max_gas_amount": "1000",
            "gas_unit_price": "1",
            "gas_currency_code": "XUS",
            "expiration_timestamp_secs": expiration_time_secs.to_string(),
            "payload": payload,
        })
    }

    /// Converts a transaction request produced by `generate_transaction` into a properly signed transaction, which can then be submitted to the blockchain.
    pub fn sign_transaction(
        &self,
        account_from: &mut Account,
        mut txn_request: serde_json::Value,
    ) -> serde_json::Value {
        let res = reqwest::blocking::Client::new()
            .post(format!("{}/transactions/signing_message", self.url))
            .body(txn_request.to_string())
            .send()
            .unwrap();

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                txn_request.as_str().unwrap_or(""),
            );
        }
        let body: serde_json::Value = res.json().unwrap();
        let to_sign_hex = Box::new(body.get("message").unwrap().as_str()).unwrap();
        let to_sign = hex::decode(&to_sign_hex[2..]).unwrap();
        let signature: String = ExpandedSecretKey::from(&account_from.signing_key)
            .sign(&to_sign, &PublicKey::from(&account_from.signing_key))
            .encode_hex();

        let signature_payload = serde_json::json!({
            "type": "ed25519_signature",
            "public_key": format!("0x{}", account_from.pub_key()),
            "signature": format!("0x{}", signature),
        });
        txn_request
            .as_object_mut()
            .unwrap()
            .insert("signature".to_string(), signature_payload);
        txn_request
    }

    /// Submits a signed transaction to the blockchain.
    pub fn submit_transaction(&self, txn_request: &serde_json::Value) -> serde_json::Value {
        let res = reqwest::blocking::Client::new()
            .post(format!("{}/transactions", self.url))
            .body(txn_request.to_string())
            .header("Content-Type", "application/json")
            .send()
            .unwrap();

        if res.status() != 202 {
            assert_eq!(
                res.status(),
                202,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                txn_request.as_str().unwrap_or(""),
            );
        }
        res.json().unwrap()
    }

    pub fn transaction_pending(&self, transaction_hash: &str) -> bool {
        let res = reqwest::blocking::get(format!("{}/transactions/{}", self.url, transaction_hash))
            .unwrap();

        if res.status() == 404 {
            return true;
        }

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                transaction_hash,
            );
        }

        res.json::<serde_json::Value>()
            .unwrap()
            .get("type")
            .unwrap()
            .as_str()
            .unwrap()
            == "pending_transaction"
    }

    /// Waits up to 10 seconds for a transaction to move past pending state.
    pub fn wait_for_transaction(&self, txn_hash: &str) {
        let mut count = 0;
        while self.transaction_pending(txn_hash) {
            assert!(count < 10, "transaction {} timed out", txn_hash);
            thread::sleep(Duration::from_secs(1));
            count += 1;
        }
    }
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
    /// Generates a transaction request that can be submitted to produce a raw transaction that can be signed, which upon being signed can be submitted to the blockchain.
    pub fn generate_transaction(
        &self,
        sender: &str,
        payload: serde_json::Value,
    ) -> serde_json::Value {
        let account_res = self.account(sender);

        let seq_num = account_res
            .get("sequence_number")
            .unwrap()
            .as_str()
            .unwrap()
            .parse::<u64>()
            .unwrap();

        // Unix timestamp, in seconds + 10 minutes
        let expiration_time_secs = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs()
            + 600;

        serde_json::json!({
            "sender": format!("0x{}", sender),
            "sequence_number": seq_num.to_string(),
            "max_gas_amount": "1000",
            "gas_unit_price": "1",
            "gas_currency_code": "XUS",
            "expiration_timestamp_secs": expiration_time_secs.to_string(),
            "payload": payload,
        })
    }

    /// Converts a transaction request produced by `generate_transaction` into a properly signed transaction, which can then be submitted to the blockchain.
    pub fn sign_transaction(
        &self,
        account_from: &mut Account,
        mut txn_request: serde_json::Value,
    ) -> serde_json::Value {
        let res = reqwest::blocking::Client::new()
            .post(format!("{}/transactions/signing_message", self.url))
            .body(txn_request.to_string())
            .send()
            .unwrap();

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                txn_request.as_str().unwrap_or(""),
            );
        }
        let body: serde_json::Value = res.json().unwrap();
        let to_sign_hex = Box::new(body.get("message").unwrap().as_str()).unwrap();
        let to_sign = hex::decode(&to_sign_hex[2..]).unwrap();
        let signature: String = ExpandedSecretKey::from(&account_from.signing_key)
            .sign(&to_sign, &PublicKey::from(&account_from.signing_key))
            .encode_hex();

        let signature_payload = serde_json::json!({
            "type": "ed25519_signature",
            "public_key": format!("0x{}", account_from.pub_key()),
            "signature": format!("0x{}", signature),
        });
        txn_request
            .as_object_mut()
            .unwrap()
            .insert("signature".to_string(), signature_payload);
        txn_request
    }

    /// Submits a signed transaction to the blockchain.
    pub fn submit_transaction(&self, txn_request: &serde_json::Value) -> serde_json::Value {
        let res = reqwest::blocking::Client::new()
            .post(format!("{}/transactions", self.url))
            .body(txn_request.to_string())
            .header("Content-Type", "application/json")
            .send()
            .unwrap();

        if res.status() != 202 {
            assert_eq!(
                res.status(),
                202,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                txn_request.as_str().unwrap_or(""),
            );
        }
        res.json().unwrap()
    }

    pub fn transaction_pending(&self, transaction_hash: &str) -> bool {
        let res = reqwest::blocking::get(format!("{}/transactions/{}", self.url, transaction_hash))
            .unwrap();

        if res.status() == 404 {
            return true;
        }

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{} - {}",
                res.text().unwrap_or("".to_string()),
                transaction_hash,
            );
        }

        res.json::<serde_json::Value>()
            .unwrap()
            .get("type")
            .unwrap()
            .as_str()
            .unwrap()
            == "pending_transaction"
    }

    /// Waits up to 10 seconds for a transaction to move past pending state.
    pub fn wait_for_transaction(&self, txn_hash: &str) {
        let mut count = 0;
        while self.transaction_pending(txn_hash) {
            assert!(count < 10, "transaction {} timed out", txn_hash);
            thread::sleep(Duration::from_secs(1));
            count += 1;
        }
    }
    
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
  /** Generates a transaction request that can be submitted to produce a raw transaction that
   can be signed, which upon being signed can be submitted to the blockchain. */
  async generateTransaction(sender: string, payload: Record<string, any>): Promise<TxnRequest> {
    const account = await this.account(sender);
    const seqNum = parseInt(account["sequence_number"]);
    return {
      "sender": `0x${sender}`,
      "sequence_number": seqNum.toString(),
      "max_gas_amount": "1000",
      "gas_unit_price": "1",
      "gas_currency_code": "XUS",
      // Unix timestamp, in seconds + 10 minutes
      "expiration_timestamp_secs": (Math.floor(Date.now() / 1000) + 600).toString(),
      "payload": payload,
    };
  }

  /** Converts a transaction request produced by `generate_transaction` into a properly signed
   transaction, which can then be submitted to the blockchain. */
  async signTransaction(accountFrom: Account, txnRequest: TxnRequest): Promise<TxnRequest> {
    const response = await fetch(`${this.url}/transactions/signing_message`, {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify(txnRequest)
    });
    if (response.status != 200) {
      assert(response.status == 200, (await response.text()) + " - " + JSON.stringify(txnRequest));
    }
    const result: Record<string, any> & { message: string } = await response.json();
    const toSign = Buffer.from(result["message"].substring(2), "hex");
    const signature = Nacl.sign(toSign, accountFrom.signingKey.secretKey);
    const signatureHex = Buffer.from(signature).toString("hex").slice(0, 128);
    txnRequest["signature"] = {
      "type": "ed25519_signature",
      "public_key": `0x${accountFrom.pubKey()}`,
      "signature": `0x${signatureHex}`,
    };
    return txnRequest;
  }

  /** Submits a signed transaction to the blockchain. */
  async submitTransaction(accountFrom: Account, txnRequest: TxnRequest): Promise<Record<string, any>> {
    const response = await fetch(`${this.url}/transactions`, {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify(txnRequest)
    });
    if (response.status != 202) {
      assert(response.status == 202, (await response.text()) + " - " + JSON.stringify(txnRequest));
    }
    return await response.json();
  }

  async transactionPending(txnHash: string): Promise<boolean> {
    const response = await fetch(`${this.url}/transactions/${txnHash}`, {method: "GET"});
    if (response.status == 404) {
      return true;
    }
    if (response.status != 200) {
      assert(response.status == 200, await response.text());
    }
    return (await response.json())["type"] == "pending_transaction";
  }

  /** Waits up to 10 seconds for a transaction to move past pending state */
  async waitForTransaction(txnHash: string) {
    let count = 0;
    while (await this.transactionPending(txnHash)) {
      assert(count < 10);
      await new Promise(resolve => setTimeout(resolve, 1000));
      count += 1;
      if (count >= 10) {
        throw new Error(`Waiting for transaction ${txnHash} timed out!`);
      }
    }
  }

  
```

{% endtab %}
{% endtabs %}

#### Step 2.3) 编写特定的逻辑[​](https://aptos.dev/tutorials/your-first-transaction#step-23-application-specific-logic) <a href="#step-23-application-specific-logic" id="step-23-application-specific-logic"></a>

下面演示如何从区块链读取数据以及如何写入数据，例如提交特定交易。[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface)

{% tabs %}
{% tab title="Python" %}

```python
    def account_balance(self, account_address: str) -> Optional[int]:
        """Returns the test coin balance associated with the account"""

        resources = self.account_resources(account_address)
        for resource in resources:
            if resource["type"] == "0x1::TestCoin::Balance":
                return int(resource["data"]["coin"]["value"])
        return None

    def transfer(self, account_from: Account, recipient: str, amount: int) -> str:
        """Transfer a given coin amount from a given Account to the recipient's account address.
        Returns the sequence number of the transaction used to transfer."""

        payload = {
            "type": "script_function_payload",
            "function": "0x1::TestCoin::transfer",
            "type_arguments": [],
            "arguments": [
                f"0x{recipient}",
                str(amount),
            ]
        }
        txn_request = self.generate_transaction(account_from.address(), payload)
        signed_txn = self.sign_transaction(account_from, txn_request)
        res = self.submit_transaction(signed_txn)
        return str(res["hash"])
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
    /// Returns the test coin balance associated with the account
    pub fn account_balance(&self, account_address: &str) -> Option<u64> {
        self.account_resources(account_address)
            .as_array()
            .unwrap()
            .iter()
            .find(|x| {
                x.get("type")
                    .map(|v| v.as_str().unwrap() == "0x1::TestCoin::Balance".to_string())
                    .unwrap_or(false)
            })
            .and_then(|coin| {
                coin.get("data")
                    .unwrap()
                    .get("coin")
                    .unwrap()
                    .get("value")
                    .unwrap()
                    .as_str()
                    .and_then(|s| s.parse::<u64>().ok())
            })
    }

    /// Transfer a given coin amount from a given Account to the recipient's account address.
    /// Returns the sequence number of the transaction used to transfer
    pub fn transfer(&self, account_from: &mut Account, recipient: &str, amount: u64) -> String {
        let payload = serde_json::json!({
            "type": "script_function_payload",
            "function": "0x1::TestCoin::transfer",
            "type_arguments": [],
            "arguments": [format!("0x{}", recipient), amount.to_string()]
        });
        let txn_request = self.generate_transaction(&account_from.address(), payload);
        let signed_txn = self.sign_transaction(account_from, txn_request);
        let res = self.submit_transaction(&signed_txn);

        res.get("hash").unwrap().as_str().unwrap().to_string()
    }
}
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
  /** Returns the test coin balance associated with the account */
  async accountBalance(accountAddress: string): Promise<number | null> {
    const resources = await this.accountResources(accountAddress);
    for (const key in resources) {
      const resource = resources[key];
      if (resource["type"] == "0x1::TestCoin::Balance") {
        return parseInt(resource["data"]["coin"]["value"]);
      }
    }
    return null;
  }

  /** Transfer a given coin amount from a given Account to the recipient's account address.
   Returns the sequence number of the transaction used to transfer. */
  async transfer(accountFrom: Account, recipient: string, amount: number): Promise<string> {
    const payload: { function: string; arguments: string[]; type: string; type_arguments: any[] } = {
      type: "script_function_payload",
      function: "0x1::TestCoin::transfer",
      type_arguments: [],
      arguments: [
        `0x${recipient}`,
        amount.toString(),
      ]
    };
    const txnRequest = await this.generateTransaction(accountFrom.address(), payload);
    const signedTxn = await this.signTransaction(accountFrom, txnRequest);
    const res = await this.submitTransaction(accountFrom, signedTxn);
    return res["hash"].toString();
  }

}
```

{% endtab %}
{% endtabs %}

### Step 3) Faucet 接口[​](https://aptos.dev/tutorials/your-first-transaction#step-3-faucet-interface) <a href="#step-3-faucet-interface" id="step-3-faucet-interface"></a>

区块链水龙头为账户提供一定数量的代币，这些代币可用于支付 gas fees 或在用户之间代币转账。如果帐户尚不存在，Aptos 水龙头还可以创建一个帐户。Aptos 水龙头接口需要一个以十六进制编码字符串表示的公钥。[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface)

{% tabs %}
{% tab title="Python" %}

```python
class FaucetClient:
    """Faucet creates and funds accounts. This is a thin wrapper around that."""

    def __init__(self, url: str, rest_client: RestClient) -> None:
        self.url = url
        self.rest_client = rest_client

    def fund_account(self, pub_key: str, amount: int) -> None:
        """This creates an account if it does not exist and mints the specified amount of
        coins into that account."""

        txns = requests.post(f"{self.url}/mint?amount={amount}&pub_key={pub_key}")
        assert txns.status_code == 200, txns.text
        for txn_hash in txns.json():
            self.rest_client.wait_for_transaction(txn_hash)
    
```

{% endtab %}

{% tab title="Rust" %}

```rust
pub struct FaucetClient {
    url: String,
    rest_client: RestClient,
}

impl FaucetClient {
    /// Faucet creates and funds accounts. This is a thin wrapper around that.
    pub fn new(url: String, rest_client: RestClient) -> Self {
        Self { url, rest_client }
    }

    /// This creates an account if it does not exist and mints the specified amount of coins into that account.
    pub fn fund_account(&self, pub_key: &str, amount: u64) {
        let res = reqwest::blocking::Client::new()
            .post(format!(
                "{}/mint?amount={}&pub_key={}",
                self.url, amount, pub_key
            ))
            .send()
            .unwrap();

        if res.status() != 200 {
            assert_eq!(
                res.status(),
                200,
                "{}",
                res.text().unwrap_or("".to_string()),
            );
        }
        for txn_hash in res.json::<serde_json::Value>().unwrap().as_array().unwrap() {
            self.rest_client
                .wait_for_transaction(txn_hash.as_str().unwrap())
        }
    }
}
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
/** Faucet creates and funds accounts. This is a thin wrapper around that. */
export class FaucetClient {
  url: string;
  restClient: RestClient;

  constructor(url: string, restClient: RestClient) {
    this.url = url;
    this.restClient = restClient;
  }

  /** This creates an account if it does not exist and mints the specified amount of
   coins into that account */
  async fundAccount(pubKey: string, amount: number) {
    const url = `${this.url}/mint?amount=${amount}&pub_key=${pubKey}`;
    const response = await fetch(url, {method: "POST"});
    if (response.status != 200) {
      assert(response.status == 200, await response.text());
    }
    const tnxHashes = await response.json() as Array<string>;
    for (const tnxHash of tnxHashes) {
      await this.restClient.waitForTransaction(tnxHash);
    }
  }

}
```

{% endtab %}
{% endtabs %}

### Step 4) 执行应用程序并验证[​](https://aptos.dev/tutorials/your-first-transaction#step-4-execute-the-application-and-verify)[​](https://aptos.dev/tutorials/your-first-transaction#step-2-rest-interface) <a href="#step-4-execute-the-application-and-verify" id="step-4-execute-the-application-and-verify"></a>

{% tabs %}
{% tab title="Python" %}

```python
if __name__ == "__main__":
    rest_client = RestClient(TESTNET_URL)
    faucet_client = FaucetClient(FAUCET_URL, rest_client)

    # Create two accounts, Alice and Bob, and fund Alice but not Bob
    alice = Account()
    bob = Account()

    print("\n=== Addresses ===")
    print(f"Alice: {alice.address()}")
    print(f"Bob: {bob.address()}")

    faucet_client.fund_account(alice.pub_key(), 1_000_000)
    faucet_client.fund_account(bob.pub_key(), 0)

    print("\n=== Initial Balances ===")
    print(f"Alice: {rest_client.account_balance(alice.address())}")
    print(f"Bob: {rest_client.account_balance(bob.address())}")

    # Have Alice give Bob 10 coins
    tx_hash = rest_client.transfer(alice, bob.address(), 1_000)
    rest_client.wait_for_transaction(tx_hash)

    print("\n=== Final Balances ===")
    print(f"Alice: {rest_client.account_balance(alice.address())}")
    print(f"Bob: {rest_client.account_balance(bob.address())}")
```

{% endtab %}

{% tab title="Rust" %}

```rust
fn main() -> () {
    let rest_client = RestClient::new(TESTNET_URL.to_string());
    let faucet_client = FaucetClient::new(FAUCET_URL.to_string(), rest_client.clone());

    // Create two accounts, Alice and Bob, and fund Alice but not Bob
    let mut alice = Account::new(None);
    let bob = Account::new(None);

    println!("\n=== Addresses ===");
    println!("Alice: 0x{}", alice.address());
    println!("Bob: 0x{}", bob.address());

    faucet_client.fund_account(&alice.pub_key(), 1_000_000);
    faucet_client.fund_account(&bob.pub_key(), 0);

    println!("\n=== Initial Balances ===");
    println!("Alice: {:?}", rest_client.account_balance(&alice.address()));
    println!("Bob: {:?}", rest_client.account_balance(&bob.address()));

    // Have Alice give Bob 10 coins
    let tx_hash = rest_client.transfer(&mut alice, &bob.address(), 1_000);
    rest_client.wait_for_transaction(&tx_hash);

    println!("\n=== Final Balances ===");
    println!("Alice: {:?}", rest_client.account_balance(&alice.address()));
    println!("Bob: {:?}", rest_client.account_balance(&bob.address()));
}
```

{% endtab %}

{% tab title="Typescript" %}

```typescript
/** run our demo! */
async function main() {
  const restClient = new RestClient(TESTNET_URL);
  const faucetClient = new FaucetClient(FAUCET_URL, restClient);

  // Create two accounts, Alice and Bob, and fund Alice but not Bob
  const alice = new Account();
  const bob = new Account();

  console.log("\n=== Addresses ===");
  console.log(`Alice: ${alice.address()}. Key Seed: ${Buffer.from(alice.signingKey.secretKey).toString("hex").slice(0, 64)}`);
  console.log(`Bob: ${bob.address()}. Key Seed: ${Buffer.from(bob.signingKey.secretKey).toString("hex").slice(0, 64)}`);

  await faucetClient.fundAccount(alice.pubKey(), 1_000_000_000);
  await faucetClient.fundAccount(bob.pubKey(), 0);

  console.log("\n=== Initial Balances ===");
  console.log(`Alice: ${await restClient.accountBalance(alice.address())}`);
  console.log(`Bob: ${await restClient.accountBalance(bob.address())}`);

  // Have Alice give Bob 1000 coins
  const txHash = await restClient.transfer(alice, bob.address(), 1_000);
  await restClient.waitForTransaction(txHash);

  console.log("\n=== Final Balances ===");
  console.log(`Alice: ${await restClient.accountBalance(alice.address())}`);
  console.log(`Bob: ${await restClient.accountBalance(bob.address())}`);
}

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

{% endtab %}
{% endtabs %}

执行后的结果输出:

```
=== Addresses ===
Alice: e26d69b8d3ff12874358da6a4082a2ac
Bob: c8585f009c8a90f22c6b603f28b9ed8c

=== Initial Balances ===
Alice: 1000000000
Bob: 0

=== Final Balances ===
Alice: 999998957
Bob: 1000
```

结果显示 Bob 从 Alice 那里收到了 1000 个硬币。 Alice 支付了 43 个硬币来支付 gas。 可以通过访问 REST 接口或区块浏览器来验证数据：

* Alice 的账户：[REST 接口](https://fullnode.devnet.aptoslabs.com/accounts/e26d69b8d3ff12874358da6a4082a2ac/resources)
* Bob 的账户：[区块浏览器](https://aptos-explorer.netlify.app/account/c8585f009c8a90f22c6b603f28b9ed8c)

{% hint style="info" %}
提醒：

devnet 会被定期重置，因此上面的链接可能无法正常查询。你可以自己尝试教程，然后在区块浏览器中查看他们的帐户。
{% endhint %}
