您的第一笔交易
本教程详细介绍了如何生成、提交和验证提交到 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 接口
Aptos 公开了一个用于与区块链交互的 REST 接口。 虽然可以直接读取来自 REST 接口的数据,但以下代码片段演示了一种更优雅的方法。下一组代码片段演示了如何使用 REST 接口从 FullNode 检索分类帐数据,包括帐户和帐户资源数据。它还演示了如何使用 REST 接口来构建由 JSON 格式表示的签名交易。
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;
}
Step 2.1) 读取账户
以下是用于查询帐户数据的包装类。
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();
}
Step 2.2) 提交一笔交易
下面演示了构建、签名和等待交易的核心功能。
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!`);
}
}
}
Step 2.3) 编写特定的逻辑
下面演示如何从区块链读取数据以及如何写入数据,例如提交特定交易。
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();
}
}
Step 3) Faucet 接口
区块链水龙头为账户提供一定数量的代币,这些代币可用于支付 gas fees 或在用户之间代币转账。如果帐户尚不存在,Aptos 水龙头还可以创建一个帐户。Aptos 水龙头接口需要一个以十六进制编码字符串表示的公钥。
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 会被定期重置,因此上面的链接可能无法正常查询。你可以自己尝试教程,然后在区块浏览器中查看他们的帐户。
Last updated