Guides

The wallet SDK and its ports

Everything a wallet does, in one service made from the platform's ports. An app draws screens over it; kivi is one such app.

The wallet's behaviour lives in one package, @wellknown-id/wallet, as a service: unlocking, personas, bonds between a holder's devices, sync, contacts, custodians and legacy, sharing, delegation, signing in for another device, and checking the pages wellknown.id serves. An app makes it once, from adapters for its platform, and draws screens over it. kivi, on Android, iOS and the web, is such an app: it keeps no state and makes no decisions of its own, and a lint rule stops its screens from importing anything from the package but the service, its types and a few pure functions.

Status: the package is TypeScript and runs in browsers, on Hermes (React Native) and in Node. It isn't published to npm yet, and its API may still change. SDKs in kettu, for other languages, are planned.

Making the service

import { createWalletService, type ServicePorts } from '@wellknown-id/wallet/service';

const ports: ServicePorts = { keys, credentials, gate, gateStatus, holderState, bonds, relay, store, drops, wake, passkeys, device, say };
const wallet = createWalletService(ports);

wallet.open((ask) => showToHolder(ask)); // the app is in front: listen for bonded devices; put to the holder what only they can answer
wallet.on('bonds', redraw);              // what changed: 'log', 'bonds', 'offers', 'pages', 'holder', 'records'
// …
wallet.close();                          // the app went to the background

Make it once per app: a second service over the same storage would answer the same bonds twice. Two services over the same storage behave like the same device restarted, which is how the package's own tests kill a device mid-step and check that nothing is lost.

The ports

Everything the service needs from the platform, and nothing else (ServicePorts):

PortWhat it iskivi's adapters
keysthe device's identity key and credential keysKeystore, the Secure Enclave; WebCrypto
credentialsdocuments, secrets' notes, consentsfiles; IndexedDB
gate, gateStatus()the biometric gate (a browser: a non-extractable key, no prompt)kivi-keys; WebCrypto
quiet?what's sealed without a prompt, on a phonekivi-keys
holderStatethe holder's state on this devicea file; localStorage
bondsbonds (with an atomic update), the sync log, offers heldfiles; IndexedDB
relaythe relay's mailboxes, over WebSocketa WebSocket relay
store, dropsthe encrypted store and drop boxesHTTPS to the relay
wake(bond)a content-free push to a bonded phonethe relay's wake-up
passkeysunlock, create, and tell providers which passkeys open the keyringCredential Manager, AuthenticationServices; WebAuthn
push?this device's push tokenexpo-notifications
devicewhat this device calls itself
release?the release watch's setup, fetch and saved state
registry?where sites find the wallet's credentials (the Digital Credentials API)Credential Manager on Android
saythe service's words, in the holder's languagethe app's translations
clock?, fetch?the platform's own unless given

packages/wallet/src/adapters has in-memory, WebCrypto, IndexedDB and WebSocket adapters to start from.

What it offers

The service's commands are grouped by what they're about: holder (unlocking, personas, recovery codes, records), bonds (pairing, joining, giving and taking back personas), sync, offers (secrets kept on another device), contacts, custodians, legacy, shares, grants, groups, signIn and give (for another device), pages (the release watch), witnesses, and wallet, the core: documents, credentials, consents and secrets. Each is in the API reference, and the pure helpers a screen may use are in @wellknown-id/wallet/view.

All of the holder's data the service keeps off the device is end to end encrypted, under keys only the holder's devices can derive, filed under ids nobody can link to them; the relay in between sees only ciphertext.