Native SDK architecture

This page explains what runs where when a native app uses the Wavewalletdk bindings, and why the API looks the way it does. It condenses the upstream architecture doc, which remains the canonical version.

The wallet runs inside your app

A normal waved deployment is a daemon process that clients reach over a gRPC socket. On mobile that model is awkward: an app cannot reliably keep a sidecar process alive, and an open port is an attack surface.

Instead, the binding compiles the whole daemon into a native library and starts it inside the app’s own process. The Go SDK boots the daemon on a background goroutine and connects to it over an in-memory gRPC channel backed by a memory buffer rather than a TCP socket. The app’s calls travel that buffer. Nothing listens on the network, and there is no second process to manage.

This is the same embedding the React Native transport uses; the native SDK simply removes the JavaScript layer above it.

A thin facade and the JSON boundary

gomobile bind carries only a narrow set of types between Go and Kotlin/Swift. The facade therefore presents a flat, gomobile-safe surface: RPC verbs pass JSON bytes in and JSON bytes out, and each wrapper decodes results with the platform’s own tools (kotlinx.serialization on Android, Codable on iOS). Neither platform needs a protobuf runtime. A few hot paths skip JSON and return a plain scalar, such as a Long confirmed balance.

No callbacks

The bindings are callback-free. start(config) blocks until the daemon’s gRPC channel is serving, then returns; the wrappers run it off the main thread for you. Unary verbs are ordinary throwing calls: a Go error becomes a Kotlin exception or a Swift throws. Streaming uses a pull handle that the wrappers turn into a Kotlin Flow or a Swift AsyncThrowingStream, so the threading lives where the platform’s concurrency tools already are, and the host implements no interfaces.

Lifecycle

One daemon runs per process. Calling start a second time before stop throws rather than booting a second daemon. stop tears the daemon down, unblocks any in-flight stream reads, and resets the guard so the app can start again after the operating system suspends and resumes it.

Panics do not cross the language boundary, so an unrecovered one takes the whole app down. The facade installs recovery at two entry points: start and the subscription’s next. A panic there becomes an ordinary error you can catch. A panic anywhere else is not recovered, so treat that guarantee as covering startup and stream reads rather than every call.