Transport Guide

The Openloop SDK supports several connection methods (transports). Pick the transport that matches your target platform and use case — dApp or native app.

Package Structure by SDK Language

The Openloop SDK ships as a set of TypeScript packages (npm). Swift / Kotlin / C# libraries for native apps are planned.

Language / format Registry Purpose
TypeScript / npm npmjs.com (@openloop/*) Web dApps, Node.js / Electron, React Native (including Connect Mobile)
Swift / SPM (planned) Native iOS apps
Kotlin / Maven (planned) Native Android
C# / NuGet (planned) Native Windows

Platform Support Matrix

Transport Chrome/Edge Firefox Safari (Desktop) iOS Safari Android Chrome iOS Native Android Native Node.js / Electron
WebHID (USB)
Web Bluetooth
LocalWS
Safari Extension
USB HID (native)
BLE (native)
Native lib (Swift / Kotlin — planned) Planned Planned
WalletConnect

WalletConnect does not talk to the device directly — it is remote signing through the Openloop Connect app. Device operations such as address retrieval happen on the Connect side.

BLE (native): a noble-based BLE transport (@openloop/transport-ble) for Node.js / Electron. It is the BLE counterpart of USB HID (native), talking to the device directly without going through a browser.

Native lib (planned): Swift / Kotlin libraries that connect directly from native iOS / Android apps are planned. Until then, use iOS Safari (Safari Extension) or Android Chrome (Web Bluetooth) on mobile.

Selection Flowchart

Diagram 0

Transport Details


WebHID (USB)

Package: @openloop/transport-webhid Supported environments: Chrome 89+ / Edge 89+ (desktop only)

Connects the device directly over a USB cable. This is the fastest and most stable connection method.

Static Methods

Method Description
isSupported(): boolean Whether the WebHID API is available
connect(): Promise<WebHidTransport> Shows the device picker and connects (requires a user gesture)
reconnect(): Promise<WebHidTransport \| null> Reconnects automatically to a previously authorized device

Code Example

import { WebHidTransport } from '@openloop/transport-webhid'

// Check support
if (!WebHidTransport.isSupported()) {
  console.log('WebHID is not supported in this browser')
}

// First connection (inside a button click)
const transport = await WebHidTransport.connect()

// Automatic reconnection (on page load)
const transport = await WebHidTransport.reconnect()

// Detect disconnection
transport.onDisconnect(() => {
  console.log('Device disconnected')
})

Web Bluetooth

Package: @openloop/transport-webble Supported environments: Chrome 56+ / Edge 79+ (desktop), Android Chrome

Connects wirelessly over Bluetooth Low Energy (BLE).

Static Methods

Method Description
isSupported(): boolean Whether the Web Bluetooth API is available
connect(): Promise<WebBleTransport> Shows the device picker and connects (requires a user gesture)
reconnect(): Promise<WebBleTransport \| null> Reconnects to a previously paired device (Chrome 100+)

Code Example

import { WebBleTransport } from '@openloop/transport-webble'

if (!WebBleTransport.isSupported()) {
  console.log('Web Bluetooth is not supported')
}

// First connection
const transport = await WebBleTransport.connect()

// Automatic reconnection
const transport = await WebBleTransport.reconnect()

Notes


LocalWS (All Browsers)

Package: @openloop/transport-local Supported environments: every browser that supports WebSocket Prerequisite: the Openloop Connect desktop app must be running

Communication goes through the WebSocket server (ws://127.0.0.1:21320) provided by the Openloop Connect desktop app. It works even in browsers without WebHID/WebBLE support, such as Firefox and Safari.

Static Methods

Method Description
isAvailable(port?, host?): Promise<boolean> Whether the Connect server is running

Code Example

import { LocalWsTransport } from '@openloop/transport-local'

// Check whether the Connect app is running
const available = await LocalWsTransport.isAvailable()

// Connect
const transport = new LocalWsTransport({
  port: 21320,    // default
  host: '127.0.0.1', // default
})
await transport.open()

// Notification when the device connection state changes
transport.onDeviceChange((connected: boolean) => {
  console.log('Device:', connected ? 'connected' : 'disconnected')
})

Protocol

JSON-RPC 2.0 over WebSocket:

Method Description
openloop_exchange Sends and receives APDU commands
openloop_lock Acquires an exclusive lock on the device
openloop_unlock Releases the exclusive lock on the device
openloop_status Retrieves the device connection state

Safari Extension (iOS)

Package: @openloop/transport-safari Supported environments: iOS Safari (with the Openloop Safari Extension installed)

Reaches iOS CoreBluetooth through a Safari Web Extension and talks to the device over BLE.

Static Methods

Method Description
isAvailable(timeout?): Promise<boolean> Whether the Safari Extension is installed (3-second timeout by default)
scan(duration?): Promise<DeviceInfo[]> Scans for BLE devices
connect(deviceId?): Promise<SafariTransport> Connects to a device

Code Example

import { SafariTransport } from '@openloop/transport-safari'

// Check for the extension
const available = await SafariTransport.isAvailable()

// Scan for devices
const devices = await SafariTransport.scan(5000) // scan for 5 seconds
console.log('Found devices:', devices)

// Connect
const transport = await SafariTransport.connect(devices[0]?.deviceId)

USB HID (Native)

Package: @openloop/transport-usb Supported environments: Node.js / Electron Dependency: node-hid

Talks to the USB device directly, without a browser. A good fit for server-side code and desktop apps.

Static Methods

Method Description
discover(): UsbDeviceInfo[] Enumerates the connected Openloop/Ledger devices

Code Example

import { UsbHidTransport } from '@openloop/transport-usb'

// Detect devices
const devices = UsbHidTransport.discover()
if (devices.length === 0) {
  console.log('No device found')
}

// Connect
const transport = new UsbHidTransport({ path: devices[0].path })
await transport.open()

// Automatic retry (up to 3 times by default)
const transport = new UsbHidTransport({
  path: devices[0].path,
  maxRetries: 3,
  exchangeTimeout: 120000, // 2 minutes
})

BLE (Native)

Package: @openloop/transport-ble Supported environments: Node.js / Electron Dependency: @abandonware/noble

Uses noble to talk to the device over BLE directly, without going through a browser. It is the BLE counterpart of USB HID (native), and a good fit for server-side code and desktop apps.

Static Methods

Method Description
scan(timeout?): Promise<BleDeviceInfo[]> Scans for BLE devices (30 seconds by default)

Code Example

import { BleTransport } from '@openloop/transport-ble'

// Scan for devices
const devices = await BleTransport.scan()

// Connect
const transport = new BleTransport({ deviceId: devices[0].id })
await transport.open()

// Detect disconnection
transport.onDisconnect(() => {
  console.log('Device disconnected')
})

Notes


WalletConnect

Package: @openloop/sdk-core (WcTransport is bundled with sdk-core) Supported environments: every browser that supports WebSocket

Communicates with a remote Openloop Connect app through the WalletConnect v2 relay. APDU commands are relayed using the custom method openloop_exchange.

See WalletConnect Integration for details.


Auto-Detection with the OpenloopSDK Class

The OpenloopSDK class auto-detects the available transports and connects for you.

import { OpenloopSDK, EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'
import { WebBleTransport } from '@openloop/transport-webble'
import { LocalWsTransport } from '@openloop/transport-local'

// Register the transports
OpenloopSDK.registerTransport('webhid', {
  factory: () => WebHidTransport.connect(),
  name: 'WebHID (USB)',
  isAvailable: () => WebHidTransport.isSupported(),
})

OpenloopSDK.registerTransport('webble', {
  factory: () => WebBleTransport.connect(),
  name: 'Web Bluetooth',
  isAvailable: () => WebBleTransport.isSupported(),
})

OpenloopSDK.registerTransport('local', {
  factory: async () => {
    const t = new LocalWsTransport()
    await t.open()
    return t
  },
  name: 'LocalWS (Connect)',
  isAvailable: () => LocalWsTransport.isAvailable(),
})

// Check which transports are available
const transports = await OpenloopSDK.discover()
console.log(transports)
// [
//   { type: 'webhid', name: 'WebHID (USB)', available: true },
//   { type: 'webble', name: 'Web Bluetooth', available: true },
//   { type: 'local', name: 'LocalWS (Connect)', available: false },
// ]

// Connect using a specific transport
const transport = await OpenloopSDK.connect({ transport: 'webhid' })

// Or connect using the first available transport
const transport = await OpenloopSDK.connect()

Reconnection Patterns

// Reconnect automatically to the previous device on page load
async function autoReconnect(): Promise<ITransport | null> {
  // WebHID: look for a previously authorized device
  if (WebHidTransport.isSupported()) {
    const transport = await WebHidTransport.reconnect()
    if (transport) return transport
  }

  // WebBLE: look for a previously paired device (Chrome 100+)
  if (WebBleTransport.isSupported()) {
    const transport = await WebBleTransport.reconnect()
    if (transport) return transport
  }

  // LocalWS: connect if the Connect app is running
  if (await LocalWsTransport.isAvailable()) {
    const transport = new LocalWsTransport()
    await transport.open()
    return transport
  }

  return null
}

Next Steps