你的第一笔交易
本教程介绍如何生成交易并将其提交到 Aptos 区块链,并验证这些提交的交易。本教程中使用的transfer-coin
示例是使用 Aptos SDK 构建的。
第 1 步:选择一个 SDK
从以下列表中安装您喜欢的 SDK:
第 2 步:运行示例
克隆 aptos-core 存储库:
git clone https://github.com/aptos-labs/aptos-core.git
- Typescript
- Python
- Rust
切换到 Typescript SDK 示例目录:
cd ~/aptos-core/ecosystem/typescript/sdk/examples/typescript
安装依赖:
yarn install
运行 transfer_coin
示例:
yarn run transfer_coin
切换到 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
第 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 和水龙头客户端。
- 创建两个帐户:Alice 和 Bob。
- 通过水龙头为 Alice 的账户提供资金和创建。
- 从水龙头创建 Bob 的帐户。
- 从 Alice 向 Bob 转移 1000 枚 coin。
- Alice 为进行转账支付的 4 枚 gas coin。
- 从 Alice 向 Bob 转移 1000 枚 coin。
- Alice 支付的额外 4 枚 gas 用于进行转账。
接下来,请参阅下面用于完成上述步骤的 SDK 功能的演练。
第 4 步:深入 SDK
transfer-coin
示例代码使用辅助函数与 REST API 进行交互。 本节回顾每个调用并深入了解功能。
- Typescript
- Python
- Rust
有关完整代码,请参阅 Typescript transfer-coin
您按照以下步骤操作。
有关完整代码,请参阅 Python transfer-coin
您按照以下步骤操作。
请参阅 Rust transfer-coin
,您按照以下步骤操作。
步骤 4.1:初始化客户端
第一步,transfer-coin
示例初始化 REST 和水龙头客户端。
- REST 客户端与 REST API 交互,以及
- 水龙头客户端与 devnet Faucet 服务交互以创建和注资帐户。
- Typescript
- Python
- Rust
const client = new AptosClient(NODE_URL);
const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
使用 API 客户端,我们可以创建一个 CoinClient
,我们将其用于常见的 coin 操作,例如转账和检查余额。
const coinClient = new CoinClient(client);
common.ts
初始化 URL 值,如下所示:
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";
rest_client = RestClient(NODE_URL)
faucet_client = FaucetClient(FAUCET_URL, rest_client)
common.py
将这些值初始化如下:
NODE_URL = os.getenv("APTOS_NODE_URL", "https://fullnode.devnet.aptoslabs.com/v1")
FAUCET_URL = os.getenv(
"APTOS_FAUCET_URL",
"https://tap.devnet.prod.gcp.aptosdev.com", # "https://faucet.testnet.aptoslabs.com"
)
let rest_client = Client::new(NODE_URL.clone());
let faucet_client = FaucetClient::new(FAUCET_URL.clone(), NODE_URL.clone());
使用 API 客户端,我们可以创建一个 CoinClient
,我们将其用于常见的 coin 操作,例如转移 coin 和检查余额。
let coin_client = CoinClient::new(&rest_client);
在示例中,我们将 URL 值初始化为:
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()
});
默认情况下,这两个服务的 URL 都指向 Aptos devnet 服务。 但是,可以使用以下环境变量配置它们:
APTOS_NODE_URL
APTOS_FAUCET_URL
步骤 4.2:创建本地帐户
下一步是在本地创建两个帐户。 Accounts 代表链上和链下状态。 链下状态由地址和用于验证所有权的公钥、私钥对组成。 此步骤演示如何生成该链下状态。
- Typescript
- Python
- Rust
const alice = new AptosAccount();
const bob = new AptosAccount();
alice = Account.generate()
bob = Account.generate()
let mut alice = LocalAccount::generate(&mut rand::rngs::OsRng);
let bob = LocalAccount::generate(&mut rand::rngs::OsRng);
步骤 4.3:创建区块链账户
在 Aptos 中,每个帐户都必须具有链上表示,以支持接收代币和 coin 以及与其他 dApp 进行交互。 账户代表了存储资产的媒介,因此必须明确创建。 此示例利用 Faucet 创建和资助 Alice 的帐户,并且只创建 Bob 的帐户:
- Typescript
- Python
- Rust
await faucetClient.fundAccount(alice.address(), 100_000_000);
await faucetClient.fundAccount(bob.address(), 0);
faucet_client.fund_account(alice.address(), 100_000_000)
faucet_client.fund_account(bob.address(), 0)
faucet_client
.fund(alice.address(), 100_000_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:阅读余额
在此步骤中,SDK 将单个调用转换为查询资源并从该资源读取字段的过程。
- Typescript
- Python
- Rust
console.log(`Alice: ${await coinClient.checkBalance(alice)}`);
console.log(`Bob: ${await coinClient.checkBalance(bob)}`);
SDK 中的 CoinClient
中的 checkBalance
函数查询了 AptosCoin 的 CoinStore 资源并读取当前存储的值:
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);
}
print(f"Alice: {rest_client.account_balance(alice.address())}")
print(f"Bob: {rest_client.account_balance(bob.address())}")
SDK 会在 CoinStore 资源中查询 AptosCoin 并读取当前存储的值:
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"]
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")?
);
SDK 会在 CoinStore 资源中查询 AptosCoin 并读取当前存储的值:
let balance = self
.get_account_resource(address, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
.await?;
步骤 4.5:转移
与上一步一样,这是另一个辅助步骤,用于构建将 coin 从 Alice 转移到 Bob 的交易。 对于正确生成的交易,API 将返回一个交易哈希,可用于后续步骤检查交易状态。 Aptos 区块链确实在提交时执行了一些验证检查,如果其中任何一个失败,用户将收到一个错误。 这些验证包括交易签名、未使用的序列号以及将交易提交到适当的链。
- Typescript
- Python
- Rust
let txnHash = await coinClient.transfer(alice, bob, 1_000, { gasUnitPrice: BigInt(100) });
transfer
函数生成一个交易参数,并让客户端签名、发送和等待它:
async transfer(
from: AptosAccount,
to: AptosAccount,
amount: number | bigint,
extraArgs?: OptionalTransactionArgs & {
// The coin type to use, defaults to 0x1::aptos_coin::AptosCoin
coinType?: string;
},
): 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);
}
在客户端中,generateSignSubmitTransaction
正在执行此操作:
const rawTransaction = await this.generateRawTransaction(sender.address(), payload, extraArgs);
const bcsTxn = AptosClient.generateBCSTransaction(sender, rawTransaction);
const pendingTransaction = await this.submitSignedBCSTransaction(bcsTxn);
return pendingTransaction.hash;
将以上内容分解为几部分:
transfer
内部是Coin Move合约中的EntryFunction
,即 Move 中可直接调用的入口函数。- Move函数存储在coin合约上:
0x1::coin
。 - 因为Coin合约可以被其他币使用,所以转账时必须明确指定要转账的币种。 如果未使用
coinType
指定,则默认为0x1::aptos_coin::AptosCoin
。
txn_hash = rest_client.transfer(alice, bob.address(), 1_000)
Python SDK 在内部生成、签署和提交交易:
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)
将以上内容分解为几部分:
transfer
内部是Coin Move合约中的EntryFunction
,即 Move 中可直接调用的入口函数。- Move函数存储在coin合约上:
0x1::coin
。 - 因为Coin合约可以被其他币使用,所以转账必须明确使用
TypeTag
来定义要转账的币。 - 交易参数必须放在带有类型说明符(
Serializer.{type}
)的TransactionArgument
中,这将在交易生成时将值序列化为适当的类型。
let txn_hash = coin_client
.transfer(&mut alice, bob.address(), 1_000, None)
.await
.context("Failed to submit transaction to transfer coins")?;
Rust SDK 在内部生成、签署和提交交易:
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())
将以上内容分解为几部分:
- 首先,我们获取构建交易有效 payload 所必需的链 ID。
transfer
内部是Coin Move合约中的EntryFunction
,即 Move 中可直接调用的入口函数。- Move函数存储在coin合约上:
0x1::coin
。 - 因为Coin合约可以被其他币使用,所以转账必须明确使用
TypeTag
来定义要转账的币。 - 交易参数,如
to_account
和amount
,必须编码为BCS才能与TransactionBuilder
一起使用。
步骤 4.6:等待交易成功
- Typescript
- Python
- Rust
在 Typescript 中,只需调用 coinClient.transfer
就足以等待交易完成。 一旦处理(成功或不成功),该函数将返回 API 返回的 Transaction
,如果处理时间超过超时,则抛出错误。
如果您希望在事务未成功提交时将其抛出,则可以在调用 transfer
时将 checkSuccess
设置为 true:
await client.waitForTransaction(txnHash);
交易哈希可用于查询交易状态:
rest_client.wait_for_transaction(txn_hash)
交易哈希可用于查询交易状态:
rest_client
.wait_for_transaction(&txn_hash)
.await
.context("Failed when waiting for the transfer transaction")?;