LedgerService
public class LedgerService : NSObject, CBPeripheralDelegate, CBCentralManagerDelegate
A service class to wrap up all the complicated interactions with CoreBluetooth and the modified version of ledgerjs, needed to communicate with a Ledger Nano X.
Ledger only provide a ReactNative module for third parties to integrate with. The architecture of the module also makes it very difficult to integrate with native mobile (if it can be packaged up) as it relies heavily on long observable chains passing through many classes and functions. To overcome this, I copied the base logic from multiple ledgerjs classes into a single typescript file and split the functions up into more of a utility style class, where each function returns a result, that must be passed into another function. This allowed the creation of a swift class to sit in the middle of these functions and decide what to do with the responses.
The modified typescript can be found in this file (under a fork of the main repo) https://github.com/simonmcl/ledgerjs/blob/native-mobile/packages/hw-app-tezos/src/NativeMobileTezos.ts . The containing package also includes a webpack file, which will package up the typescript and its dependencies into mobile friendly JS file, which needs to be included in the swift project. Usage of the JS can be seen below.
NOTE: this modified typescript is Tezos only as I was unable to find a way to simply subclass their Transport
class, to produce a re-usable
NativeMobile transport. The changes required modifiying the app and other class logic which became impossible to refactor back into the project, without rewriting everything.
-
Instead of returning data, sometimes ledger returns a code to indicate that so far the message have been received successfully
Declaration
Swift
public static let successCode: String
-
General Ledger error codes, pulled from the source, and some additional ones added for native swift issues
See moreDeclaration
Swift
public enum GeneralErrorCodes : String, Error, Codable
-
Dedicated error codes pulled from the Ledger tezos app
See moreDeclaration
Swift
public enum TezosAppErrorCodes : String, Error, Codable
-
Be notified when the ledger device returns a success message, part way through the process. This can be useful to indicate to users that the request has succeed, but s waiting on input on the Ledger device to continue
Declaration
Swift
@Published public var partialSuccessMessageReceived: Bool { get set }
-
Public shared instace to avoid having multiple copies of the underlying
JSContext
createdDeclaration
Swift
public static let shared: LedgerService
-
Start listening for ledger devices
Declaration
Swift
public func listenForDevices() -> AnyPublisher<[String : String], KukaiError>
Return Value
Publisher with a dictionary of
[UUID: deviceName]
or anKukaiError
-
Stop listening for and reporting new ledger devices found
Declaration
Swift
public func stopListening()
-
Connect to a ledger device by a given UUID
Declaration
Swift
public func connectTo(uuid: String) -> AnyPublisher<Bool, KukaiError>
Return Value
Publisher which will indicate true / false, or return an
KukaiError
if it can’t connect to bluetooth -
Disconnect from the current Ledger device
Declaration
Swift
public func disconnectFromDevice()
Return Value
A Publisher with a boolean, or
KukaiError
if soemthing goes wrong -
Get the UUID of the connected device
Declaration
Swift
public func getConnectedDeviceUUID() -> String?
Return Value
a string if it can be found
-
Get a TZ address and public key from the current connected Ledger device
Declaration
Swift
public func getAddress(forDerivationPath derivationPath: String = HD.defaultDerivationPath, curve: EllipticalCurve = .ed25519, verify: Bool) -> AnyPublisher<(address: String, publicKey: String), KukaiError>
Parameters
forDerivationPath
Optional. The derivation path to use to extract the address from the underlying HD wallet
curve
Optional. The
EllipticalCurve
to use to extract the addressverify
Whether or not to ask the ledger device to prompt the user to show them what the TZ address should be, to ensure the mobile matches
Return Value
A publisher which will return a tuple containing the address and publicKey, or an
KukaiError
-
getAddress(forDerivationPath:
Asynchronouscurve: verify: ) Get a TZ address and public key from the current connected Ledger device
Declaration
Swift
public func getAddress(forDerivationPath derivationPath: String = HD.defaultDerivationPath, curve: EllipticalCurve = .ed25519, verify: Bool) async -> Result<(address: String, publicKey: String), KukaiError>
Parameters
forDerivationPath
Optional. The derivation path to use to extract the address from the underlying HD wallet
curve
Optional. The
EllipticalCurve
to use to extract the addressverify
Whether or not to ask the ledger device to prompt the user to show them what the TZ address should be, to ensure the mobile matches
Return Value
An async
Result
object, allowing code to be triggered via while loops more easily -
Sign an operation payload with the underlying secret key, returning the signature
Declaration
Swift
public func sign(hex: String, forDerivationPath derivationPath: String = HD.defaultDerivationPath, parse: Bool) -> AnyPublisher<String, KukaiError>
Parameters
hex
An operation converted to JSON, forged and watermarked, converted to a hex string. (Note: there are some issues with the ledger app signing batch transactions. May simply return no result at all. Can’t run REVEAL and TRANSACTION together for example)
forDerivationPath
Optional. The derivation path to use to extract the address from the underlying HD wallet
parse
Ledger can parse non-hashed (blake2b) hex data and display operation data to user (e.g. transfer 1 XTZ to TZ1abc, for fee: 0.001). There are many limitations around what can be parsed. Frequnetly it will require passing in false
Return Value
A Publisher which will return a string containing the hex signature, or an
KukaiError
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func centralManagerDidUpdateState(_ central: CBCentralManager)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?)
-
Declaration
Swift
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: (any Error)?)
-
Declaration
Swift
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, timestamp: CFAbsoluteTime, isReconnecting: Bool, error: (any Error)?)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?)
-
CBCentralManagerDelegate function, must be marked public because of protocol definition
Declaration
Swift
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)