Skip to content

chore(sdk): 0.13.1 → 0.14.1 — Connect version floor + the payments-v2 flip - #23

Merged
KruGoL merged 2 commits into
mainfrom
chore/sdk-0.14.1
Aug 8, 2026
Merged

chore(sdk): 0.13.1 → 0.14.1 — Connect version floor + the payments-v2 flip#23
KruGoL merged 2 commits into
mainfrom
chore/sdk-0.14.1

Conversation

@MastaP

@MastaP MastaP commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Moves all five packages to @unicitylabs/sphere-sdk 0.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. ConnectHost applies a built-in default of 0.14.1-0 (overridable via ConnectHostConfig.minSdkVersion) and refuses anything below it with UNSUPPORTED_PROTOCOL_VERSION (4007) — before any approval UI appears.

A 0.13.1 ConnectClient does not report an sdkVersion in the handshake at all, so it is rejected as "unknown". Confirmed by replaying a pre-0.14.1 handshake against a real ConnectHost:

{ "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.payments is the v2 facade now. Only the bot/ package calls it directly:

before (≤0.13) now
payments.mintFungibleToken(hex, amt) payments.mint(hex, amt)
payments.getBalance() (sync) await payments.assets()
payments.send(req) unchanged in shape
payments.getHistory() payments.history({ before?, limit? }) — paged

Also added to the bot's command loop: pendingTransfers() (pending) and resumeNow() (resume). resumeNow() is the only safe retry for a possibly-committed send — it replays the same transferId instead of issuing a second spend.

sphere.paymentsV2 exists 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/.

TokenStorageProvider and both platform implementations are deleted, along with the tokenStorage / tokensDir options and createOwnStorageWalletApiProviders. Sphere.init now throws a typed INVALID_CONFIG without a walletApi composition. There is exactly one supported shape:

const base = createNodeProviders({ network: 'testnet2', dataDir, oracle: { apiKey } });
const providers = createWalletApiProviders(base, { baseUrl, network: 'testnet2', deviceId });
const { sphere } = await Sphere.init({ ...providers, network: 'testnet2' });

So WALLET_API_URL is 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.ts documented the own-storage-vs-wallet-api custody choice at length. That prose was now false and is rewritten to the single supported composition, and bot/README.md gained 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, and transfer: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

  • The invoice / accounting Connect surface. sphere_getInvoices, sphere_getInvoiceStatus, the nine invoice intents and the invoice:read / invoice:write scopes are deleted in 0.14 (modules/accounting and modules/swap are gone from the SDK entirely). No example used them — only browser/CONNECT.md mentioned them, as "experimental, not supported". That note is now a removal note. RPC_METHODS is 14, INTENT_ACTIONS is 6, PERMISSION_SCOPES is 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:status events work and are subscribed to.

Nothing else was unmigratable; no sample is left broken.

Per-package gate results

package install typecheck / build tests
bot tsc --noEmit ✅ 37 (was 25)
browser tsc -b + vite build ✅ 72 (was 70)
nodejs tsc --noEmit ✅ 18
backend-auth/frontend tsc -b + vite build ✅ 12 (was 8)
backend-auth/backend tsc --noEmit ✅ 4

No test was weakened; no .skip / .only.

Verified live, not just compiled

  • The bot ran end-to-end against testnet2 + wallet-api.unicity.network: booted with the new composition, auto-provisioned its aggregator key, minted 100 UCT via payments.mint, and read the credit back through assets() / history().
  • The nodejs mock host + CLI client ran over the real WebSocket transport (identity / balance / history / send / disconnect).
  • The version floor was confirmed against a real ConnectHost with 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 checked isPossiblyCommittedSendOutcome(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. But isPossiblyCommittedSendOutcome gates on instanceof SphereError, which silently returns false when two SDK copies land in one dependency tree — the exact hazard this repo's own connectErrors.ts calls out for ConnectError. 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.
  • EventLogPanel name 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:updated handler now has an epoch guard (two overlapping assets() 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 missing decimals.

Incidental

bot and backend-auth/backend had no CI coverage at all — added jobs for both (and typecheck scripts, 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.

… 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread browser/src/components/events/EventLogPanel.tsx
@MastaP
MastaP requested a review from KruGoL August 7, 2026 17:30
… 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).
@KruGoL
KruGoL merged commit af08e27 into main Aug 8, 2026
5 checks passed
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants