The legacy keystone-sign-request / keystone-sign-result APIs have been removed. Use TRON Native for TRON and PSBT Extend for LTC, BCH, DASH and DOGE. These protocols require different transaction inputs and response handling; they are not drop-in replacements. Integrations that still require the removed APIs must pin an earlier SDK version.
A swift package of KeystoneSDK. It's based on KeystoneSDK which is generated by keystone-sdk-rust
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler.
To add KeystoneSDK as a dependency to your Xcode project, select File > Add Packages and enter URL: https://github.com/KeystoneHQ/keystone-sdk-ios.git.
Use generatePSBTExtend and parsePSBTExtend for the crypto-psbt-extend UR type. These APIs require the sdk-0.2.5 binary dependency or later.
Pass the raw PSBT bytes as Data:
import Foundation
import KeystoneSDK
let sdk = KeystoneSDK()
let psbtData = try Data(contentsOf: psbtFileURL) // URL of your PSBT file
// Litecoin. Display successive fragments as QR codes for the device to scan.
let encoder = try sdk.btc.generatePSBTExtend(psbt: psbtData, coinId: 2)
let qrFragment = encoder.nextPart()
// Call encoder.nextPart() again for each subsequent QR frame.
// Bitcoin: omit coinId, or pass 0. The coin field is omitted from the payload.
let bitcoinEncoder = try sdk.btc.generatePSBTExtend(psbt: psbtData)
| Coin | coinId |
|---|---|
| Bitcoin | 0 (default) |
| Litecoin | 2 |
| Dogecoin | 3 |
| Dash | 5 |
| Bitcoin Cash | 145 |
coinId is an Int32. Unsupported values throw KeystoneError.generatePSBTError.
Reuse the same KeystoneSDK instance across frames so the decoder can assemble multipart URs:
// Call for each scanned QR string, using the sdk instance above.
func receivePSBTExtendFragment(_ qrString: String) throws -> PSBTExtend? {
let decoded = try sdk.decodeQR(qrCode: qrString)
guard let ur = decoded.ur else {
return nil // More fragments are needed.
}
defer { sdk.resetQRDecoder() }
return try sdk.btc.parsePSBTExtend(ur: ur)
}
// Inside your scanner callback:
if let result = try receivePSBTExtendFragment(scannedQRCode) {
let psbt: Data = result.psbt
let coinId: Int32? = result.coinId // nil means Bitcoin.
// Pass psbt and coinId to your transaction handling code.
}
If you already have a decoded UR, call sdk.btc.parsePSBTExtend(ur: ur) directly. A different UR type throws QRCodeError.unexpectedQRCode; an invalid Extend payload throws KeystoneError.parsePSBTError.
For ordinary crypto-psbt URs, continue using generatePSBT(psbt:) and parsePSBT(ur:).
-
Install Rust https://www.rust-lang.org/learn/get-started
-
Clone the rust repo.
git clone https://github.com/KeystoneHQ/keystone-sdk-rust
cd keystone-sdk-rust
- Install ios targets
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
- Generate XCFramework
Make sure the header file keystone-sdk-rust/include/URRegistryFFI/lib_ur_registry_ffi.h is up to date.
make generate_xcframework
- Replace the
URRegistryFFI.xcframeworkinURRegistry/Libraryfrom thetargetbuild folder
Use sdk.tronNative. The URRegistryFFI dependency is pinned to sdk-0.2.6, which includes generate_tron_native_sign_request and parse_tron_native_signature. Native TRON signing requires this release or a compatible later version.
import Foundation
import KeystoneSDK
let sdk = KeystoneSDK()
let requestId = UUID().uuidString.lowercased()
// transactionRawDataHex is the unsigned transaction's raw_data_hex.
// deviceXfp is the master fingerprint obtained when connecting the device.
let request = TronNativeSignRequest(
requestId: requestId,
signData: transactionRawDataHex,
path: "m/44'/195'/0'/0/0",
xfp: deviceXfp,
dataType: 1,
origin: "My Wallet"
)
let encoder = try sdk.tronNative.generateSignRequest(tronSignRequest: request)
let qrFragment = encoder.nextPart()
// Display successive encoder.nextPart() values as QR frames.
Keep the same SDK instance while scanning a multipart response:
enum SigningResponseError: Error { case unexpectedRequest }
func receiveTronFragment(_ qr: String) throws -> Signature? {
let decoded = try sdk.decodeQR(qrCode: qr)
guard let ur = decoded.ur else { return nil }
defer { sdk.resetQRDecoder() }
let result = try sdk.tronNative.parseSignature(ur: ur)
guard result.requestId.lowercased() == requestId else {
throw SigningResponseError.unexpectedRequest
}
return result // result.signature is hex.
}
Invalid requests or responses throw. If you already have a decoded UR, call sdk.tronNative.parseSignature(ur: ur) directly.
| Parameter | Meaning |
|---|---|
requestId |
A new UUID for each signing request. Match the response UUID before using its signature. |
signData |
Hex with or without a 0x / 0X prefix; the SDK strips the prefix before validation and encoding. For dataType = 1, use the serialized TRON transaction raw_data (raw_data_hex), not JSON, a full signed transaction, or a transaction hash. For dataType = 2, use the original personal-message bytes encoded as hex. |
path |
Signing derivation path, for example m/44'/195'/0'/0/0. |
xfp |
The connected device's master fingerprint: exactly 8 hex characters. |
dataType |
1 = transaction (default in mobile SDKs); 2 = personal message. |
address |
Optional TRON Base58Check address; an empty string omits it. |
origin |
Optional wallet/application name; an empty string omits it. |
signData must contain 1–32768 bytes. path is limited to 256 bytes and origin to 128 UTF-8 bytes.
The request UR type is tron-sign-request; the response UR type is tron-signature.
The response contains requestId and a 65-byte signature encoded as hex. It does not contain a signed transaction or transaction ID. For transactions, retain the original transaction, verify the response request ID and signature against the intended signer, then attach the signature to that transaction before broadcasting. Parsing does not perform that cryptographic verification or broadcast.
Use a firmware version that supports the native TRON UR types.