API Reference

A complete list of type definitions, interfaces, classes, and constants. Unless stated otherwise, everything here is exported from @openloop/sdk-core.

Contents


Interfaces

ITransport

The 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

TransportStatus

interface TransportStatus {
  connected: boolean
  deviceName?: string
  deviceId?: string
}

TransportEvent

type TransportEventType = 'connected' | 'disconnected' | 'error'

interface TransportEvent {
  type: TransportEventType
  error?: Error
}

type TransportEventHandler = (event: TransportEvent) => void

TransportInfo

The 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
}

TransportFactory

type TransportFactory = () => Promise<ITransport>

SignatureResult

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)
}

BtcMessageSignature

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)
}

InputSigningPath

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")
}

DeviceInfo

interface DeviceInfo {
  connected: boolean
  path?: string
  vendorId?: number
  productId?: number
}

XrpSignatureResult

The XRP signature result.

interface XrpSignatureResult {
  signature: string  // Signature hex (Ed25519: 64B, secp256k1: DER, variable length)
}

XrpCurve

type XrpCurve = 'ed25519' | 'secp256k1'

WcSession

interface WcSession {
  topic: string
}

IWcSignClient

interface IWcSignClient {
  request<T>(params: {
    topic: string
    chainId: string
    request: { method: string; params: unknown }
  }): Promise<T>
}

WcTransportOptions

interface WcTransportOptions {
  client: IWcSignClient
  session: WcSession
  chainId?: string  // Default: "eip155:1"
}

App Classes

EthereumApp

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>
}

BitcoinApp

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>
}

SolanaApp

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>
}

TronApp

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>
}

XrpApp

class XrpApp {
  constructor(transport: ITransport)

  getAddress(path: string, curve?: XrpCurve): Promise<{ publicKey: string; address: string }>

  signTransaction(path: string, txBlob: string): Promise<XrpSignatureResult>
}

OpenloopApp

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>
}

OpenloopDeviceInfo

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

OpenloopDeviceState

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.

Example

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
  }
}

Signature Verification

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 of identity decides 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) and verifyXrpSignedTx (XRP) recover the signer / public key from the signed data itself, so there is no pubkey to pass in from outside.


Error Classes

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)
}

OpenloopSDK Class

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
}

WcTransport Class

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>
}

Constants

USB Identifiers

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 compatible

BIP44 Default Paths

const 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'"

BLE Constants

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       = 155

WalletConnect Constants

const WALLETCONNECT_PROJECT_ID = 'b3d610140c2d0d39f50150a030c7c985'

WalletConnect Supported Chains (CAIP-2)

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 const

WalletConnect Supported Methods

const 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 const

APDU Instruction Set

const 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

Utility Functions

// 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