Native BLE (Bluetooth Low Energy) transport for communicating with Ledger hardware wallets on iOS and macOS.
Fork of LedgerHQ/hw-transport-ios-ble — modernized for Swift 6, with security hardening, expanded device support, and a developer-friendly API.
| Device | Status |
|---|---|
| Ledger Nano X | Supported |
| Ledger Nano S Plus (FTS) | Supported |
| Ledger Stax | Supported |
| Ledger Flex | Supported |
- iOS 17.0+ / macOS 14.0+
- Swift 6.0+
- Xcode 16.0+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/NovaShieldWallet/ledger-swift-sdk.git", from: "2.0.0")
]Or in Xcode: File > Add Package Dependencies... and enter:
https://github.com/NovaShieldWallet/ledger-swift-sdk.git
import BleTransport
// Async stream of discovered devices
for try await devices in BleTransport.shared.scan(duration: 10) {
for device in devices {
print("Found: \(device.peripheral.name) (RSSI: \(device.rssi))")
}
}let peripheral = try await BleTransport.shared.create(
scanDuration: 10,
disconnectedCallback: {
print("Device disconnected")
}
)
print("Connected to: \(peripheral.name)")// By identifier
try await BleTransport.shared.connect(
toPeripheralID: peripheralIdentifier,
disconnectedCallback: nil
)
// By name
try await BleTransport.shared.connect(
toPeripheralNamed: "Nano X 1A2B",
disconnectedCallback: nil
)// Send an APDU and get the response
let response = try await BleTransport.shared.exchange(
apdu: APDU(data: [0xb0, 0x01, 0x00, 0x00])
)
print("Response: \(response)")
// Or from a hex string
let apdu = APDU(raw: "e0d8000007426974636f696e")
let result = try await BleTransport.shared.exchange(apdu: apdu)let appInfo = try await BleTransport.shared.getAppAndVersion()
print("Running: \(appInfo.name) v\(appInfo.version)")// Opens Bitcoin app, closing current app if needed
try await BleTransport.shared.openAppIfNeeded("Bitcoin")try await BleTransport.shared.disconnect()let state = await BleTransport.shared.bluetoothStateCallback()
print("Bluetooth state: \(state.rawValue)")All async methods also have callback-based equivalents for compatibility:
BleTransport.shared.scan(duration: 10) { devices in
print("Found \(devices.count) devices")
} stopped: { error in
if let error = error {
print("Scan error: \(error.localizedDescription)")
}
}
BleTransport.shared.connect(
toPeripheralID: peripheral,
disconnectedCallback: nil,
success: { connectedPeripheral in
print("Connected!")
},
failure: { error in
print("Failed: \(error.localizedDescription)")
}
)By default, BleTransport scans for all supported Ledger devices. You can customize:
// Scan only for specific device models
let config = BleTransportConfiguration(services: [
.nanoX, // Ledger Nano X
.nanoFTS, // Ledger Nano S Plus
.stax, // Ledger Stax
.flex, // Ledger Flex
])The SDK provides typed errors for precise error handling:
do {
try await BleTransport.shared.connect(
toPeripheralID: peripheral,
disconnectedCallback: nil
)
} catch let error as BleTransportError {
switch error {
case .bluetoothNotAvailable:
print("Please enable Bluetooth")
case .scanningTimedOut:
print("No device found")
case .connectError(let description):
print("Connection failed: \(description)")
case .pairingError(let description):
print("Pairing failed: \(description)")
default:
print("Error: \(error.localizedDescription)")
}
}Add the following to your app's Info.plist for Bluetooth access:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to communicate with your Ledger device.</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
</array>The repository includes demo applications. Open BleTransportDemo/BleTransportDemo.xcodeproj and run:
- BleTransportDemo — iOS demo
- BleTransportDemoMac — macOS demo
This fork includes significant improvements over the original LedgerHQ/hw-transport-ios-ble:
- Swift 6.0 with swift-tools-version 6.0
- iOS 17+ / macOS 14+ platform targets
- Removed legacy
@objcannotations and unnecessaryNSObjectinheritance - Renamed
Sendableprotocol toBluetoothSendable(Swift's built-inSendableconflict)
- Eliminated all force unwraps (
!) — safe parsing throughout - Added bounds checking in APDU response parsing (prevents crashes on malformed data)
- MTU negotiation validates range (20-512 bytes per BLE spec)
- Replaced
print()debug statements withos.Loggerusing privacy annotations - APDU data is never logged in production builds
- Added Ledger Stax BLE service UUIDs
- Added Ledger Flex BLE service UUIDs
- Named static constants:
BleService.nanoX,.nanoFTS,.stax,.flex
- Fixed
openApp/closeAppcallingBleTransport.sharedinstead ofself - Fixed
PeripheralIdentifierhash/equality inconsistency - Fixed potential crash in
parseMTUresponsewith short responses
- Expanded from 14 to 69 unit tests
- Security-focused test suite for hex parsing and bounds checking
- Configuration and error type test coverage
MIT — see LICENSE for details.