Day 3 交易与余额查询

掌握节点客户端使用和基本转账操作。

学习目标
  • 掌握节点客户端的使用
  • 理解交易的生命周期
  • 查询账户余额和 Nonce
  • 完成基本的转账操作

1. 节点 API 概述

API 类型端口用途权限
External3013公开查询只读
Internal3113管理操作需授权
WebSocket3014实时推送订阅

2. 交易生命周期

┌─────────────────────────────────────────────────────────────┐
│                    交易生命周期                              │
├─────────────────────────────────────────────────────────────┤
│   1. 构建交易 (TxBuilder)                                   │
│        │                                                    │
│        ▼                                                    │
│   2. 签名交易 (Account.sign)                                │
│        │                                                    │
│        ▼                                                    │
│   3. 广播交易 (client.broadcast_transaction)                │
│        │                                                    │
│        ▼                                                    │
│   4. 进入内存池 (Pending)                                   │
│        │                                                    │
│        ▼                                                    │
│   5. 打包进微块 (Included)                                  │
│        │                                                    │
│        ▼                                                    │
│   6. 确认 (Confirmed, 3+ 个关键块)                          │
└─────────────────────────────────────────────────────────────┘
2.1 交易类型
类型Tag说明
SpendTx12转账交易
ContractCreateTx42创建合约
ContractCallTx43调用合约
NamePreclaimTx33域名预注册
NameClaimTx32域名注册
OracleRegisterTx22注册预言机
2.2 费用计算
# 基础费用公式
fee = base_gas * gas_price + tx_size * gas_per_byte * gas_price

# 默认值
base_gas = 15000
gas_per_byte = 20
gas_price = 1000000000  # 1 Gwei

# 最小费用约 16000 * 1e9 = 0.000016 AE

3. 实践任务

任务 3.1: 查询账户余额
from aeternity.node import NodeClient, Config
from aeternity.utils import format_amount

client = NodeClient(Config(external_url='https://testnet.aeternity.io'))

# 使用测试网上已知有余额的账户
test_address = "ak_2swhLkgBPeeADxVTAVCJnZLY5NZtCFiM93JxsEaMuC59euuFRQ"

print(f"=== 查询账户: {test_address[:20]}... ===\n")

try:
    # 方法 1: 使用 get_account
    account = client.get_account(test_address)
    print(f"账户信息:")
    print(f"  地址: {account.address}")
    print(f"  余额: {account.balance} aettos")
    print(f"  余额: {format_amount(account.balance)}")
    print(f"  Nonce: {account.nonce}")
    print(f"  类型: {account.kind}")
    
    # 方法 2: 使用 get_balance(简化版)
    balance = client.get_balance(test_address)
    print(f"\nget_balance 返回: {balance}")
    
except Exception as e:
    print(f"查询失败: {e}")
    print("提示: 新账户在链上没有记录时返回 404")
任务 3.2: 获取 Nonce
from aeternity.node import NodeClient, Config
from aeternity.signing import Account

client = NodeClient(Config(external_url='https://testnet.aeternity.io'))

# 生成新账户(链上无记录)
new_account = Account.generate()
print(f"新账户: {new_account.get_address()}")

# 获取 Nonce
nonce = client.get_next_nonce(new_account.get_address())
print(f"下一个 Nonce: {nonce}")

# Nonce 说明
print(f"""
=== Nonce 说明 ===
- Nonce 是账户的交易计数器
- 每次交易后 Nonce +1
- 新账户的第一笔交易 Nonce = 1
- 交易必须按 Nonce 顺序执行
- 重复 Nonce 的交易会被拒绝
""")
任务 3.3: 构建转账交易
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.transactions import TxBuilder

client = NodeClient(Config(external_url='https://testnet.aeternity.io'))

# 准备账户
sender = Account.generate()
recipient = Account.generate()

print(f"发送方: {sender.get_address()}")
print(f"接收方: {recipient.get_address()}")

# 获取交易构建器
tx_builder = client.tx_builder

# 获取 Nonce 和 TTL
nonce = client.get_next_nonce(sender.get_address())
ttl = client.compute_absolute_ttl(100)  # 100 个区块后过期

print(f"\n交易参数:")
print(f"  Nonce: {nonce}")
print(f"  TTL: {ttl.absolute_ttl}")

# 构建 Spend 交易
tx = tx_builder.tx_spend(
    sender_id=sender.get_address(),
    recipient_id=recipient.get_address(),
    amount=1000000000000000000,  # 1 AE
    payload="Hello from Python SDK!",
    fee=0,  # 0 = 自动计算
    ttl=ttl.absolute_ttl,
    nonce=nonce
)

print(f"\n交易信息:")
print(f"  类型: {tx.data.tag}")
print(f"  哈希: {tx.hash}")
print(f"  费用: {tx.data.fee} aettos")

# 注意: 这个交易不会成功,因为发送方没有余额
# 实际转账需要先从水龙头获取测试币
任务 3.4: 发送转账交易
# 注意: 此示例需要有余额的账户
# 可以从 https://faucet.aepps.com 获取测试币

from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.utils import format_amount

client = NodeClient(Config(
    external_url='https://testnet.aeternity.io',
    blocking_mode=True  # 等待交易确认
))

# 转账流程(需要有余额的账户)
"""
# 1. 加载发送方账户
sender = Account.from_keystore("wallet.json", "password")

# 2. 检查余额
balance = client.get_balance(sender.get_address())
print(f"余额: {format_amount(balance)}")

# 3. 发送转账
tx = client.spend(
    account=sender,
    recipient_id="ak_recipient...",
    amount="0.1AE",          # 支持字符串格式
    payload="转账备注"
)

# 4. 获取交易结果
print(f"交易哈希: {tx.hash}")
print(f"实际费用: {tx.data.fee}")

# 5. 等待确认(blocking_mode=True 时自动等待)
block_height = client.wait_for_transaction(tx.hash)
print(f"确认区块: {block_height}")
"""
任务 3.5: 金额格式转换
from aeternity.utils import amount_to_aettos, format_amount

print("=== 金额转换 ===\n")

# aettos 是 AE 的最小单位
# 1 AE = 10^18 aettos

# 转换为 aettos
test_values = [
    1,           # 1 aetto
    1.5,         # 1.5 AE
    "1AE",       # 1 AE
    "1.5ae",     # 1.5 AE
    "100",       # 100 aettos
    0.001,       # 0.001 AE
]

print("转换为 aettos:")
for val in test_values:
    try:
        aettos = amount_to_aettos(val)
        print(f"  {repr(val):15} → {aettos:>25} aettos")
    except Exception as e:
        print(f"  {repr(val):15} → 错误: {e}")

# 格式化显示
print("\n格式化为 AE:")
test_aettos = [
    1000000000000000000,      # 1 AE
    1500000000000000000,      # 1.5 AE
    100000000000000000,       # 0.1 AE
]

for val in test_aettos:
    formatted = format_amount(val)
    print(f"  {val:>25} → {formatted}")

4. 常见问题

  • 检查账户余额是否足够支付金额 + 手续费
  • 使用 get_balance 确认当前余额
  • 测试网可从 水龙头 获取测试币

# 获取正确的 Nonce
nonce = client.get_next_nonce(account.get_address())
print(f"应使用 Nonce: {nonce}")

# 使用相对 TTL,让 SDK 自动计算
ttl = client.compute_absolute_ttl(100)  # 100 个区块后过期
知识检查点
  • 配置和使用节点客户端
  • 查询账户余额和 Nonce
  • 理解交易生命周期
  • 构建和发送 Spend 交易
  • 等待交易确认