The Openloop SDK supports several connection methods (transports). Pick the transport that matches your target platform and use case — dApp or native app.
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 |
| 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.
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.
| 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 |
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')
})Package: @openloop/transport-webble
Supported environments: Chrome 56+ / Edge 79+
(desktop), Android Chrome
Connects wirelessly over Bluetooth Low Energy (BLE).
| 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+) |
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()exchange() call.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.
| Method | Description |
|---|---|
isAvailable(port?, host?): Promise<boolean> |
Whether the Connect server is running |
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')
})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 |
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.
| 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 |
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)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.
| Method | Description |
|---|---|
discover(): UsbDeviceInfo[] |
Enumerates the connected Openloop/Ledger devices |
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
})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.
| Method | Description |
|---|---|
scan(timeout?): Promise<BleDeviceInfo[]> |
Scans for BLE devices (30 seconds by default) |
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')
})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.
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()// 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
}