Day 6
AEX-9 代币标准
中级阶段 · 预计学习时间 90 分钟
学习目标
- 理解 AEX-9 代币标准
- 部署 AEX-9 代币合约
- 执行代币转账和授权
- 查询代币信息和余额
AEX-9 标准概述
什么是 AEX-9
AEX-9 是 Aeternity 的同质化代币标准,类似于以太坊的 ERC-20。
| 特性 | 说明 |
|---|---|
| 同质化 | 每个代币完全相同 |
| 可分割 | 支持小数点(通常 18 位) |
| 可转账 | 支持点对点转账 |
| 可授权 | 支持第三方转账 |
标准接口
// 必须实现的接口
entrypoint meta_info() : meta_info
entrypoint total_supply() : int
entrypoint balance(account : address) : int
entrypoint allowance(owner : address, spender : address) : int
stateful entrypoint transfer(to : address, value : int)
stateful entrypoint approve(spender : address, value : int)
stateful entrypoint transfer_from(from : address, to : address, value : int)
// 扩展接口(可选)
stateful entrypoint create_allowance(spender : address, value : int)
stateful entrypoint change_allowance(spender : address, value : int)
stateful entrypoint reset_allowance(spender : address)
// 可选功能
stateful entrypoint mint(account : address, value : int)
stateful entrypoint burn(value : int)
标准事件
datatype event =
Transfer(address, address, int) // from, to, value
| Approval(address, address, int) // owner, spender, value
| Mint(address, int) // account, value
| Burn(address, int) // account, value
AEX-9 合约实现
完整合约代码
@compiler >= 6
include "Option.aes"
contract AEX9Token =
record meta_info = {
name : string,
symbol : string,
decimals : int
}
record state = {
owner : address,
total_supply : int,
balances : map(address, int),
allowances : map(address, map(address, int)),
meta_info : meta_info
}
datatype event =
Transfer(address, address, int)
| Approval(address, address, int)
| Mint(address, int)
| Burn(address, int)
entrypoint init(name : string, symbol : string, decimals : int, initial_supply : int) = {
owner = Call.caller,
total_supply = initial_supply,
balances = { [Call.caller] = initial_supply },
allowances = {},
meta_info = { name = name, symbol = symbol, decimals = decimals }
}
// ===== 查询接口 =====
entrypoint meta_info() : meta_info = state.meta_info
entrypoint total_supply() : int = state.total_supply
entrypoint balance(account : address) : int =
Map.lookup_default(account, state.balances, 0)
// ===== 转账接口 =====
stateful entrypoint transfer(to : address, value : int) =
internal_transfer(Call.caller, to, value)
// ... 更多实现详见完整合约
实践任务
任务 6.1: 部署 AEX-9 代币
from aeternity.node import NodeClient, Config
from aeternity.signing import Account
from aeternity.contract_native import ContractManager
client = NodeClient(Config(external_url='https://testnet.aeternity.io'))
# 加载账户
# account = Account.from_keystore("wallet.json", "password")
# AEX-9 合约源码(简化版)
aex9_source = '''
@compiler >= 6
contract AEX9Token =
record meta_info = { name : string, symbol : string, decimals : int }
record state = {
meta_info : meta_info,
total_supply : int,
balances : map(address, int),
allowances : map(address, map(address, int))
}
entrypoint init(name : string, symbol : string, decimals : int, supply : int) = {
meta_info = { name = name, symbol = symbol, decimals = decimals },
total_supply = supply,
balances = { [Call.caller] = supply },
allowances = {}
}
'''
# 创建 ContractManager
manager = ContractManager(
client=client,
account=account,
source=aex9_source
)
# 部署代币
tx = manager.deploy(
'"MyToken"', # 代币名称
'"MTK"', # 代币符号
'18', # 小数位数
'1000000000000000000000000' # 100万代币 (18位小数)
)
print(f"代币合约地址: {manager.contract_id}")
print(f"部署交易: {tx.hash}")
任务 6.2: 代币转账
# 假设已部署代币合约
contract_id = "ct_xxx..."
recipient = "ak_yyy..."
# 使用 ContractManager
manager = ContractManager(
client=client,
account=account,
contract_id=contract_id,
source=aex9_source
)
# 转账 100 个代币(18位小数)
amount = "100000000000000000000" # 100 * 10^18
tx = manager.call("transfer", f'"{recipient}"', amount)
print(f"转账交易: {tx.hash}")
# 查询余额
sender_balance = manager.call_static("balance", f'"{account.get_address()}"')
recipient_balance = manager.call_static("balance", f'"{recipient}"')
print(f"发送者余额: {sender_balance}")
print(f"接收者余额: {recipient_balance}")
任务 6.3: 授权和转账 (Approve + TransferFrom)
# 场景:允许第三方(如交易所)代替你转账
# 步骤 1: 持有者授权
owner_manager = ContractManager(
client=client,
account=owner_account,
contract_id=contract_id,
source=aex9_source
)
# 授权 spender 可以转账 1000 个代币
spender_address = "ak_spender..."
amount = "1000000000000000000000" # 1000 * 10^18
tx = owner_manager.call("approve", f'"{spender_address}"', amount)
print(f"授权交易: {tx.hash}")
# 步骤 2: 被授权者执行转账
spender_manager = ContractManager(
client=client,
account=spender_account, # 被授权者的账户
contract_id=contract_id,
source=aex9_source
)
# 从 owner 转账到 recipient
recipient = "ak_recipient..."
transfer_amount = "500000000000000000000" # 500 * 10^18
tx = spender_manager.call(
"transfer_from",
f'"{owner_account.get_address()}"', # from
f'"{recipient}"', # to
transfer_amount # amount
)
print(f"转账交易: {tx.hash}")
任务 6.4: 代币信息格式化
def format_token_amount(amount: int, decimals: int, symbol: str = "") -> str:
"""格式化代币金额"""
value = amount / (10 ** decimals)
if value == int(value):
formatted = f"{int(value):,}"
else:
formatted = f"{value:,.{decimals}f}".rstrip('0').rstrip('.')
return f"{formatted} {symbol}".strip()
# 示例
decimals = 18
symbol = "MTK"
test_amounts = [
1000000000000000000, # 1 MTK
1500000000000000000, # 1.5 MTK
100000000000000000000, # 100 MTK
]
for amount in test_amounts:
formatted = format_token_amount(amount, decimals, symbol)
print(f"{amount:>30} → {formatted}")
练习题
目标:编写脚本,向多个地址空投代币。
def airdrop(manager, recipients, amount_each):
"""向多个地址空投代币"""
results = []
for recipient in recipients:
try:
tx = manager.call("transfer", f'"{recipient}"', str(amount_each))
results.append({'address': recipient, 'status': 'success', 'tx': tx.hash})
except Exception as e:
results.append({'address': recipient, 'status': 'failed', 'error': str(e)})
return results
# 使用示例
# recipients = ["ak_1...", "ak_2...", "ak_3..."]
# amount = 1000000000000000000 # 1 token
# results = airdrop(manager, recipients, amount)
目标:扩展 AEX-9 合约,添加锁仓功能。
// 在 state 中添加
record lock_info = { amount : int, unlock_height : int }
// locks : map(address, lock_info)
// 添加锁仓函数
stateful entrypoint lock(amount : int, duration : int) =
let sender = Call.caller
require(balance(sender) >= amount, "INSUFFICIENT_BALANCE")
let unlock_height = Chain.block_height + duration
put(state{
balances = state.balances{ [sender] = balance(sender) - amount },
locks = state.locks{ [sender] = { amount = amount, unlock_height = unlock_height }}
})
// 添加解锁函数
stateful entrypoint unlock() =
let sender = Call.caller
switch(Map.lookup(sender, state.locks))
None => abort("NO_LOCKED_TOKENS")
Some(info) =>
require(Chain.block_height >= info.unlock_height, "STILL_LOCKED")
put(state{
balances = state.balances{ [sender] = balance(sender) + info.amount },
locks = Map.delete(sender, state.locks)
})
常见问题
错误: BALANCE_NOT_ENOUGH 或 INSUFFICIENT_BALANCE
解决: 检查发送者余额是否足够
错误: ALLOWANCE_NOT_ENOUGH
解决: 确保 approve 的额度足够
问题: 代币金额显示不正确
解决: 注意 decimals,通常是 18 位
# 1 代币 = 10^18 最小单位
amount_in_tokens = 100
amount_in_smallest = amount_in_tokens * (10 ** 18)
知识检查点
完成 Day 6 后,你应该能够:
相关资源