A complete list of type definitions, interfaces, classes, and
constants. Unless stated otherwise, everything here is exported from
@openloop/sdk-core.
@openloop/sdk-core/verifyThe interface that abstracts communication with the device. Every transport implements it.
interface ITransport {
open(): Promise<void>
close(): Promise<void>
isConnected(): boolean
exchange(apdu: Uint8Array): Promise<Uint8Array>
lock(): Promise<void>
unlock(): Promise<void>
}| Method | Description |
|---|---|
open() |
Open the connection to the device |
close() |
Close the connection |
isConnected() |
Whether a connection is active |
exchange(apdu) |
Send an APDU command and receive the response |
lock() |
Acquire exclusive access to the device (for multi-step operations) |
unlock() |
Release exclusive access |
interface TransportStatus {
connected: boolean
deviceName?: string
deviceId?: string
}type TransportEventType = 'connected' | 'disconnected' | 'error'
interface TransportEvent {
type: TransportEventType
error?: Error
}
type TransportEventHandler = (event: TransportEvent) => voidThe transport information returned by
OpenloopSDK.discover().
interface TransportInfo {
type: string // Registered name (e.g. 'webhid')
name: string // Display name (e.g. 'WebHID (USB)')
available: boolean // Whether it is available
}type TransportFactory = () => Promise<ITransport>The ECDSA signature result for Ethereum / TRON.
interface SignatureResult {
v: number // Recovery ID
r: string // R value (32 bytes hex, no 0x prefix)
s: string // S value (32 bytes hex, no 0x prefix)
}The Bitcoin BIP-137 message signature result.
interface BtcMessageSignature {
v: number // Recovery ID (35-38: P2WPKH native SegWit)
r: string // R value (32 bytes hex)
s: string // S value (32 bytes hex)
signature: string // Base64 of the full signature (V || R || S, 65 bytes)
}The per-input BIP32 path for a Bitcoin PSBT.
interface InputSigningPath {
index: number // Input index within the PSBT
path: string // BIP32 path (e.g. "84'/1'/0'/0/5")
}interface DeviceInfo {
connected: boolean
path?: string
vendorId?: number
productId?: number
}The XRP signature result.
interface XrpSignatureResult {
signature: string // Signature hex (Ed25519: 64B, secp256k1: DER, variable length)
}type XrpCurve = 'ed25519' | 'secp256k1'interface WcSession {
topic: string
}interface IWcSignClient {
request<T>(params: {
topic: string
chainId: string
request: { method: string; params: unknown }
}): Promise<T>
}interface WcTransportOptions {
client: IWcSignClient
session: WcSession
chainId?: string // Default: "eip155:1"
}class EthereumApp {
constructor(transport: ITransport)
getAddress(path: string): Promise<{ publicKey: string; address: string }>
signPersonalMessage(path: string, message: string, chainId?: number): Promise<SignatureResult>
signTransaction(path: string, rawTx: string): Promise<SignatureResult>
signTypedData(
path: string, domainSeparatorHash: string, messageHash: string, chainId?: number
): Promise<SignatureResult>
signAuthorization(path: string, authorizationRlp: string): Promise<SignatureResult>
}class BitcoinApp {
constructor(transport: ITransport)
getAddress(testnet?: boolean): Promise<{ publicKey: string; address: string; chainCode: string }>
getAddressWithPath(path: string): Promise<{ publicKey: string; address: string; chainCode: string }>
getAccountXpub(coinType: number): Promise<{ publicKey: string; chainCode: string }>
getMasterFingerprint(): Promise<string> // hash160(master pubkey)[:4], 8-char hex (e.g. "631698e0")
signMessage(message: string, path: string): Promise<BtcMessageSignature>
signPsbt(psbt: Uint8Array | string): Promise<Uint8Array>
signPsbtHex(psbt: string): Promise<string>
signPsbtWithPaths(psbt: string, inputPaths: InputSigningPath[]): Promise<string>
}class SolanaApp {
constructor(transport: ITransport)
getAddress(path: string): Promise<{ publicKey: string; address: string }>
signTransaction(path: string, txBytes: Uint8Array): Promise<string>
signOffchainMessage(path: string, messageBytes: Uint8Array): Promise<string>
}class TronApp {
constructor(transport: ITransport)
getAddress(path: string): Promise<{ publicKey: string; address: string }>
signTransaction(path: string, rawDataHex: string): Promise<SignatureResult>
signMessage(path: string, message: string): Promise<SignatureResult>
}class XrpApp {
constructor(transport: ITransport)
getAddress(path: string, curve?: XrpCurve): Promise<{ publicKey: string; address: string }>
signTransaction(path: string, txBlob: string): Promise<XrpSignatureResult>
}The class for utility commands specific to the Openloop firmware. It provides the Openloop-specific commands exposed under CLA=0xF0 (CLA_OPENLOOP).
class OpenloopApp {
constructor(transport: ITransport)
/**
* Sends GET_DEVICE_INFO (F0 A0) and retrieves identity, state, and feature information.
*
* Return value:
* - Returns null for older Openloop firmware (earlier than v0.91.13) and for
* devices that do not support the command
* - Throws on transport-layer errors
*/
getDeviceInfo(): Promise<OpenloopDeviceInfo | null>
}interface OpenloopDeviceInfo {
modelId: number // Hardware SKU ID (e.g. 0x0001 = Openloop V1)
hwRevision: number // PCB revision
mainFwVersion: { major: number; minor: number; patch: number }
recoveryFwVersion: { major: number; minor: number; patch: number }
state: number // 16-bit state bitmask (raw value)
stateBits: OpenloopDeviceState // Decoded view of state
features: number // 16-bit feature bitmask (implementation detail, subject to change)
}| Field | Type | Description |
|---|---|---|
modelId |
number |
Hardware SKU ID. Increments with every new SKU |
hwRevision |
number |
PCB revision number |
mainFwVersion |
{major,minor,patch} |
Version of the main firmware currently running |
recoveryFwVersion |
{major,minor,patch} |
Version of the recovery (factory) partition. All zeros when it cannot be read |
state |
number |
16-bit bitmask representing the current state (raw value) |
stateBits |
OpenloopDeviceState |
state decoded into an object |
features |
number |
16-bit bitmask representing the hardware features (raw value). The bit layout is an implementation detail of the firmware and may change between versions. Host apps should only store and forward the raw value |
The state field decoded into a human-readable
object.
interface OpenloopDeviceState {
walletProvisioned: boolean
language: number // 0=English, 1=Japanese, 2-7=reserved
usbEnabled: boolean
usbVidCompat: boolean // false=Native (0x303A), true=Compatible (0x2C97)
bleEnabled: boolean
blePaired: boolean
fidoEnabled: boolean
pivEnabled: boolean
}state bitmask layout (finalized in FW v0.91.14+):
| bit | Name | Meaning |
|---|---|---|
| 0 | WALLET_PROVISIONED |
A wallet has been created |
| 1-3 | LANG (3 bit) |
Language index (0=EN, 1=JA, …) |
| 4 | USB_ENABLED |
USB communication on |
| 5 | USB_VID_COMPAT |
USB VID = compatible mode (0x2C97). Native (0x303A) when 0 |
| 6 | BLE_ENABLED |
BLE communication on |
| 7 | BLE_PAIRED |
A paired BLE device exists |
| 8 | FIDO_ENABLED |
FIDO / passkey feature on |
| 9 | PIV_ENABLED |
PIV / PKCS#11 feature on |
| 10-15 | reserved | Reserved for future expansion, currently 0 |
More bits may be added in the future. To read a bit that
stateBits does not expose, mask the raw state
value directly.
import { OpenloopApp, formatOpenloopState } from '@openloop/sdk-core'
const app = new OpenloopApp(transport)
const info = await app.getDeviceInfo()
if (info === null) {
console.log('GET_DEVICE_INFO not supported (old firmware)')
} else {
console.log(`Model: 0x${info.modelId.toString(16).padStart(4, '0')}`)
console.log(`FW: ${info.mainFwVersion.major}.${info.mainFwVersion.minor}.${info.mainFwVersion.patch}`)
console.log(`State: ${formatOpenloopState(info.stateBits)}`)
if (!info.stateBits.walletProvisioned) {
// Branch for when no wallet has been created yet
}
}The @openloop/sdk-core/verify subpath. Utilities that
verify a signature returned by the device against a reference
implementation (viem / @noble). Transport-independent — USB, BLE,
AirGap, and WalletConnect all work. It is not part of the base
@openloop/sdk-core (viem is never forced on you); it ships
as a subpath instead.
import { verifyPersonalSign } from '@openloop/sdk-core/verify'
const result = verifyPersonalSign(message, sig, address)
// result: { recovered: string; expected?: string; ok: boolean }| Function | Chain | Verifies |
|---|---|---|
verifyPersonalSign(message, sig, expected?) |
EVM | EIP-191 personal_sign |
verifyTypedDataJson(typedDataJson, sig, expected?) |
EVM | EIP-712 (full JSON) |
verifyTypedDataHashes(domainHash, msgHash, sig, expected?) |
EVM | EIP-712 (pre-hashed) |
verifyTransaction(unsignedTxHex, sig, expected?) |
EVM | legacy / EIP-2718 typed tx (takes {v,r,s}) |
verifySignedTransaction(signedTxHex, expected?) |
EVM | Recovers the signer from a signed serialized tx (the return value of
WalletConnect eth_signTransaction) |
verifyAuth7702(authRlpHex, sig, expected?) |
EVM | EIP-7702 authorization |
verifyBtcMessage(message, sig, identity) |
BTC | BIP-137 message (identity = pubkey hex or BTC
address) |
verifyBtcPsbt(signedPsbtHex) |
BTC | Signed PSBT (BIP-143 P2WPKH; the pubkey comes from the PSBT) |
verifySolMessage(message, sigHex, pubkeyHex) |
SOL | ed25519 off-chain message |
verifySolTransaction(txHex, sigHex, pubkeyHex) |
SOL | ed25519 transaction |
verifyTronMessage(message, sig, identity) |
TRON | keccak256 TRON prefix (identity = pubkey hex or TRON
address) |
verifyTronTransaction(rawDataHex, sig, identity) |
TRON | sha256(raw_data) (identity = pubkey hex or TRON
address) |
verifyXrpTransaction(txBlobHex, curve, result, pubkeyHex) |
XRP | ed25519 / secp256k1 (for-signing blob + explicit pubkey) |
verifyXrpSignedTx(signedTxHex) |
XRP | Self-contained verification from a signed tx blob (extracts SigningPubKey / TxnSignature; no pubkey needed) |
Every one of them returns a VerifyResult:
{ recovered: string; expected?: string; ok: boolean }. Pass
expected and the check goes one step further, reporting
whether the recovered signer matches (ok).
About
identity(pubkey or address): for BTC / TRON the shape ofidentitydecides automatically whether to compare the public key recovered from the signature directly, or to derive a chain address from the recovered key and compare that. On routes where no public key is available — WalletConnect, for example — you can match by address.verifySignedTransaction(EVM) andverifyXrpSignedTx(XRP) recover the signer / public key from the signed data itself, so there is no pubkey to pass in from outside.
class OpenloopError extends Error {
name: 'OpenloopError'
}
class ApduError extends OpenloopError {
name: 'ApduError'
readonly statusWord: number
constructor(statusWord: number)
}
class TransportError extends OpenloopError {
name: 'TransportError'
constructor(message: string)
}The class that manages transport registration, discovery, and connection. Every method is static.
class OpenloopSDK {
static registerTransport(type: string, config: {
factory: TransportFactory
name: string
isAvailable: () => boolean | Promise<boolean>
}): void
static discover(): Promise<TransportInfo[]>
static connect(opts?: { transport?: string }): Promise<ITransport>
static getRegisteredTransports(): string[]
static clearTransports(): void
}class WcTransport implements ITransport {
constructor(options: WcTransportOptions)
open(): Promise<void>
close(): Promise<void>
isConnected(): boolean
lock(): Promise<void>
unlock(): Promise<void>
exchange(apdu: Uint8Array): Promise<Uint8Array>
}const OPENLOOP_VENDOR_ID = 0x303a // Espressif VID
const OPENLOOP_PRODUCT_ID = 0x8341 // Openloop PID
const LEDGER_VENDOR_ID = 0x2c97 // Ledger SAS VID
const LEDGER_PRODUCT_ID = 0x1011 // Nano S compatibleconst DEFAULT_ETH_PATH = "44'/60'/0'/0/0"
const BTC_MAINNET_PATH = "84'/0'/0'/0/0"
const BTC_TESTNET_PATH = "84'/1'/0'/0/0"
const DEFAULT_SOL_PATH = "44'/501'/0'/0'"
const DEFAULT_TRON_PATH = "44'/195'/0'/0/0"
const DEFAULT_XRP_PATH = "44'/144'/0'/0'/0'"const BLE_SERVICE_UUID = '13d63400-2c97-0004-0000-4c6564676572'
const BLE_NOTIFY_UUID = '13d63400-2c97-0004-0001-4c6564676572'
const BLE_WRITE_UUID = '13d63400-2c97-0004-0002-4c6564676572'
const BLE_DEVICE_NAME_PREFIX = 'Openloop'
const BLE_DEFAULT_MTU = 155const WALLETCONNECT_PROJECT_ID = 'b3d610140c2d0d39f50150a030c7c985'const SUPPORTED_EVM_CHAINS = [
'eip155:1', // Ethereum Mainnet
'eip155:11155111', // Sepolia Testnet
] as const
const SUPPORTED_BIP122_CHAINS = [
'bip122:000000000019d6689c085ae165831e93', // Bitcoin Mainnet
'bip122:000000000933ea01ad0ee984209779ba', // Bitcoin Testnet3
] as const
const SUPPORTED_SOLANA_CHAINS = [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // Mainnet Beta
'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', // Devnet
] as const
const SUPPORTED_TRON_CHAINS = [
'tron:0x2b6653dc', // TRON Mainnet
'tron:0x94a9059e', // TRON Shasta Testnet
] as constconst SUPPORTED_EVM_METHODS = [
'eth_sendTransaction', 'eth_signTransaction', 'eth_sign',
'personal_sign', 'eth_signTypedData', 'eth_signTypedData_v3',
'eth_signTypedData_v4', 'wallet_getCapabilities',
] as const
const SUPPORTED_BIP122_METHODS = [
'signPsbt', 'getAccountAddresses', 'signMessage', 'sendTransfer',
] as const
const SUPPORTED_SOLANA_METHODS = [
'solana_signTransaction', 'solana_signAllTransactions',
'solana_signAndSendTransaction', 'solana_signMessage',
] as const
const SUPPORTED_TRON_METHODS = [
'tron_signTransaction', 'tron_signMessage',
] as const
const SUPPORTED_EVENTS = ['chainChanged', 'accountsChanged'] as constconst APDU = {
// Class bytes
CLA_ETHEREUM: 0xe0, // Ledger-compatible
CLA_OPENLOOP: 0xf0, // Openloop-specific
// Shared INS (routed automatically by coin_type)
INS_GET_PUBLIC_KEY: 0x02, // Get the address / public key
INS_SIGN_TX: 0x04, // Sign a transaction
INS_SIGN_MESSAGE: 0x08, // Sign a message
INS_SIGN_EIP712: 0x0c, // EIP-712 typed data
INS_SIGN_AUTHORIZATION: 0x34, // EIP-7702
// Bitcoin PSBT (Openloop-specific)
INS_BTC_SIGN_PSBT: 0x70, // Send the PSBT
INS_BTC_GET_SIGNED_PSBT: 0x71, // Get the signed PSBT
INS_BTC_GET_WALLET_PUBLIC_KEY: 0x40, // Get the BTC public key / address
INS_BTC_GET_ACCOUNT_XPUB: 0x72, // Get the account xpub
INS_BTC_SIGN_MESSAGE: 0x73, // Sign a BTC message
// Solana (Ledger Solana compatible)
INS_SOL_GET_PUBKEY: 0x05, // Get the Ed25519 public key
INS_SOL_SIGN_MESSAGE: 0x06, // Sign a transaction / message
INS_SOL_SIGN_OFFCHAIN_MSG: 0x07, // Sign an off-chain message
} as const// Build an APDU command
function buildApdu(cla: number, ins: number, p1: number, p2: number, data?: Uint8Array): Uint8Array
// Convert a BIP32 path to a buffer
function pathToBuffer(path: string): Uint8Array
// Convert a BIP32 path to an array of elements
function pathToElements(path: string): number[]
// Concatenate Uint8Arrays
function concat(...arrays: Uint8Array[]): Uint8Array
// Convert a hex string to a Uint8Array (with or without the 0x prefix)
function hexToBytes(hex: string): Uint8Array
// Convert a Uint8Array to a hex string (no 0x prefix)
function bytesToHex(bytes: Uint8Array): string
// Base58 encode / decode
function base58Encode(bytes: Uint8Array): string
function base58Decode(str: string): Uint8Array
// Format an OpenloopDeviceState into a single-line log string
// e.g. "wallet=N lang=0 USB(native) ble=off FIDO PIV"
function formatOpenloopState(s: OpenloopDeviceState): string