Sphere SDK: A Wallet SDK Where the Payment Rail Is Not Nostr
The SDK for autonomous economic agents. Give an agent an identity, a wallet, and the ability to find, negotiate with, and settle with other agents - peer-to-peer, with perfect privacy and ultra-fast finality
At a glance
- What is it?
- Unicity's TypeScript SDK gives an agent a BIP39 identity, a wallet-api payment rail, and a Nostr messaging layer that is explicitly kept out of the money path. The two-layer provider model is the whole story, and the README is blunt about what happens if you skip it.
- Who is it for?
- Adopt Sphere SDK if you are building an agent or dApp that needs a wallet with a certified, resumable payment rail and you accept server-side custody of token inventory. Do not adopt it if you need a local token store: the README states own-storage custody was rescinded and there is no local token store, so that requirement is not negotiable.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem Sphere SDK Solves, and the Boundary It Draws Around Money
Most agent frameworks treat payments as a function call against a custodial API key. Sphere SDK instead composes a wallet out of swappable ports and gives the payments vertical its own transport, separate from the messaging transport. The README states this directly: the rail is wallet-api, not Nostr, and Nostr carries messaging and nametags only. That distinction is the reason the SDK exists in this shape. If you wire up a Nostr transport and assume it moves value, nothing will move.
The intended user is someone building an autonomous agent or a dApp that needs an identity, a wallet, and a way to settle with another party. The SDK covers wallet management with BIP39/BIP32 derivation and optional PBKDF2 password encryption, HD address derivation under BIP32/BIP44, payment requests with encrypted memos, a signed intent bulletin board with semantic search, NIP-17 direct messages and NIP-29 group chat, and a Connect protocol for dApp to wallet communication through `ConnectClient` and `ConnectHost`. The CLI is no longer in this repository; it moved to the separate `@unicity-sphere/cli` package.
Two Provider Layers, and Why Sphere.init Throws INVALID_CONFIG
A wallet is assembled in two steps. The first builds the base: `createBrowserProviders` or `createNodeProviders` supplies `storage` for keys, identity and journals, a Nostr `transport` for messaging and nametags, and an `oracle` for the gateway and trust base. The second step, `createWalletApiProviders(base, ...)`, attaches the `walletApi` transport config. The README is explicit that the first call builds only the base and that money moves only through the wallet-api vertical.
What `createWalletApiProviders` returns is a plain config object shaped as `{ ...base, walletApi }`, where `walletApi` holds `{ network, baseUrl, deviceId?, fetchFn?, webSocketFactory?, paymentsV2Transport? }`. That object is what `Sphere.init` consumes. The `paymentsV2Transport` key is the interesting seam: it lets a test harness or a custom host inject a whole replacement bundle for the money ports. The README notes that `StoragePort` and `DeliveryPort` in `modules/payments-v2/ports.ts` have wallet-api implementations, so the ports are contract-enforced rather than advisory.
The failure mode here is loud by design. `network` is required on the base factory, in the `walletApi` config, and on `Sphere.init`, and all three must agree. `Sphere.init` resolves the payments composition and the token registry from its own `network`, so omitting it or letting it disagree with `walletApi.network` throws `INVALID_CONFIG` before any storage write. That ordering matters: a misconfigured wallet fails before it can persist a half-built identity.
Getting It Running: Install, Provider Wiring, and the Public Testnet Key
Installation is `npm install @unicitylabs/sphere-sdk`. The README's quick start imports `Sphere` from the package root, `createBrowserProviders` from `@unicitylabs/sphere-sdk/impl/browser`, and `createWalletApiProviders` from `@unicitylabs/sphere-sdk/impl/shared/wallet-api`. Node.js also needs `ws` installed, and file storage is the optional piece there; the browser build uses IndexedDB storage optionally.
The base factory takes a `network` and an `oracle.apiKey`. The README uses `network: 'testnet'`, described as an alias of testnet2 with networkId 4, and a testnet2 gateway key that it states is public and not a secret but is required at runtime for send and mint. The wallet-api config takes `baseUrl`, `network: 'testnet2'`, and a `deviceId`. The README advises persisting that device ID to avoid re-authentication on each launch.
Initialization is `await Sphere.init({ ...providers, autoGenerate: true })`, which returns `sphere`, a `created` flag, and a `generatedMnemonic` when a wallet was created. Sending is `sphere.payments.send({ recipient: '@alice', amount: '1000000', coinId: 'UCT', memo: 'hello' })`. Two details in that call are easy to get wrong. The amount is a decimal string and the README says never a JavaScript number. The recipient must have a published identity, meaning a chain public key such as a registered Unicity ID, or the send fails with `INVALID_RECIPIENT`. A symbol like `UCT` auto-resolves to its hex coinId.
Receiving happens automatically while the wallet runs, through mailbox drain and a wake socket. A batch or CLI application can call `await sphere.payments.receive()` to drain explicitly, and `sphere.on('transfer:incoming', ...)` fires per transfer with a sender nametag. Assets are read with `await sphere.payments.assets()`.
deliveryPending Is Not a Failure, and That Is a Documentation Problem Worth Fixing
The `send()` call resolves with a `TransferResult`. The `status` field is `'completed'` on success, and the README lists `'pending'`, `'submitted'`, `'confirmed'`, `'delivered'` and `'failed'` as the other in-flight and terminal states. The `deliveryPending` field is `true` when the spend is certified on-chain but the recipient's mailbox delivery was deferred, which the README attributes to causes including a full inbox, and which it says will land on retry.
The README calls this normal, not a failure. That is the single most important sentence in the send documentation, because the natural reading of a boolean named `deliveryPending` next to a `status` field is that something went wrong. An integration that treats it as an error and retries the send risks a second spend attempt for a transfer that already certified. The README notes that the wallet-api vertical uses durable server-side intents with crash-safe resume under the same `transferId`, which is the mechanism that makes retry safe in principle, but the resume semantics are described at the level of the transfer ID rather than spelled out as a client-side recipe. If you are building a payment loop, the exact retry contract is the first thing to confirm against `docs/INTEGRATION.md` rather than infer from the field name.
Server-Side Custody Is the Design, and It Rules Some Projects Out
The README states that custody is server-side: the wallet-api backend holds your token inventory while your keys never leave the client. It then states that own-storage custody was rescinded and that there is no local token store. This is not a roadmap item or an optional mode. It is the current architecture, presented as a settled decision.
That constraint decides whether the SDK fits. If your threat model requires the token inventory to live on the user's device, this SDK does not offer that, and no amount of provider wiring changes it. If your threat model instead requires that private keys never reach a server, the split here satisfies it: keys stay local, inventory does not. Those are two different properties and the README is careful to claim only the second.
The second consequence is operational. Your `baseUrl` points at a wallet-api deployment that holds the inventory your wallet reports. The README's example uses `https://wallet-api.unicity.network` for testnet2 and describes it as your deployment. Whether you run it or rely on someone else's decides who can see and affect the inventory side of your users' wallets, independent of the key custody story.
Compared With a Local-Key Wallet Library Like ethers
The closest familiar reference point is a library such as ethers, where the wallet holds keys and the application broadcasts signed transactions directly to a chain. The difference is where state lives between signing and finality. In that model the application owns nonce management, gas estimation and any retry logic, and the token balance is whatever the chain says it is. In Sphere SDK the backend holds inventory, a token engine certifies transfers on-chain, and the finished token is deposited into the recipient's wallet-api mailbox. The client does not broadcast a transaction; it hands a durable intent to the wallet-api vertical and waits for certification plus delivery.
That trades control for a narrower failure surface. You do not manage nonces or fees, and crash-safe resume under a stable `transferId` is provided rather than built. In exchange you inherit two dependencies the ethers-style model does not have: a running wallet-api backend, and a recipient who has published an identity, since a send to an unregistered nametag fails with `INVALID_RECIPIENT`. If your counterparties are arbitrary chain addresses rather than registered Unicity IDs, the recipient model is the blocking difference, not the custody model.
Licence, Maintenance, and What the Release Cadence Suggests
The repository is MIT licensed and the default branch is `main`. MIT is permissive and imposes no copyleft obligation on your application code, but a licence file is not legal advice and the SDK's dependency tree carries its own terms; check those before shipping.
The release history in the supplied material shows v0.8.0 on 2026-05-29, titled UXF Inter-Wallet Transfer Protocol plus profile-layer hardening, and v0.2.4 on 2026-02-11. The gap between those version numbers is wide, and the 0.x series in general signals that the API is still moving. The README's own framing supports that reading: it documents a rescinded custody model, which is the kind of change that invalidates integration code written against an earlier version. The last push to the repository is dated 2026-09-10, so the project is active.
For upgrade cost, the practical exposure is the provider wiring. Three places must agree on `network`, and the wallet-api config object is the seam that custom hosts and tests replace. Pin your SDK version, and re-read the provider section of the README on each bump rather than assuming the config shape is stable. The `paymentsV2Transport` injection point is also the part most likely to shift, since it exists to let hosts substitute an entire bundle of money ports.
Editorial conclusion
Adopt Sphere SDK if you are building an agent or dApp that needs a wallet with a certified, resumable payment rail and you accept server-side custody of token inventory. Do not adopt it if you need a local token store: the README states own-storage custody was rescinded and there is no local token store, so that requirement is not negotiable. Before writing integration code, verify three things against your own target network: that `createBrowserProviders` or `createNodeProviders`, the `walletApi` config, and `Sphere.init` all receive the same `network` value, that your `deviceId` is persisted somewhere durable, and that your wallet-api `baseUrl` is a deployment you control or trust, since it holds the inventory your wallet reports.
Community notes