Interstellar SDK for iOS
The iOS SDK is the InterstellarSdk Swift package (iosSdk/ in the source repository, published as InterstellarSdk-<version>.zip): a Swift Package Manager package that bundles
- the SDK layer — the
InterstellarSdkclass (the balances, the recovery/VCA flows, the airdrop campaigns, the signing) + the pinpad UI (TxPinpadScreen,CircuitSurfaceView), - the data model (
CoinType,WalletBalances) and the currency helpers (CurrencyMath), - the pre-generated uniffi Swift bindings to the Rust core (
substrate-client) — the same uniffi FFI surface Android uses (via its uniffi-Kotlin bindings); only the renderer surface is wired per-platform (extern"C"+ therendererFFIframework), - the two native frameworks
substrate_clientFFI(the uniffi FFI) andrendererFFI(the C FFI of the Rust renderer, Bevy/Metal), - the
MobileKeychainSigner— the device key as a P-256 (secp256r1) ECDSA key in the iOS Keychain.
Requirements
| Requirement | Value |
|---|---|
| Platform | macOS (Apple Silicon) + Xcode 15+ |
| Deployment target | iOS 17.0 (the package declares platforms: [.iOS(.v17)]) |
| Architectures | arm64 only — the device slice + the arm64 simulator slice |
| Device key | iOS Keychain — P-256 ECDSA (tag interstellar_mobile_proxy_v1) |
| Backend | Interstellar worker (wss://), node (ws://), IPFS endpoint |
| Toolchain | Xcode/SPM only — no Rust, no uniffi, no Kotlin/Native, no linker flags |
Defaults are the local dev stack: wss://127.0.0.1:2090 (worker), ws://127.0.0.1:9944 (node), /ip4/127.0.0.1/tcp/5001 (IPFS). Unlike Android, the iOS factory accepts overrides (create(wsURL:nodeURL:ipfsAddr:…)), so you can point the SDK at another environment at runtime. Contact us for the shared testnet values.
Package integration
The published package is self-contained: download InterstellarSdk-<version>.zip, unzip it and add the InterstellarSdk folder as a local Swift package (Xcode: File > Add Package Dependencies… > Add Local…). Xcode links and embeds the two dynamic frameworks by itself — there is no "Embed & Sign" build phase, no bridging header and no linker flag to add.
import InterstellarSdk
That single import gives you the SDK layer and the raw Rust API (the package re-exports the two FFI modules).
The reference project (the wallet app) instead builds the SDK from source through a standard Gradle/Xcode bridge — its Run Script phase runs ./gradlew :shared:embedAndSignAppleFrameworkForXcode, which builds the Kotlin/Native framework and the Rust static libraries, and the app links them through FRAMEWORK_SEARCH_PATHS / LIBRARY_SEARCH_PATHS / OTHER_LDFLAGS. That is the "heavy" path the published package exists to avoid.
The device key lives in the iOS Keychain and is NOT reset when the app is uninstalled — on reinstall the SDK keeps the same identity and registration (opposite of a "fresh install" expectation). On Android the opposite is true: the key lives in the Android Keystore, which is wiped together with the app, so an uninstall + reinstall gives you a brand-new account. Only resetKeyPairAndRegister() or a Keychain-only reset changes the identity.
The one and only entry point
let sdk = try await InterstellarSdk.create(
onServerResetDetected: {
// Show a blocking warning UI ("server was reset, re-register?")
// and return ONLY once the user acknowledges.
await presentServerResetConfirmation()
}
)
create is async throws and is the only way to obtain a fully-initialized instance — on return the SDK is registered (or re-registered after a server reset) and balances are cached. A second overload lets you override the endpoints:
try await InterstellarSdk.create(
wsURL: "wss://…", nodeURL: "ws://…", ipfsAddr: "/ip4/…",
onServerResetDetected: { … }
)
Server-reset semantics
Identical to Android (both mirror the same Rust flow):
- Fresh install → transparent registration.
- Stored account ≠ server root (or server no longer knows the device) → your
onServerResetDetectedclosure is awaited; when it returns, the SDK re-registers. - On app resume call
sdk.checkServerReset(onServerResetDetected:).
@Published mutations are always serialized on the main thread by the SDK itself (it hops internally) — safe to consume from SwiftUI/Combine directly.
The auto re-registration described above is a convenience for the current stage (the local stack and the shared testnet, where the server is reset regularly while developing). The production version will do the opposite: if the servers no longer know the device's account, the SDK will plainly fail — your app must tell the user — instead of silently registering a new identity (which would quietly abandon the previous root account and everything on it).
So treat onServerResetDetected as a development-time hook: in production, do not
auto-accept it. Show a blocking error, and let the user retry or restore the account through
the recovery flows.
Resetting the device identity
// USER-initiated full reset: deletes the Keychain key, clears cached state,
// re-registers as a brand-new account.
await sdk.resetKeyPairAndRegister()
Next steps:
- Getting Started — fresh app in Xcode → root account ID on screen.
- API Reference — every public API, documented.
- VCA Pinpad Integration —
TxPinpadScreenand result semantics.