chore(sdk): 0.13.1 → 0.14.1 — Connect version floor + the payments-v2 flip - #23
Merged
Conversation
… flip
All five packages move to @unicitylabs/sphere-sdk 0.14.1 (exact pin).
The urgent part is the Connect handshake version floor: 0.14.1 hosts refuse
any client below DEFAULT_MIN_CLIENT_SDK_VERSION ('0.14.1-0') with
UNSUPPORTED_PROTOCOL_VERSION (4007) before any approval UI appears. A 0.13.1
ConnectClient does not report an sdkVersion at all, so every dApp example here
stopped connecting to a current Sphere wallet. The Connect protocol itself is
unchanged at 2.1 — this is a dependency bump.
The API migration below is what makes that bump compile and run.
bot — the only package with real breakage:
- Own-storage custody is DELETED in 0.14. `tokensDir` and
`createOwnStorageWalletApiProviders` are gone; the composition is now
createNodeProviders -> createWalletApiProviders({ baseUrl, network, deviceId? }).
WALLET_API_URL is REQUIRED (Sphere.init throws INVALID_CONFIG without it),
so the bot fails fast with an explicit message instead of a typed error from
inside the SDK. The long own-storage-vs-wallet-api prose in src/sphere.ts was
wrong and is rewritten to the single supported composition.
- payments-v2 renames: mintFungibleToken -> mint, getBalance() -> assets().
- Adds `pending` / `resume` commands (pendingTransfers / resumeNow) — resumeNow
is the ONLY safe retry for a possibly-committed send.
- Subscribes with the current event names (transfer:updated, inventory:updated,
connection:status) and refreshes the balance on inventory:updated, since the
server credits a fresh mint asynchronously.
nodejs:
- mockSphere reshaped to a real 0.14 wallet: `payments` is the payments-v2
facade (assets/tokens/paged history) and `paymentsV2` is the alias ConnectHost
reads to take the v2 branch. The dApp side of the wire is unchanged.
browser / backend-auth frontend:
- Compiled untouched (the Connect wire contract is preserved by the host
adapter). Updated the event log to the current names and added version-floor
error copy that names the required version.
backend-auth backend: unaffected — recoverPubkeyFromSignature is unchanged.
Docs: removed the invoice/accounting surface (deleted in 0.14, along with
modules/accounting and modules/swap), corrected the locked-gate counts
(4 of 14 RPC_METHODS, 13 scopes), and documented the custody removal with the
SDK's relocate-funds-before-upgrading warning.
Also adds `typecheck` scripts and CI jobs for bot and backend-auth/backend,
which had no CI coverage.
Verified live on testnet2: the bot boots against wallet-api, provisions its
aggregator key, mints, and reads assets/history back. The version floor was
confirmed by replaying a pre-0.14.1 handshake against a real ConnectHost.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43a175e36b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… never look green transfer:updated is the COMBINED outcome event (it replaced transfer:confirmed + transfer:failed + transfer:delivery_pending), so the event NAME says nothing about whether money moved. The panel keyed the badge on the name alone, painting a failed payment with the success green — the one miscolour that actively misleads, since a dApp dev reads this log to answer 'did it go through?'. badgeFor(event, data) now derives it from the payload: failed → red, still-converging (deliveryPending / status pending) → amber, delivered → green. Every other event and unknown/null payloads keep the table colour. 4 tests incl. the null-payload case; mutation-verified (breaking the failed branch turns the red-badge test red).
This was referenced Aug 8, 2026
KruGoL
added a commit
that referenced
this pull request
Aug 8, 2026
… audit (#25) The bump PR shipped documentation that teaches code which cannot work, plus three defects no test could see. Nothing crashed, which is why it landed. False claims, each verified against the pinned SDK: - The refusal a pre-flip dApp receives names its version. ConnectClient has sent `sdkVersion` in the handshake since 0.10.1, so a 0.13.1 client is refused with `actualSdk: "0.13.1"`, not `null`. The docs presented the null/"unknown (not reported)" case as what an old dApp sees, and the browser and backend-auth tests were written around that unreachable branch. A reader branching on `actualSdk == null` gets dead code. - "Every pre-0.14 name still fires" is wrong. The flip removed 38 event names and gave 16 an adapter; the other 26 are accepted by `subscribe` and then never emit. 24 of them were live emitters in 0.13.1 — every `invoice:*`, every `swap:*`, `sync:started`/`:error`/`:provider` and more. CONNECT.md now carries the full removed-with-no-adapter table. - `payment_request:accepted` and `:response` were not fabrications: both are declared in 0.13.1's SphereEventType, and `:response` is emitted by PaymentsModule. They were removed by the flip without an adapter. Defects: - badgeFor painted an in-flight `submitted` transfer with the success green, as it did any payload with no `status`. Colour is now driven by the settled set (confirmed/delivered/completed) rather than a blocklist, so an unknown or absent status can no longer answer "did it go through?" with yes. - The bot registered its `inventory:updated` listener after the mint and after the first `assets()` round trip, so the credit event it exists to catch could fire with nobody attached. Subscribe first, mint second, read third — the boot read now goes through the same epoch guard. - mockSphere's paymentsV2 lacked `requests`, which the host dereferences in `sphere.paymentsV2?.requests.list()` — the optional chain guards paymentsV2, not requests, so the first live `payment_request:updated` would throw a TypeError inside ConnectHost. Masked only by `on` being a no-op stub. Also: - sendSafety.test.ts could not detect a code the SDK ADDS, which is the direction that costs money. It now sweeps the whole SphereErrorCode universe through the SDK predicate, with a type-level guard that fails `tsc` and names any code a future SDK adds. - formatAssets moved to bot/src/balance.ts so it is reachable by tests at all; index.ts calls main() at module scope. - nodejs describeConnectFailure and backend-auth describeVersionFloor each handled one of the three handshake-refusal shapes. Both now cover the SDK floor, the protocol floor and the 4008 network mismatch — the last being what a dApp that omits `network` actually hits. The latter is renamed describeHandshakeRefusal to match what it does. - CI runs npm ci instead of npm install. The comment justifying npm install described a file: link that no lockfile carries any more, and npm install will not fail when package.json and package-lock.json disagree.
KruGoL
added a commit
that referenced
this pull request
Aug 8, 2026
* chore(sdk): 0.14.1 -> 0.14.2 across all five packages * fix: correct three false SDK claims and three real defects from the #23 audit (#25) The bump PR shipped documentation that teaches code which cannot work, plus three defects no test could see. Nothing crashed, which is why it landed. False claims, each verified against the pinned SDK: - The refusal a pre-flip dApp receives names its version. ConnectClient has sent `sdkVersion` in the handshake since 0.10.1, so a 0.13.1 client is refused with `actualSdk: "0.13.1"`, not `null`. The docs presented the null/"unknown (not reported)" case as what an old dApp sees, and the browser and backend-auth tests were written around that unreachable branch. A reader branching on `actualSdk == null` gets dead code. - "Every pre-0.14 name still fires" is wrong. The flip removed 38 event names and gave 16 an adapter; the other 26 are accepted by `subscribe` and then never emit. 24 of them were live emitters in 0.13.1 — every `invoice:*`, every `swap:*`, `sync:started`/`:error`/`:provider` and more. CONNECT.md now carries the full removed-with-no-adapter table. - `payment_request:accepted` and `:response` were not fabrications: both are declared in 0.13.1's SphereEventType, and `:response` is emitted by PaymentsModule. They were removed by the flip without an adapter. Defects: - badgeFor painted an in-flight `submitted` transfer with the success green, as it did any payload with no `status`. Colour is now driven by the settled set (confirmed/delivered/completed) rather than a blocklist, so an unknown or absent status can no longer answer "did it go through?" with yes. - The bot registered its `inventory:updated` listener after the mint and after the first `assets()` round trip, so the credit event it exists to catch could fire with nobody attached. Subscribe first, mint second, read third — the boot read now goes through the same epoch guard. - mockSphere's paymentsV2 lacked `requests`, which the host dereferences in `sphere.paymentsV2?.requests.list()` — the optional chain guards paymentsV2, not requests, so the first live `payment_request:updated` would throw a TypeError inside ConnectHost. Masked only by `on` being a no-op stub. Also: - sendSafety.test.ts could not detect a code the SDK ADDS, which is the direction that costs money. It now sweeps the whole SphereErrorCode universe through the SDK predicate, with a type-level guard that fails `tsc` and names any code a future SDK adds. - formatAssets moved to bot/src/balance.ts so it is reachable by tests at all; index.ts calls main() at module scope. - nodejs describeConnectFailure and backend-auth describeVersionFloor each handled one of the three handshake-refusal shapes. Both now cover the SDK floor, the protocol floor and the 4008 network mismatch — the last being what a dApp that omits `network` actually hits. The latter is renamed describeHandshakeRefusal to match what it does. - CI runs npm ci instead of npm install. The comment justifying npm install described a file: link that no lockfile carries any more, and npm install will not fail when package.json and package-lock.json disagree.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves all five packages to
@unicitylabs/sphere-sdk0.14.1 (exact pin, no caret).Why this is urgent, not cosmetic: the Connect version floor
Wallet hosts from 0.14.1 enforce an SDK version floor at the handshake.
ConnectHostapplies a built-in default of0.14.1-0(overridable viaConnectHostConfig.minSdkVersion) and refuses anything below it withUNSUPPORTED_PROTOCOL_VERSION(4007) — before any approval UI appears.A 0.13.1
ConnectClientdoes not report ansdkVersionin the handshake at all, so it is rejected as "unknown". Confirmed by replaying a pre-0.14.1 handshake against a realConnectHost:{ "code": 4007, "message": "SDK version unknown (not reported) is below the required minimum 0.14.1-0", "data": { "reason": "protocol_incompatible", "requiredSdk": "0.14.1-0", "actualSdk": null } }Every dApp example in this repo had stopped connecting to a current Sphere wallet. The Connect protocol itself is unchanged at 2.1 — this is a dependency bump and a rebuild, nothing more. The rest of the diff is what makes that bump compile and run.
The API renames (payments-v2 flip)
sphere.paymentsis the v2 facade now. Only thebot/package calls it directly:payments.mintFungibleToken(hex, amt)payments.mint(hex, amt)payments.getBalance()(sync)await payments.assets()payments.send(req)payments.getHistory()payments.history({ before?, limit? })— pagedAlso added to the bot's command loop:
pendingTransfers()(pending) andresumeNow()(resume).resumeNow()is the only safe retry for a possibly-committed send — it replays the sametransferIdinstead of issuing a second spend.sphere.paymentsV2exists as a deprecated alias; the examples deliberately do not use it, since examples should teach the current API.The own-storage custody removal
This is the one genuinely breaking change, and it only affects
bot/.TokenStorageProviderand both platform implementations are deleted, along with thetokenStorage/tokensDiroptions andcreateOwnStorageWalletApiProviders.Sphere.initnow throws a typedINVALID_CONFIGwithout awalletApicomposition. There is exactly one supported shape:So
WALLET_API_URLis now required for the bot. It fails fast on boot with an explicit message rather than surfacing an SDK-internal error. Keys/mnemonic/identity stay local; token inventory, history and payment requests live in wallet-api; Nostr keeps DMs and nametag bindings.bot/src/sphere.tsdocumented the own-storage-vs-wallet-api custody choice at length. That prose was now false and is rewritten to the single supported composition, andbot/README.mdgained a Custody: wallet-api is required section carrying the SDK's own relocate-funds-before-upgrading warning — anyone holding local-only tokens must move them to server inventory on their current version before upgrading, or they are stranded until they downgrade.Event names
The examples now subscribe with the current names —
transfer:updated,transfer:attention,inventory:updated,history:updated,payment_request:updated,connection:status, andtransfer:incoming(unchanged). Old names still fire via the host's compatibility adapter, so this is a teaching fix, not a functional one. The old browser list also contained four names (payment_request:accepted,payment_request:response,sync:started,sync:error) that never existed in any SDK release.What I had to drop
sphere_getInvoices,sphere_getInvoiceStatus, the nine invoice intents and theinvoice:read/invoice:writescopes are deleted in 0.14 (modules/accountingandmodules/swapare gone from the SDK entirely). No example used them — onlybrowser/CONNECT.mdmentioned them, as "experimental, not supported". That note is now a removal note.RPC_METHODSis 14,INTENT_ACTIONSis 6,PERMISSION_SCOPESis 13; the locked-gate counts in the docs were corrected to match (4 of 14 served while locked, not 4 of 16).payments.connectionStatus(), which the design doc describes but 0.14.1 does not ship — so nothing here uses it.connection:statusevents work and are subscribed to.Nothing else was unmigratable; no sample is left broken.
Per-package gate results
bottsc --noEmitbrowsertsc -b+ vite buildnodejstsc --noEmitbackend-auth/frontendtsc -b+ vite buildbackend-auth/backendtsc --noEmitNo test was weakened; no
.skip/.only.Verified live, not just compiled
wallet-api.unicity.network: booted with the new composition, auto-provisioned its aggregator key, minted 100 UCT viapayments.mint, and read the credit back throughassets()/history().ConnectHostwith a synthetic pre-0.14.1 handshake.New tests worth calling out
Both were mutation-tested — reverting the code they cover makes them fail, so they assert something.
bot/src/sendSafety.ts+ tests. Migrating the bot's send error handling nearly introduced a money bug. The old code checkedisPossiblyCommittedSendOutcome(err) || err.code === 'SEND_PARTIALLY_COMPLETED'; the second clause looks redundant (that code is in the SDK's set — I verified) so I initially dropped it. ButisPossiblyCommittedSendOutcomegates oninstanceof SphereError, which silently returnsfalsewhen two SDK copies land in one dependency tree — the exact hazard this repo's ownconnectErrors.tscalls out forConnectError. A false negative there routes a possibly-committed send into the "failed, safe to retry" branch, i.e. the double-pay. Restored as a duck-typed fallback over the whole code set, with tests pinning the cross-bundle case.EventLogPanelname assertions. The existing tests only checked for duplicates and badge colours, so they would have passed with the stale event list unchanged.Two smaller robustness fixes found in the same pass: the bot's
inventory:updatedhandler now has an epoch guard (two overlappingassets()reads could resolve out of order and print a stale balance after a fresher one), and balance formatting can no longer throw the process down on an asset with missingdecimals.Incidental
botandbackend-auth/backendhad no CI coverage at all — added jobs for both (andtypecheckscripts, which they lacked). The bot is where all the real breakage was, so it should not have been the untested one.The lockfiles shrink by ~500 lines each: 0.14.1 drops the libp2p / ipns / multiformats transitive stack.