import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'Thrown when the device returns an error status word — anything other
than 0x9000.
class ApduError extends OpenloopError {
readonly statusWord: number // e.g. 0x6985
}| Status word | Constant name | Description | Common cause |
|---|---|---|---|
0x9000 |
— | Success | — |
0x6985 |
User rejected | The user rejected on the device | The user pressed the reject button |
0x6a80 |
Invalid data | Invalid data | A malformed BIP44 path or transaction |
0x6a82 |
App not found | App not found | The matching app is not open on the device |
0x6d00 |
Instruction not supported | Instruction not supported | The device firmware version is too old |
0x6e00 |
CLA not supported | CLA not supported | Invalid class byte |
0x6f00 |
Internal error | Internal error | An unexpected error inside the device |
0x61XX |
More data | More data available | Intermediate response while fetching a PSBT (not an error) |
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('The user rejected the signing request on the device')
} else {
console.log(`APDU error: 0x${err.statusWord.toString(16)}`)
}
}
}Thrown when a communication error occurs in the transport layer — USB, BLE, WebSocket, and so on.
class TransportError extends OpenloopError {
// message carries the error details
}| Message | Cause | What to do |
|---|---|---|
WalletConnect transport not connected |
WcTransport is not connected | Call open() before using it |
Transport type "..." not registered |
The transport is not registered with OpenloopSDK | Register it with registerTransport() |
No available transport found |
No transport is available | Check the registered transports and the environment |
Device disconnected |
The device was disconnected | Try reconnecting |
Exchange timeout |
The APDU response timed out | Check the state of the device — it may be waiting for approval |
import { OpenloopError, ApduError, TransportError } from '@openloop/sdk-core'
try {
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)
} catch (err) {
if (err instanceof ApduError) {
// Error response from the device
switch (err.statusWord) {
case 0x6985:
showMessage('Rejected on the device')
break
case 0x6a80:
showMessage('Invalid data')
break
default:
showMessage(`Device error: ${err.message}`)
}
} else if (err instanceof TransportError) {
// Communication error
showMessage('Lost the connection to the device. Please reconnect.')
} else if (err instanceof OpenloopError) {
// Any other SDK error
showMessage(`Error: ${err.message}`)
} else {
// Unexpected error
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')
}
// Usage example
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)) {
// A deliberate user action — no error display needed
return
}
// Show every other error
showError(err)
}