エラー処理ガイド

エラークラス階層

Diagram 0

インポート

import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'

ApduError

デバイスがエラーのステータスワード(0x9000 以外)を返した場合にスローされます。

class ApduError extends OpenloopError {
  readonly statusWord: number  // 例: 0x6985
}

APDU ステータスワード表

ステータスワード 定数名 説明 よくある原因
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)}`)
    }
  }
}

TransportError

Transport レイヤー(USB/BLE/WebSocket 等)で通信エラーが発生した場合にスローされます。

class TransportError extends OpenloopError {
  // message にエラー詳細が含まれる
}

よくある TransportError

メッセージ 原因 対処法
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)
}

次のステップ