Aptos Wiki
MoveMove 社区Aptos 链接合集Sui WikiStarcoin Wiki
  • 🎉欢迎
  • 💡概览
    • 起源
    • 愿景
    • 主网路线图
    • 激励测试网(1)
    • 技术路线图
    • 激励测试网(2)
    • 激励测试网(3)
    • 激励测试网(4)
  • 📌基础
    • 账户(Account)
    • 事件(Events)
    • 证明(Proof)
    • Gas 和交易费
    • 节点网络和同步
    • 验证者节点
    • 全节点
    • 交易和状态
  • 🔄交易
    • 与 Aptos 区块链交互
  • 📓教程
    • Petra 钱包插件安装使用
    • 教程指引
    • 您的第一笔交易
    • 您的第一个 Move 模块
    • 您的第一个 NFT
    • 运行本地网络
    • 开发网 Devnet
      • 运行全节点(官方教程)
      • 运行全节点(社区教程)
        • Windows
        • Linux
    • 测试网 Testnet
      • 社区教程
        • AIT-3 新功能
        • 节点要求
        • AIT-3 步骤
        • 使用 Docker
        • 全节点搭建
        • 加入测试网
        • 离开激励测试网
  • 🖊️博客
    • Block-STM:我们如何在 Aptos 区块链上每秒执行超过 16 万笔交易
  • 🗻生态
    • Aptos 宣布资助计划!
    • Aptos 生态项目汇总
      • 聚合器
        • Hippo Labs
      • Defi
        • 🟢Pontem Network
        • 🟢Vial Protocol
        • 1KX Protocol
        • Seam Money
        • Aries Markets
        • Empo Finance
        • Ultima protocol
        • Econia
        • Zaptos Finance
        • Laminar Market
        • Thala Labs
        • AptoSwap
        • Aptoslend
        • ASwap
      • 基础设施
        • 🟢Aptos 域名服务
        • Nutrios
        • Dialect
        • Switchboard
      • NFT
        • 🟢Topaz NFT Marketplace
        • TokenMasksLabs
        • Clone Protocol
        • Aptos Ape Society
      • 钱包
        • 🟢Fewcha Wallet
        • 🟢Martian Wallet
        • Volt.id wallet
        • ONTO Wallet
        • Hive Wallet
        • Blocto
      • 工具
        • ChainIDE
        • Paymagic
        • Aptosphere
        • Saber Labs
  • ❓问题(FAQ)
    • 常见问题
    • 如何分享自己的节点
    • 如何运行多个节点
    • 如何修改节点配置文件
Powered by GitBook
On this page
  • Step 1) 创建一个账户的表达形式​
  • Step 2) REST 接口
  • Step 3) Faucet 接口​
  • Step 4) 执行应用程序并验证​​
  1. 教程

您的第一笔交易

Previous教程指引Next您的第一个 Move 模块

Last updated 3 years ago

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

  • 创建一个帐户的表达形式

  • 围绕 REST 接口准备一个包装类

  • 在 Faucet 接口周围准备一个包装类

  • 将它们组合成一个应用程序,执行并验证

请从下面的代码仓库中 clone 最新代码!

以下是示例代码,可以从 github 下载:

对于本教程,将重点关注 first_transaction.py。

可以在找到代码

对于本教程,将重点关注 first_transaction.py。

可以在找到代码

对于本教程,将重点关注 first_transaction.py。

可以在找到代码

Step 1) 创建一个账户的表达形式

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

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()
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())
    }
}
/** 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");
  }
}

Step 2) REST 接口

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

    def __init__(self, url: str) -> None:
        self.url = url
#[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 }
    }
    
/** A wrapper around the Aptos-core Rest API */
export class RestClient {
  url: string;

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

    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()
    /// 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()
    }
    
  /** 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();
  }

    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;
        }
    }
    
    /// 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;
        }
    }
    
  /** 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!`);
      }
    }
  }

  

    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"])
    
    /// 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()
    }
}
  /** 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();
  }

}

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)
    
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())
        }
    }
}
/** 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);
    }
  }

}

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())}")
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()));
}
/** 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));
}

执行后的结果输出:

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

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

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

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

提醒:

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

Aptos 公开了一个用于与区块链交互的 。 虽然可以直接读取来自 REST 接口的数据,但以下代码片段演示了一种更优雅的方法。下一组代码片段演示了如何使用 REST 接口从 FullNode 检索分类帐数据,包括帐户和帐户资源数据。它还演示了如何使用 REST 接口来构建由 JSON 格式表示的签名交易。

Step 2.1) 读取账户

以下是用于查询帐户数据的包装类。

Step 2.2) 提交一笔交易

下面演示了构建、签名和等待交易的核心功能。

Step 2.3) 编写特定的逻辑

下面演示如何从区块链读取数据以及如何写入数据,例如提交特定交易。

Step 3) Faucet 接口

区块链水龙头为账户提供一定数量的代币,这些代币可用于支付 gas fees 或在用户之间代币转账。如果帐户尚不存在,Aptos 水龙头还可以创建一个帐户。Aptos 水龙头接口需要一个以十六进制编码字符串表示的公钥。

Step 4) 执行应用程序并验证

Alice 的账户:

Bob 的账户:

📓
这里
这里
这里
​
REST 接口
​
​
​
​
​
​
​
​
​
​
​
REST 接口
区块浏览器