特殊
Ledger 硬件钱包
使用 Ledger 硬件钱包进行安全签名
前提条件
- 拥有 Ledger 硬件钱包(Ledger Nano X 或 Ledger Nano S)
- 安装 Ledger Live
- 通过 Ledger Live 安装 Æternity app (v0.4.4+)
- 将 Ledger 连接到电脑,解锁,并打开 Æternity 应用
基本用法
首先选择传输实现方式。Ledger 可通过 USB 或蓝牙连接,需要安装对应的 NPM 包。
步骤 1创建账户工厂
import { AccountLedgerFactory } from '@aeternity/aepp-sdk';
import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
// 创建 USB 传输
const transport = await TransportWebUSB.create();
// 创建 Ledger 账户工厂
const accountFactory = new AccountLedgerFactory(transport);
步骤 2初始化账户
// 通过索引创建账户实例
const account = await accountFactory.initialize(0);
console.log(account.address); // 'ak_2dA...'
// 签名交易(在 Ledger 设备上确认)
const signedTx = await account.signTransaction('tx_...');
console.log(signedTx); // 'tx_...' (带签名)
安全特性:私钥在 Ledger 设备上派生,永远不会离开设备。
账户验证(防止中间人攻击)
为防止 MITM 攻击,建议让用户确认应用显示的地址与 Ledger 设备屏幕上的地址一致:
// 触发验证流程,Ledger 会在屏幕上显示地址
const address = await accountFactory.getAddress(0, true);
// 用户在 Ledger 上确认后返回地址
console.log('已验证地址:', address);
账户持久化
保存账户的 index 和 address 属性,可在应用重启后恢复:
import { AccountLedger } from '@aeternity/aepp-sdk';
// 保存
const accountIndex = accountToPersist.index;
const accountAddress = accountToPersist.address;
localStorage.setItem('ledgerAccount', JSON.stringify({ index: accountIndex, address: accountAddress }));
// 恢复
const saved = JSON.parse(localStorage.getItem('ledgerAccount'));
const restoredAccount = new AccountLedger(transport, saved.index, saved.address);
账户发现
自动发现链上已使用过的账户(用于用户在新应用中恢复账户):
import { Node } from '@aeternity/aepp-sdk';
const node = new Node('https://testnet.aeternity.io');
// 发现所有链上活跃的账户
const accounts = await accountFactory.discover(node);
console.log('发现账户:');
accounts.forEach(acc => console.log(acc.address));
错误处理
用户在 Ledger 上拒绝操作时会抛出异常:
import { TransportStatusError } from '@ledgerhq/hw-transport';
try {
const signedTx = await account.signTransaction(tx);
} catch (err) {
if (err instanceof TransportStatusError) {
if (err.statusCode === 0x6985) {
console.log('用户在 Ledger 上拒绝了操作');
// err.statusText === 'CONDITIONS_OF_USE_NOT_SATISFIED'
}
}
}
完整示例