import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'デバイスがエラーのステータスワード(0x9000
以外)を返した場合にスローされます。
class ApduError extends OpenloopError {
readonly statusWord: number // 例: 0x6985
}| ステータスワード | 定数名 | 説明 | よくある原因 |
|---|---|---|---|
0x9000 |
— | 成功 | — |
0x6985 |
User rejected | ユーザーがデバイスで拒否 | ユーザーが拒否ボタンを押した |
0x6a80 |
Invalid data | 無効なデータ | 不正な BIP44 パスやトランザクション |
0x6a82 |
App not found | アプリが見つからない | デバイスで対応アプリが開いていない |
0x6d00 |
Instruction not supported | 命令未サポート | デバイスのファームウェアバージョンが古い |
0x6e00 |
CLA not supported | CLA 未サポート | 不正なクラスバイト |
0x6f00 |
Internal error | 内部エラー | デバイス内部の予期しないエラー |
0x61XX |
More data | 続きデータあり | PSBT 取得時の中間レスポンス(エラーではない) |
import { ApduError } from '@openloop/sdk-core'
try {
const sig = await eth.signPersonalMessage(DEFAULT_ETH_PATH, 'Hello')
} catch (err) {
if (err instanceof ApduError) {
if (err.statusWord === 0x6985) {
console.log('ユーザーがデバイスで署名を拒否しました')
} else {
console.log(`APDU エラー: 0x${err.statusWord.toString(16)}`)
}
}
}Transport レイヤー(USB/BLE/WebSocket 等)で通信エラーが発生した場合にスローされます。
class TransportError extends OpenloopError {
// message にエラー詳細が含まれる
}| メッセージ | 原因 | 対処法 |
|---|---|---|
WalletConnect transport not connected |
WcTransport が未接続 | open() を呼んでから使用する |
Transport type "..." not registered |
OpenloopSDK に未登録の Transport | registerTransport() で登録する |
No available transport found |
利用可能な Transport がない | Transport の登録と環境を確認 |
Device disconnected |
デバイスが切断された | 再接続を試みる |
Exchange timeout |
APDU 応答タイムアウト | デバイスの状態を確認(承認待ちかもしれない) |
import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'
try {
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)
} catch (err) {
if (err instanceof ApduError) {
// デバイスからのエラー応答
switch (err.statusWord) {
case 0x6985:
showMessage('デバイスで拒否されました')
break
case 0x6a80:
showMessage('無効なデータです')
break
default:
showMessage(`デバイスエラー: ${err.message}`)
}
} else if (err instanceof TransportError) {
// 通信エラー
showMessage('デバイスとの接続が切れました。再接続してください。')
} else if (err instanceof OpenloopError) {
// その他の SDK エラー
showMessage(`エラー: ${err.message}`)
} else {
// 予期しないエラー
throw err
}
}async function withRetry<T>(
fn: () => Promise<T>,
reconnect: () => Promise<void>,
maxRetries: number = 1
): Promise<T> {
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn()
} catch (err) {
if (err instanceof TransportError && i < maxRetries) {
await reconnect()
continue
}
throw err
}
}
throw new Error('Unreachable')
}
// 使用例
const { address } = await withRetry(
() => eth.getAddress(DEFAULT_ETH_PATH),
async () => {
transport = await WebHidTransport.reconnect()
eth = new EthereumApp(transport!)
}
)function isUserRejected(err: unknown): boolean {
return err instanceof ApduError && err.statusWord === 0x6985
}
try {
const sig = await eth.signTransaction(DEFAULT_ETH_PATH, rawTx)
} catch (err) {
if (isUserRejected(err)) {
// ユーザーの意図的な操作 — エラー表示不要
return
}
// その他のエラーは表示
showError(err)
}