From 43a175e36b856b6cbf0a1612632a720254497584 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Fri, 7 Aug 2026 19:16:18 +0200 Subject: [PATCH 1/2] =?UTF-8?q?chore(sdk):=200.13.1=20=E2=86=92=200.14.1?= =?UTF-8?q?=20=E2=80=94=20Connect=20version=20floor=20+=20the=20payments-v?= =?UTF-8?q?2=20flip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/ci.yml | 32 + CLAUDE.md | 52 +- README.md | 23 +- backend-auth/README.md | 8 + backend-auth/backend/package-lock.json | 523 ++-------------- backend-auth/backend/package.json | 3 +- backend-auth/frontend/package-lock.json | 523 ++-------------- backend-auth/frontend/package.json | 2 +- backend-auth/frontend/src/errors.test.ts | 42 +- backend-auth/frontend/src/errors.ts | 26 + bot/.env.example | 14 +- bot/README.md | 126 ++-- bot/package-lock.json | 523 ++-------------- bot/package.json | 3 +- bot/src/coins.test.ts | 32 +- bot/src/coins.ts | 48 +- bot/src/index.ts | 188 +++--- bot/src/provisionAggregatorKey.ts | 21 +- bot/src/sendSafety.test.ts | 62 ++ bot/src/sendSafety.ts | 49 ++ bot/src/smoke.ts | 11 +- bot/src/sphere.ts | 139 ++--- browser/CONNECT.md | 54 +- browser/README.md | 8 +- browser/package-lock.json | 578 ++---------------- browser/package.json | 2 +- .../components/events/EventLogPanel.test.ts | 45 ++ .../src/components/events/EventLogPanel.tsx | 57 +- browser/src/hooks/useWalletConnect.test.ts | 12 +- nodejs/README.md | 15 + nodejs/package-lock.json | 523 ++-------------- nodejs/package.json | 3 +- nodejs/src/mockSphere.ts | 138 +++-- 33 files changed, 1082 insertions(+), 2803 deletions(-) create mode 100644 bot/src/sendSafety.test.ts create mode 100644 bot/src/sendSafety.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e441408..c96cd73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,3 +59,35 @@ jobs: - run: npm install - run: npm test - run: npx tsc -b + + bot: + runs-on: ubuntu-latest + defaults: + run: + working-directory: bot + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: bot/package-lock.json + - run: npm install + - run: npm test + - run: npm run typecheck + + backend-auth-backend: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend-auth/backend + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: backend-auth/backend/package-lock.json + - run: npm install + - run: npm test + - run: npm run typecheck diff --git a/CLAUDE.md b/CLAUDE.md index a9126ff..c5b5dea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,12 @@ # CLAUDE.md - Sphere SDK Connect Example +> **SDK floor:** every package pins `@unicitylabs/sphere-sdk` **0.14.1** exactly. Wallet hosts +> from 0.14.1 enforce an SDK version floor at the Connect handshake (`ConnectHost`'s built-in +> default is `0.14.1-0`, overridable via `ConnectHostConfig.minSdkVersion`): a client on an older +> SDK — or one too old to report a version, i.e. anything before 0.14.1 — is refused with +> `UNSUPPORTED_PROTOCOL_VERSION` (4007) carrying `data.requiredSdk` / `data.actualSdk`. The +> Connect protocol is unchanged at **2.1**. + Demonstration project with four runnable examples of working with a Sphere wallet: a **browser dApp** and a **Node.js dApp** (both use the Connect protocol to drive a user's wallet), a **bot** that runs its own wallet (direct SDK, no Connect), and a **backend-auth** flow (a frontend brokers a wallet signature, a backend verifies it and issues a JWT). The Connect module enables dApps to interact with Sphere wallets through a transport-agnostic, permission-based RPC interface. ## Project Structure @@ -55,9 +62,10 @@ sphere-sdk-connect-example/ │ ├── bot/ # Bot that runs its OWN wallet (direct SDK, NOT Connect) │ ├── src/ -│ │ ├── sphere.ts # own-wallet Sphere.init (+ optional wallet-api receive rail) +│ │ ├── sphere.ts # own-wallet Sphere.init (createNodeProviders + createWalletApiProviders) │ │ ├── index.ts # DM auto-reply, self-mint, money-safe send loop -│ │ ├── coins.ts # symbol→coinId + human→base-unit helpers (unit-tested) +│ │ ├── coins.ts # symbol→coinId + human↔base-unit helpers (unit-tested) +│ │ ├── sendSafety.ts # mayBeCommitted(): never re-send a possibly-committed spend (unit-tested) │ │ ├── aggregatorKey.ts # env key → saved → SGW auto-provision + persist (unit-tested) │ │ ├── provisionAggregatorKey.ts, sgwChallenge.ts # SGW challenge/sign/verify │ │ └── *.test.ts @@ -111,7 +119,7 @@ cp .env.example .env npm start # boots its own wallet on testnet2, self-mints, DM-echoes ``` -No Connect — the bot *is* the wallet. Leave `AGGREGATOR_API_KEY` empty and it auto-provisions its own free-plan SGW key on first boot and persists it. +No Connect — the bot *is* the wallet. Leave `AGGREGATOR_API_KEY` empty and it auto-provisions its own free-plan SGW key on first boot and persists it. `WALLET_API_URL` is **required**: sphere-sdk 0.14 deleted own-storage custody (`tokenStorage` / `tokensDir` and both `TokenStorageProvider` implementations are gone), so `Sphere.init` throws `INVALID_CONFIG` without a `walletApi` composition. Keys/identity stay local under `BOT_DATA_DIR`; tokens, history and payment requests live in wallet-api. ### Backend Auth (sign in with a wallet) @@ -126,9 +134,9 @@ Frontend brokers a `sign_message`; backend recovers the pubkey via `recoverPubke ## Dependencies -All five packages pin the same published SDK version: +All five packages pin the same published SDK version, exactly (no caret): ```json -"@unicitylabs/sphere-sdk": "0.13.0" +"@unicitylabs/sphere-sdk": "0.14.1" ``` - **Browser / backend-auth frontend:** React 19, Vite 7 @@ -195,6 +203,12 @@ The browser `tsconfig.json` requires explicit path mappings for connect submodul | `sphere_subscribe` | Subscribe to events | `events:subscribe` | | `sphere_unsubscribe` | Unsubscribe | `events:subscribe` | +`RPC_METHODS` has **14** members (the 9 above plus `sphere_disconnect` and the four DM reads: +`sphere_getConversations`, `sphere_getMessages`, `sphere_getDMUnreadCount`, `sphere_markAsRead`). +`PERMISSION_SCOPES` has **13**. The invoice surface (`sphere_getInvoices`, +`sphere_getInvoiceStatus`, the nine invoice intents, `invoice:read` / `invoice:write`) was +**deleted in sphere-sdk 0.14** — it never shipped enabled in any wallet host. + **Intents** (require user approval each time): | Intent Action | Description | Required Permission | |--------------|-------------|---------------------| @@ -245,12 +259,14 @@ HOST_READY_TIMEOUT = 30_000 // ms | 4004 | `SESSION_EXPIRED` | Session TTL exceeded | | 4005 | `ORIGIN_BLOCKED` | Origin not allowed | | 4006 | `RATE_LIMITED` | Too many requests | -| 4007 | `UNSUPPORTED_PROTOCOL_VERSION` | Connect protocol MAJOR mismatch | +| 4007 | `UNSUPPORTED_PROTOCOL_VERSION` | Connect protocol MAJOR mismatch **or** the client's npm SDK version is below the host's floor (`data.requiredSdk` / `data.actualSdk`) | | 4008 | `INCOMPATIBLE_NETWORK` | dApp targets a different network than the wallet | +| 4009 | `WALLET_LOCKED` | Wallet locked; the session is still alive | | 4100 | `INSUFFICIENT_BALANCE` | Not enough tokens | | 4101 | `INVALID_RECIPIENT` | Bad recipient address | | 4102 | `TRANSFER_FAILED` | Transfer error | | 4200 | `INTENT_CANCELLED` | Intent cancelled | +| 4201 | `INTENT_OUTCOME_UNKNOWN` | The wallet took the intent and the answer was lost — **never retry**, reconcile | ## Key Implementation Details @@ -299,7 +315,7 @@ Token metadata (symbol, name, decimals, iconUrl) comes from the wallet's TokenRe ### Mock Wallet Server (`nodejs/src/mock-wallet-server.ts`) -- Creates `ConnectHost` with a mock `SphereInstance` (`src/mockSphere.ts`, shared with the tests) +- Creates `ConnectHost` with a mock `SphereInstance` (`src/mockSphere.ts`, shared with the tests). The mock is shaped like a real 0.14 wallet: `payments` is the payments-v2 facade (`assets()` / `tokens()` / paged `history()`) and `paymentsV2` is the deprecated alias `ConnectHost` reads to detect a v2 wallet - Auto-approves all connection requests with full permissions - Auto-approves all intents with action-specific success responses - Returns rich mock data: identity, assets (UCT + USDU with fiat/24h change), tokens (with statuses), history @@ -313,16 +329,22 @@ Token metadata (symbol, name, decimals, iconUrl) comes from the wallet's TokenRe - `wallet:disconnected` — session destroyed; re-handshake to continue - `identity:changed` — address switch -Subscribable events (via `client.on()`): -- `transfer:incoming` — Received tokens -- `transfer:confirmed` — Outgoing confirmed -- `transfer:failed` — Outgoing failed +Subscribable events (via `client.on()`), using the **sphere-sdk 0.14 names**: +- `transfer:incoming` — Received tokens (unchanged across the flip) +- `transfer:updated` — A transfer advanced; read `status` / `deliveryPending` (replaces `transfer:confirmed`, `transfer:delivery_pending`, `transfer:failed`) +- `transfer:attention` — `{ transferId, code, detail? }` (replaces `split:checkpoint-stuck`, `delivery:undeliverable`, `delivery:deferred`) +- `inventory:updated` — Token inventory changed (replaces the `sync:*` family) +- `history:updated` — A history entry was recorded +- `payment_request:incoming` — Payment request received +- `payment_request:updated` — `{ id, status }` (replaces `payment_request:paid` / `:rejected` / `:expired`) +- `connection:status` — `{ status: 'connected' | 'degraded' | 'offline' }` (replaces `realtime:status` + `storage:degraded`) - `identity:changed` — Address switch -- `nametag:registered` — Nametag registered -- `nametag:recovered` — Nametag recovered +- `nametag:registered` / `nametag:recovered` — Nametag lifecycle - `address:activated` — New address tracked -- `sync:provider` — Sync result -- `payment_request:incoming` — Payment request received + +Every pre-0.14 name still fires: the host re-emits each one from the new event through a +compatibility adapter, so no dApp subscription silently went dead. New code uses the names above. +`browser/src/components/events/EventLogPanel.tsx` holds the canonical list this repo subscribes to. ## Connect Module Source (in sphere-sdk) diff --git a/README.md b/README.md index cbc3c5a..c79ee24 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,14 @@ seeing the private keys**. The dApp runs a `ConnectClient`; the wallet runs a dApp (ConnectClient) ←→ Transport ←→ Wallet (ConnectHost) ``` +> **All five packages require `@unicitylabs/sphere-sdk` ≥ 0.14.1.** Wallet hosts +> from 0.14.1 onward enforce an **SDK version floor at the handshake**: a client +> built on an older SDK is refused with `UNSUPPORTED_PROTOCOL_VERSION` (4007) +> before any approval UI appears, so a dApp that is not bumped simply stops +> connecting. The Connect protocol itself is unchanged (still **2.1**) — this is +> a dependency bump and a rebuild, nothing more. See +> [browser/CONNECT.md](browser/CONNECT.md#-your-dapp-needs-unicitylabssphere-sdk--0141). + Not every example uses Connect: the **bot** runs its own wallet directly, and the **backend** in backend-auth only verifies a signature. See the guide below. @@ -27,7 +35,7 @@ it needs to do** with it. |---|---|---|---|---| | **`browser/`** | a web page / dApp | the **user's**, elsewhere | ✅ with per-action approval | your dApp runs in a browser and the user is present to approve each send — e.g. a **browser game** where the player approves in-game purchases | | **`nodejs/`** | a Node CLI / service | the **user's**, over WebSocket | ✅ with approval | you need a **CLI / desktop / server** dApp that drives a user's wallet over a WebSocket (see the caveat in its README) | -| **`bot/`** | an autonomous agent | **its own** (own keys) | ✅ no approval — it owns the funds | you're building a **tipping bot, faucet, game NPC that pays rewards, or an agent** that acts from its own float with no human in the loop | +| **`bot/`** | an autonomous agent | **its own** (own keys, wallet-api custody) | ✅ no approval — it owns the funds | you're building a **tipping bot, faucet, game NPC that pays rewards, or an agent** that acts from its own float with no human in the loop | | **`backend-auth/`** | a frontend + backend | the **user's** (frontend only signs) | ❌ **auth only** | you need to know **who** the player is — login, leaderboards, ownership, sessions — but do **not** need to move their tokens. **Recommended for games/apps that need authenticated identity, not custody or intents** | Rules of thumb: @@ -68,10 +76,12 @@ Drives a wallet over `WebSocketTransport`. See [nodejs/README.md](nodejs/README. ```bash cd bot npm install -cp .env.example .env -npm start # boots its own wallet on testnet2, self-mints, DM-echoes +cp .env.example .env # WALLET_API_URL is REQUIRED — token custody lives there +npm start # boots its own wallet on testnet2, self-mints, DM-echoes ``` -No Connect involved — the bot *is* the wallet. See [bot/README.md](bot/README.md). +No Connect involved — the bot *is* the wallet. Since sphere-sdk 0.14 there is no +own-storage custody: `Sphere.init` throws `INVALID_CONFIG` without a `walletApi` +composition, so `WALLET_API_URL` must be set. See [bot/README.md](bot/README.md). ### `backend-auth/` — sign in with a Sphere wallet @@ -85,10 +95,11 @@ Frontend brokers a `sign_message`; backend recovers the pubkey and issues a JWT. ## Dependencies -All five packages pin the same published SDK version: +All five packages pin the same published SDK version, exactly (no caret — these +are examples, and pin clarity matters more than float): ```json -"@unicitylabs/sphere-sdk": "0.13.0" +"@unicitylabs/sphere-sdk": "0.14.1" ``` ## Documentation diff --git a/backend-auth/README.md b/backend-auth/README.md index 3644de6..5687bec 100644 --- a/backend-auth/README.md +++ b/backend-auth/README.md @@ -6,6 +6,14 @@ Sphere wallet's signature — not by trusting anything the client claims. A server independently recovers the signer's public key from that signature and issues a session JWT. +> **The frontend needs `@unicitylabs/sphere-sdk` ≥ 0.14.1.** Wallet hosts from +> 0.14.1 enforce an SDK version floor at the handshake and refuse older clients +> with `UNSUPPORTED_PROTOCOL_VERSION` (4007) before any approval UI appears — +> so an un-bumped dApp simply stops signing anybody in. `src/errors.ts` +> (`describeVersionFloor`) turns that refusal into copy that names the required +> version. The **backend** is unaffected: it only calls +> `recoverPubkeyFromSignature` / `verifySignedMessage`, which are unchanged. + ## Topology ``` diff --git a/backend-auth/backend/package-lock.json b/backend-auth/backend/package-lock.json index cbfe3cf..9c81e5b 100644 --- a/backend-auth/backend/package-lock.json +++ b/backend-auth/backend/package-lock.json @@ -8,7 +8,7 @@ "name": "sphere-connect-backend-auth-example", "version": "0.0.0", "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "dotenv": "^17.4.2", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", @@ -26,27 +26,6 @@ "node": ">=22" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT", - "optional": true - }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "optional": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -530,156 +509,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT", - "optional": true - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.21.tgz", - "integrity": "sha512-GipsBinthJuk7DpwthTUixZHSaKogcxm2glPCP0VkYkEpzZrfEvBEekRvlP5+1Ak8pReaHcz4gPKFA9X6MG0WQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/crypto/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.3", - "main-event": "^1.0.1", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/logger": { - "version": "6.2.10", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.10.tgz", - "integrity": "sha512-recRqNABHG32YXvpMvNepFCuU14d51dF5hs+vR2C/tkqXBQc0Sqg5LtWB2NYPoIJV+JUB50iWLrvBXZUOjLc9Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-datastore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", - "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2", - "interface-store": "^8.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-store": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", - "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2" - } - }, - "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.12.tgz", - "integrity": "sha512-U44MeKs+U1SqZPqS0RDzjjcS9ERix2zJsJkTcqi8I+5flEz4uVAObBDxvkkaTaqRFRXCXEPjL5kA4UbgVubCZQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.1.21", - "@libp2p/interface": "^3.2.5", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -712,12 +541,12 @@ } }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -727,9 +556,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1256,16 +1085,16 @@ } }, "node_modules/@unicitylabs/sphere-sdk": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.13.1.tgz", - "integrity": "sha512-i/cnbr9Puk30b7z/JANTYN8hhp5OmVCpmo/MA2dS0SwV9wtBasQYI3sLosRXaVMCEX2n269K1y3M+BccU1KBxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.14.1.tgz", + "integrity": "sha512-8Jxro6A9bAvNYhFTAweYq08paIEVkDZayOl0MNBHmbED0ef2Si2oe+JK92VYL2DBlqfUeK2kWlJW+lAckQIZtw==", "license": "MIT", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@unicitylabs/nostr-js-sdk": "^0.6.0", - "@unicitylabs/state-transition-sdk": "2.0.1", + "@unicitylabs/state-transition-sdk": "2.0.2", "bip39": "^3.1.0", "buffer": "^6.0.3", "canonicalize": "^3.0.0", @@ -1274,41 +1103,19 @@ "engines": { "node": ">=22.0.0" }, - "optionalDependencies": { - "@libp2p/crypto": "^5.1.13", - "@libp2p/peer-id": "^6.0.4", - "ipns": "^10.0.0", - "multiformats": "^13.4.2" - }, "peerDependencies": { - "@libp2p/crypto": ">=5.0.0", - "@libp2p/peer-id": ">=6.0.0", - "ipns": ">=10.0.0", - "multiformats": ">=13.0.0", "ws": ">=8.0.0" }, "peerDependenciesMeta": { - "@libp2p/crypto": { - "optional": true - }, - "@libp2p/peer-id": { - "optional": true - }, - "ipns": { - "optional": true - }, - "multiformats": { - "optional": true - }, "ws": { "optional": true } } }, "node_modules/@unicitylabs/state-transition-sdk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.1.tgz", - "integrity": "sha512-O0pjxaL32VgHYjSM3Ei64UWBG2qwBzdQ9aIdlyhzTnPdI+kKu3h9saSMF1uV9XTfqKHsNGUpE/2dFUulNfmdmg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.2.tgz", + "integrity": "sha512-SYCCShlwSUqeRhOocGkg9QCx0lacxelpRa6w3PQTLdXO8BO6Xo6Qh34ABZW2UcRfMDDPaXA0KSIyUo+UbiM9FQ==", "license": "ISC", "dependencies": { "@noble/curves": "2.2.0", @@ -1319,6 +1126,33 @@ "node": ">=22" } }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -1432,13 +1266,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1613,16 +1440,6 @@ "node": ">=18" } }, - "node_modules/cborg": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.7.tgz", - "integrity": "sha512-rGg2MG9zZEUKtKqjkBppIWUecTXf9N1vs1Qru43vJWoDaODhCrtmzdehCaA/aq/c1cPI5A0kPrJ5Tf+jIfhV4w==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1874,13 +1691,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT", - "optional": true - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -2076,13 +1886,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT", - "optional": true - }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -2153,34 +1956,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/interface-datastore": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-9.0.3.tgz", - "integrity": "sha512-NLZa7Mp+0qn48nSwIY/C36da4uVIKzwG2tuEIpaSJArsuB2RrdyDWwkoDUyjsJ+VrMntXz38VSk9vXTx/ZUpAw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "interface-store": "^7.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-datastore/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/interface-store": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-7.0.2.tgz", - "integrity": "sha512-KYOPcDH+1peaPhSeoZujR5nwkVeola1EdrnrlHTIM0HRNUs9B0aTsUQMH5kTmIjaQq1BOowoUyoCamgL8IMyww==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -2190,68 +1965,6 @@ "node": ">= 0.10" } }, - "node_modules/ipns": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-10.1.6.tgz", - "integrity": "sha512-mTACIdTBBXY5kbCo3t/M3ycNWbjajLrSXhTvjD0MEGyOUaI3uMv94JbJSHGaJ2xxRlpOrRk1ifToOsyPZiglnA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/logger": "^6.0.4", - "cborg": "^5.1.0", - "interface-datastore": "^9.0.2", - "multiformats": "^13.2.2", - "protons-runtime": "^6.0.1", - "timestamp-nano": "^1.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/protons-runtime": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", - "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/uint8-varint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", - "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/ipns/node_modules/uint8arraylist": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", - "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/ipns/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -2620,13 +2333,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/main-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.4.tgz", - "integrity": "sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2702,13 +2408,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/nanoid": { "version": "5.1.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", @@ -2774,36 +2473,6 @@ "node": ">= 0.8" } }, - "node_modules/p-queue": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.1.tgz", - "integrity": "sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventemitter3": "^5.0.4", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2894,25 +2563,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/progress-events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", - "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3201,29 +2851,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -3331,44 +2958,6 @@ "node": ">=14.17" } }, - "node_modules/uint8-varint": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", - "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^3.0.1", - "uint8arrays": "^6.1.0" - } - }, - "node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^14.0.0" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -3385,13 +2974,6 @@ "node": ">= 0.8" } }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT", - "optional": true - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -3591,27 +3173,6 @@ } } }, - "node_modules/weald": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", - "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "ms": "^4.0.0-nightly.202508271359", - "supports-color": "^10.0.0" - } - }, - "node_modules/weald/node_modules/ms": { - "version": "4.0.0-nightly.202508271359", - "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", - "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/backend-auth/backend/package.json b/backend-auth/backend/package.json index f980815..16dd460 100644 --- a/backend-auth/backend/package.json +++ b/backend-auth/backend/package.json @@ -8,10 +8,11 @@ }, "scripts": { "start": "tsx src/server.ts", + "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "dotenv": "^17.4.2", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", diff --git a/backend-auth/frontend/package-lock.json b/backend-auth/frontend/package-lock.json index 826830e..9e5078f 100644 --- a/backend-auth/frontend/package-lock.json +++ b/backend-auth/frontend/package-lock.json @@ -8,7 +8,7 @@ "name": "sphere-connect-backend-auth-frontend", "version": "0.0.0", "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "react": "^19.1.1", "react-dom": "^19.2.4" }, @@ -303,27 +303,6 @@ "node": ">=6.9.0" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT", - "optional": true - }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "optional": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -816,156 +795,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT", - "optional": true - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.21.tgz", - "integrity": "sha512-GipsBinthJuk7DpwthTUixZHSaKogcxm2glPCP0VkYkEpzZrfEvBEekRvlP5+1Ak8pReaHcz4gPKFA9X6MG0WQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/crypto/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.3", - "main-event": "^1.0.1", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/logger": { - "version": "6.2.11", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.11.tgz", - "integrity": "sha512-zZ4m2EKyyRY2G8GAzmG/ZJm4CIqmaJucl5X7zTmGs1SXlb4Ayj4aP1vsaP/cVWadf/RuPWjgmIJP+Y/sZoE7Rg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-datastore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", - "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2", - "interface-store": "^8.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-store": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", - "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2" - } - }, - "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.13.tgz", - "integrity": "sha512-A3sP3QUjunp7Wo5aGOkRsuYtGJZbLD4KPlHKObMam1fUCx+gHKnfyKeaWMQLkjeaUNETz9R6llquWyi41QzkLg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.1.21", - "@libp2p/interface": "^3.2.5", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/@noble/ciphers": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", @@ -979,12 +808,12 @@ } }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -994,9 +823,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1524,16 +1353,16 @@ } }, "node_modules/@unicitylabs/sphere-sdk": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.13.1.tgz", - "integrity": "sha512-i/cnbr9Puk30b7z/JANTYN8hhp5OmVCpmo/MA2dS0SwV9wtBasQYI3sLosRXaVMCEX2n269K1y3M+BccU1KBxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.14.1.tgz", + "integrity": "sha512-8Jxro6A9bAvNYhFTAweYq08paIEVkDZayOl0MNBHmbED0ef2Si2oe+JK92VYL2DBlqfUeK2kWlJW+lAckQIZtw==", "license": "MIT", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@unicitylabs/nostr-js-sdk": "^0.6.0", - "@unicitylabs/state-transition-sdk": "2.0.1", + "@unicitylabs/state-transition-sdk": "2.0.2", "bip39": "^3.1.0", "buffer": "^6.0.3", "canonicalize": "^3.0.0", @@ -1542,41 +1371,19 @@ "engines": { "node": ">=22.0.0" }, - "optionalDependencies": { - "@libp2p/crypto": "^5.1.13", - "@libp2p/peer-id": "^6.0.4", - "ipns": "^10.0.0", - "multiformats": "^13.4.2" - }, "peerDependencies": { - "@libp2p/crypto": ">=5.0.0", - "@libp2p/peer-id": ">=6.0.0", - "ipns": ">=10.0.0", - "multiformats": ">=13.0.0", "ws": ">=8.0.0" }, "peerDependenciesMeta": { - "@libp2p/crypto": { - "optional": true - }, - "@libp2p/peer-id": { - "optional": true - }, - "ipns": { - "optional": true - }, - "multiformats": { - "optional": true - }, "ws": { "optional": true } } }, "node_modules/@unicitylabs/state-transition-sdk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.1.tgz", - "integrity": "sha512-O0pjxaL32VgHYjSM3Ei64UWBG2qwBzdQ9aIdlyhzTnPdI+kKu3h9saSMF1uV9XTfqKHsNGUpE/2dFUulNfmdmg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.2.tgz", + "integrity": "sha512-SYCCShlwSUqeRhOocGkg9QCx0lacxelpRa6w3PQTLdXO8BO6Xo6Qh34ABZW2UcRfMDDPaXA0KSIyUo+UbiM9FQ==", "license": "ISC", "dependencies": { "@noble/curves": "2.2.0", @@ -1587,6 +1394,33 @@ "node": ">=22" } }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -1721,13 +1555,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1883,16 +1710,6 @@ "node": ">=18" } }, - "node_modules/cborg": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.8.tgz", - "integrity": "sha512-enonPtLyeEF8M8GF/5NdjLHmRGslrwKR2LvkN+yFYQJw7mzt6iOoPvkVs3vcb3rUvltzL0QfvtM6FQ44o8nhSg==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -2017,13 +1834,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT", - "optional": true - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -2077,13 +1887,6 @@ "node": ">=6.9.0" } }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT", - "optional": true - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2104,96 +1907,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/interface-datastore": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-9.0.3.tgz", - "integrity": "sha512-NLZa7Mp+0qn48nSwIY/C36da4uVIKzwG2tuEIpaSJArsuB2RrdyDWwkoDUyjsJ+VrMntXz38VSk9vXTx/ZUpAw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "interface-store": "^7.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-datastore/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/interface-store": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-7.0.2.tgz", - "integrity": "sha512-KYOPcDH+1peaPhSeoZujR5nwkVeola1EdrnrlHTIM0HRNUs9B0aTsUQMH5kTmIjaQq1BOowoUyoCamgL8IMyww==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/ipns": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-10.1.6.tgz", - "integrity": "sha512-mTACIdTBBXY5kbCo3t/M3ycNWbjajLrSXhTvjD0MEGyOUaI3uMv94JbJSHGaJ2xxRlpOrRk1ifToOsyPZiglnA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/logger": "^6.0.4", - "cborg": "^5.1.0", - "interface-datastore": "^9.0.2", - "multiformats": "^13.2.2", - "protons-runtime": "^6.0.1", - "timestamp-nano": "^1.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/protons-runtime": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", - "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/uint8-varint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", - "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/ipns/node_modules/uint8arraylist": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", - "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/ipns/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2253,13 +1966,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/main-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.4.tgz", - "integrity": "sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2267,13 +1973,6 @@ "dev": true, "license": "MIT" }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -2317,36 +2016,6 @@ "node": ">=12.20.0" } }, - "node_modules/p-queue": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", - "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventemitter3": "^5.0.4", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2403,25 +2072,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/progress-events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", - "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, "node_modules/react": { "version": "19.2.7", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", @@ -2545,29 +2195,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2626,44 +2253,6 @@ "node": ">=14.17" } }, - "node_modules/uint8-varint": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", - "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^3.0.1", - "uint8arrays": "^6.1.0" - } - }, - "node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^14.0.0" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2695,13 +2284,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT", - "optional": true - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -2880,27 +2462,6 @@ } } }, - "node_modules/weald": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", - "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "ms": "^4.0.0-nightly.202508271359", - "supports-color": "^10.0.0" - } - }, - "node_modules/weald/node_modules/ms": { - "version": "4.0.0-nightly.202508271359", - "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", - "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/backend-auth/frontend/package.json b/backend-auth/frontend/package.json index 2274593..5fd053e 100644 --- a/backend-auth/frontend/package.json +++ b/backend-auth/frontend/package.json @@ -10,7 +10,7 @@ "test": "vitest run" }, "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "react": "^19.1.1", "react-dom": "^19.2.4" }, diff --git a/backend-auth/frontend/src/errors.test.ts b/backend-auth/frontend/src/errors.test.ts index af07df2..dabfb33 100644 --- a/backend-auth/frontend/src/errors.test.ts +++ b/backend-auth/frontend/src/errors.test.ts @@ -1,8 +1,10 @@ import { describe, it, expect } from 'vitest'; import { ERROR_CODES } from '@unicitylabs/sphere-sdk/connect'; -import { describeError, isConnectErrorCode, isWalletLocked } from './errors'; +import { describeError, describeVersionFloor, isConnectErrorCode, isWalletLocked } from './errors'; const coded = (code: number, message = 'refused') => Object.assign(new Error(message), { code }); +const codedWithData = (code: number, message: string, data: unknown) => + Object.assign(new Error(message), { code, data }); describe('isWalletLocked', () => { it('is true only for 4009', () => { @@ -31,6 +33,18 @@ describe('describeError', () => { ); }); + it('names the required SDK version when the wallet enforces its floor', () => { + const text = describeError( + codedWithData( + ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, + 'SDK version unknown (not reported) is below the required minimum 0.14.1-0', + { reason: 'protocol_incompatible', requiredSdk: '0.14.1-0', actualSdk: null }, + ), + ); + expect(text).toContain('0.14.1-0'); + expect(text).toMatch(/reported no sphere-sdk version/); + }); + it('falls back to the error message', () => { expect(describeError(new Error('Could not reach the backend'))).toBe('Could not reach the backend'); }); @@ -40,6 +54,32 @@ describe('describeError', () => { }); }); +describe('describeVersionFloor', () => { + it('is null for any other code', () => { + expect(describeVersionFloor(coded(ERROR_CODES.USER_REJECTED))).toBeNull(); + }); + + it('is null when the host sent no versions to name', () => { + expect( + describeVersionFloor(codedWithData(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'nope', { reason: 'x' })), + ).toBeNull(); + }); + + it('names both versions when the host reported them', () => { + expect( + describeVersionFloor( + codedWithData(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'nope', { + requiredSdk: '0.14.1-0', + actualSdk: '0.13.1', + }), + ), + ).toBe( + 'This app is built on sphere-sdk 0.13.1 — the wallet requires 0.14.1-0 or newer. ' + + 'Upgrade @unicitylabs/sphere-sdk and rebuild.', + ); + }); +}); + describe('isConnectErrorCode', () => { it('duck-types on a numeric code', () => { expect(isConnectErrorCode(coded(4003), 4003)).toBe(true); diff --git a/backend-auth/frontend/src/errors.ts b/backend-auth/frontend/src/errors.ts index e426a2e..80c5219 100644 --- a/backend-auth/frontend/src/errors.ts +++ b/backend-auth/frontend/src/errors.ts @@ -11,10 +11,36 @@ export function isWalletLocked(err: unknown): boolean { return isConnectErrorCode(err, ERROR_CODES.WALLET_LOCKED); } +/** A non-empty string field of an untrusted `data` bag, or null. */ +function text(value: unknown): string | null { + return typeof value === 'string' && value.length > 0 ? value : null; +} + +/** + * A wallet host on sphere-sdk >= 0.14.1 refuses any dApp built on an older SDK with + * UNSUPPORTED_PROTOCOL_VERSION (4007), before any approval UI appears. It publishes the two + * versions it compared in `error.data`, so say which version is needed rather than rendering + * a bare "incompatible". Read defensively — `data` crosses postMessage from a peer on an SDK + * version this app does not control. + */ +export function describeVersionFloor(err: unknown): string | null { + if (!isConnectErrorCode(err, ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION)) return null; + const data = (err as { data?: unknown }).data; + if (typeof data !== 'object' || data === null) return null; + const bag = data as Record; + const requiredSdk = text(bag.requiredSdk); + if (!requiredSdk) return null; + const actualSdk = text(bag.actualSdk); + const has = actualSdk ? `is built on sphere-sdk ${actualSdk}` : 'reported no sphere-sdk version'; + return `This app ${has} — the wallet requires ${requiredSdk} or newer. Upgrade @unicitylabs/sphere-sdk and rebuild.`; +} + export function describeError(err: unknown): string { if (isConnectErrorCode(err, ERROR_CODES.USER_REJECTED) || isConnectErrorCode(err, ERROR_CODES.INTENT_CANCELLED)) { return 'You declined the signature request in your wallet.'; } + const versionFloor = describeVersionFloor(err); + if (versionFloor) return versionFloor; if (isWalletLocked(err)) { return 'Your wallet is locked. Unlock it and press Sign in again — you are still connected, so no re-approval is needed.'; } diff --git a/bot/.env.example b/bot/.env.example index d2f3402..ad90e7c 100644 --- a/bot/.env.example +++ b/bot/.env.example @@ -13,10 +13,16 @@ AGGREGATOR_API_KEY= # The bot's own wallet. Leave BOT_MNEMONIC empty on first run to auto-generate # (the bot prints the generated mnemonic — save it). Set it to persist identity. BOT_MNEMONIC= -# Local file storage for the bot's wallet + tokens +# Local file storage for the bot's KEYS + identity state. Token custody is NOT +# local any more (sphere-sdk 0.14 deleted own-storage custody) — tokens live in +# the wallet-api inventory keyed by WALLET_API_URL below. BOT_DATA_DIR=./.bot-data -# OPTIONAL: only needed to RECEIVE tokens from the hosted Sphere wallet, which -# delivers via the wallet-api mailbox (not Nostr). Leave empty to run DM + send only. +# REQUIRED. The wallet-api instance that holds this bot's token inventory and +# mailbox. Sphere.init throws INVALID_CONFIG without it — money does not move +# without wallet-api custody. # Known endpoints: prod = https://wallet-api.unicity.network # staging = https://wallet-api.staging.unicity.network -WALLET_API_URL= +WALLET_API_URL=https://wallet-api.unicity.network +# OPTIONAL stable device label for the wallet-api refresh token. Omit it and +# every run performs a fresh challenge sign-in. +BOT_DEVICE_ID= diff --git a/bot/README.md b/bot/README.md index 403d47c..2cc7b8a 100644 --- a/bot/README.md +++ b/bot/README.md @@ -1,10 +1,14 @@ # Sphere Connect — Own-Wallet Bot Example -A Node bot that runs its **own** Sphere wallet: its own keys, its own local -token storage, direct SDK usage. It boots a real `Sphere` instance against +A Node bot that runs its **own** Sphere wallet: its own keys, its own +identity, direct SDK usage. It boots a real `Sphere` instance against **testnet2** the same way a wallet app would — there is no Connect protocol involved anywhere in this example. +> **Token custody lives in wallet-api.** sphere-sdk 0.14 deleted own-storage +> custody, so `WALLET_API_URL` is now **required** — see +> [Custody: wallet-api is required](#custody-wallet-api-is-required). + This is the opposite shape from the `browser/` and `nodejs/` examples in this repo, which are **dApps** that connect *to* someone else's already-running wallet over Connect (`ConnectClient` + a transport). This bot doesn't connect @@ -14,8 +18,17 @@ What it does once running: - Replies to any incoming Nostr DM with `echo: `. - Self-mints 100 `UCT` on boot as a starting balance (best-effort — logs a message instead of failing if minting isn't available). -- Exposes a small `send` / `balance` / `help` / `exit` command loop on stdin. -- Optionally logs incoming token transfers (see "Receiving tokens" below). +- Exposes a small `send` / `balance` / `pending` / `resume` / `help` / `exit` + command loop on stdin. +- Logs incoming token transfers and replies with a thank-you DM. +- Logs `transfer:updated`, `inventory:updated` and `connection:status` — the + sphere-sdk 0.14 event names. + +> `payments.assets()` reads the **wallet-api inventory**, which the server credits +> asynchronously. The balance printed immediately after the boot mint can legitimately +> be empty even though the mint certified on-chain; `inventory:updated` is the signal +> that the server view caught up, and the bot re-prints the balance on it. A UI +> should refresh on that event rather than polling. ## When to use this @@ -37,7 +50,10 @@ Concrete examples: ## Prerequisites - Node.js **>= 22**. -- No account setup needed to send/DM: leave `AGGREGATOR_API_KEY` unset and the +- A reachable **wallet-api** instance in `WALLET_API_URL` (the `.env.example` + ships the public testnet2 one). This is required — see + [Custody: wallet-api is required](#custody-wallet-api-is-required). +- No aggregator account setup needed: leave `AGGREGATOR_API_KEY` unset and the bot provisions its own free testnet2 aggregator key on first boot (see "Aggregator key — two ways" below). @@ -62,9 +78,9 @@ SAVE THIS — set BOT_MNEMONIC to persist this identity across runs. Copy that mnemonic into `BOT_MNEMONIC` in `.env`. Without it, every restart generates a brand-new identity (and a brand-new empty wallet) instead of -resuming the previous one. Wallet + token data are stored locally under -`BOT_DATA_DIR` (default `./.bot-data`, split into `./.bot-data/wallet` and -`./.bot-data/tokens`). +resuming the previous one. Keys and identity state are stored locally under +`BOT_DATA_DIR` (default `./.bot-data`, wallet files in `./.bot-data/wallet`); +**tokens are not** — they live in the wallet-api inventory for this identity. Once it's running: @@ -73,6 +89,8 @@ Once it's running: Commands: send @to - Send L3 tokens (money-safe: no auto-resend on a possibly-committed outcome) balance - Show current balance + pending - Show transfers still converging + resume - Replay open intents (the safe retry — never re-sends) help - Show this help exit - Shut down and exit @@ -85,9 +103,10 @@ Amounts are human-readable decimals (`"1.5"`) — the bot converts them to exact integer base units itself, so there's no floating-point precision loss even for high-decimal coins. -`npm test` runs the unit tests (currently just the base-unit/coin-resolution -helpers in `src/coins.ts`, plus the SGW-provisioning challenge validator and -key-resolution fallback chain in `src/sgwChallenge.ts` / `src/aggregatorKey.ts`). +`npm test` runs the unit tests: the base-unit/coin-resolution helpers in +`src/coins.ts`, the possibly-committed classifier in `src/sendSafety.ts`, and the +SGW-provisioning challenge validator plus key-resolution fallback chain in +`src/sgwChallenge.ts` / `src/aggregatorKey.ts`. `npm run typecheck` runs `tsc --noEmit`. ## Aggregator key — two ways @@ -113,33 +132,57 @@ two ways to get one, controlled by `AGGREGATOR_API_KEY` in `.env`: The bot logs which path it took on boot: `Aggregator key: env` / `saved` / `provisioned`. The key value itself is never logged. -## Receiving tokens: the wallet-api nuance +## Custody: wallet-api is required + +Up to sphere-sdk 0.13 this bot had a choice of custody: keep tokens in its own +local file storage (`tokensDir`), or let a wallet-api server hold them. **0.14 +removed the first option.** `TokenStorageProvider` and both platform +implementations are deleted, the `tokenStorage` / `tokensDir` options are gone, +and `Sphere.init` throws a typed `INVALID_CONFIG` if no `walletApi` config is +passed. There is now exactly one supported composition, and it is what +`src/sphere.ts` builds: + +```ts +const base = createNodeProviders({ + network: 'testnet2', + dataDir: `${botDataDir}/wallet`, // keys + identity, still local + oracle: { apiKey }, +}); + +const providers = createWalletApiProviders(base, { + baseUrl: process.env.WALLET_API_URL, // token custody + mailbox + network: 'testnet2', // must match the base network + deviceId, // optional, stabilises the refresh token +}); + +const { sphere } = await Sphere.init({ ...providers, network: 'testnet2', /* … */ }); +``` -DMs and **sending** work out of the box against testnet2 — no extra config -needed. **Receiving** tokens sent from the hosted Sphere wallet (e.g. -`sphere.unicity.network`, or a locally-run `sphere` dev server) is different. +What is and isn't local now: -The hosted Sphere wallet delivers sent tokens through the **wallet-api -mailbox**, not over Nostr. If `WALLET_API_URL` is left unset, this bot never -composes that mailbox rail, so it has no way to see those deliveries — a -transfer sent to it from the hosted wallet will simply never show up, no -error, nothing to catch. The bot logs this plainly on boot: +| | Where it lives | +|---|---| +| Mnemonic, keys, identity, nametag cache | local (`StorageProvider`, `BOT_DATA_DIR`) | +| Token inventory, history, payment requests, mailbox | **wallet-api** (`WALLET_API_URL`) | +| DMs, group chat, nametag bindings | Nostr (unchanged) | -``` -WALLET_API_URL not set — this bot will not receive tokens from the hosted wallet (see README). -``` +`WALLET_API_URL` is no longer an optional receive rail — it is the money path. +The bot fails fast on boot with a clear message if it is unset. + +> **Upgrading a bot that already holds local tokens?** Relocate the funds +> **before** you upgrade: on your current (≤0.13) version, switch the +> composition to wallet-api custody and send-to-self so the tokens enter server +> inventory. Upgrading a build that still holds local-only tokens strands them +> until you downgrade and relocate. This is the SDK's own warning — see +> `sphere-sdk/docs/MIGRATION-PAYMENTS-V2.md` §3. -To fix that, set `WALLET_API_URL` in `.env` to a wallet-api endpoint, e.g. -the public testnet2 instance: +Known endpoints for `WALLET_API_URL`: ``` -WALLET_API_URL=https://wallet-api.unicity.network +prod = https://wallet-api.unicity.network +staging = https://wallet-api.staging.unicity.network ``` -With it set, the bot composes an *own-storage* wallet-api rail — it keeps -using its own local token storage and keys, it just also picks up mailbox -deliveries — and logs each incoming transfer, replying with a DM thank-you. - ## Money-safety on send `send` is deliberately conservative about anything that isn't an unambiguous @@ -148,15 +191,26 @@ success or failure: - If the SDK reports the send as **delivery-pending**, that's a *success* — the source token is already spent and the recipient's copy will land later. The bot just logs `sent, delivery pending` and moves on. -- If the SDK reports a **possibly-committed** outcome (the spend may or may - not have gone through), the bot logs `spend may already be committed — do - NOT retry` and stops there. It does **not** automatically resend. +- If the SDK reports a **possibly-committed** outcome + (`isPossiblyCommittedSendOutcome(err)`), the bot logs `spend may already be + committed — do NOT re-send` and stops there. It does **not** automatically + resend. That last point matters for any bot you build on this pattern too: retrying a possibly-committed send doesn't "try again" — it spends a *different* -source token for the same intent, which can pay the recipient twice. Treat a -possibly-committed outcome as something a human (or a higher-level -reconciliation step) needs to look at, never as "just retry." +source token for the same intent, which can pay the recipient twice. + +The safe recovery is `payments.resumeNow()` — the `resume` command — which +replays the **same** intent (same `transferId`) rather than issuing a new +spend, and `payments.pendingTransfers()` — the `pending` command — to see what +is still converging. + +The classifier lives in [`src/sendSafety.ts`](src/sendSafety.ts). It wraps the +SDK's `isPossiblyCommittedSendOutcome` with a duck-typed fallback on the same +error codes, because the SDK predicate gates on `instanceof SphereError` — which +silently returns `false` if two copies of the SDK ever land in one dependency +tree. A false negative there is precisely the retry that double-pays, so the +helper errs toward "may be committed" on any uncertainty. ## Generating a bot like this diff --git a/bot/package-lock.json b/bot/package-lock.json index c427805..b596947 100644 --- a/bot/package-lock.json +++ b/bot/package-lock.json @@ -8,7 +8,7 @@ "name": "sphere-connect-bot-example", "version": "0.0.0", "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "dotenv": "^17.4.2", "ws": "^8.21.1" }, @@ -23,27 +23,6 @@ "node": ">=22" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT", - "optional": true - }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "optional": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -527,156 +506,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT", - "optional": true - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.21.tgz", - "integrity": "sha512-GipsBinthJuk7DpwthTUixZHSaKogcxm2glPCP0VkYkEpzZrfEvBEekRvlP5+1Ak8pReaHcz4gPKFA9X6MG0WQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/crypto/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.3", - "main-event": "^1.0.1", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/logger": { - "version": "6.2.10", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.10.tgz", - "integrity": "sha512-recRqNABHG32YXvpMvNepFCuU14d51dF5hs+vR2C/tkqXBQc0Sqg5LtWB2NYPoIJV+JUB50iWLrvBXZUOjLc9Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-datastore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", - "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2", - "interface-store": "^8.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-store": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", - "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2" - } - }, - "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.12.tgz", - "integrity": "sha512-U44MeKs+U1SqZPqS0RDzjjcS9ERix2zJsJkTcqi8I+5flEz4uVAObBDxvkkaTaqRFRXCXEPjL5kA4UbgVubCZQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.1.21", - "@libp2p/interface": "^3.2.5", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -709,12 +538,12 @@ } }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -724,9 +553,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1137,16 +966,16 @@ } }, "node_modules/@unicitylabs/sphere-sdk": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.13.1.tgz", - "integrity": "sha512-i/cnbr9Puk30b7z/JANTYN8hhp5OmVCpmo/MA2dS0SwV9wtBasQYI3sLosRXaVMCEX2n269K1y3M+BccU1KBxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.14.1.tgz", + "integrity": "sha512-8Jxro6A9bAvNYhFTAweYq08paIEVkDZayOl0MNBHmbED0ef2Si2oe+JK92VYL2DBlqfUeK2kWlJW+lAckQIZtw==", "license": "MIT", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@unicitylabs/nostr-js-sdk": "^0.6.0", - "@unicitylabs/state-transition-sdk": "2.0.1", + "@unicitylabs/state-transition-sdk": "2.0.2", "bip39": "^3.1.0", "buffer": "^6.0.3", "canonicalize": "^3.0.0", @@ -1155,41 +984,19 @@ "engines": { "node": ">=22.0.0" }, - "optionalDependencies": { - "@libp2p/crypto": "^5.1.13", - "@libp2p/peer-id": "^6.0.4", - "ipns": "^10.0.0", - "multiformats": "^13.4.2" - }, "peerDependencies": { - "@libp2p/crypto": ">=5.0.0", - "@libp2p/peer-id": ">=6.0.0", - "ipns": ">=10.0.0", - "multiformats": ">=13.0.0", "ws": ">=8.0.0" }, "peerDependenciesMeta": { - "@libp2p/crypto": { - "optional": true - }, - "@libp2p/peer-id": { - "optional": true - }, - "ipns": { - "optional": true - }, - "multiformats": { - "optional": true - }, "ws": { "optional": true } } }, "node_modules/@unicitylabs/state-transition-sdk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.1.tgz", - "integrity": "sha512-O0pjxaL32VgHYjSM3Ei64UWBG2qwBzdQ9aIdlyhzTnPdI+kKu3h9saSMF1uV9XTfqKHsNGUpE/2dFUulNfmdmg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.2.tgz", + "integrity": "sha512-SYCCShlwSUqeRhOocGkg9QCx0lacxelpRa6w3PQTLdXO8BO6Xo6Qh34ABZW2UcRfMDDPaXA0KSIyUo+UbiM9FQ==", "license": "ISC", "dependencies": { "@noble/curves": "2.2.0", @@ -1200,6 +1007,33 @@ "node": ">=22" } }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -1313,13 +1147,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1407,16 +1234,6 @@ "node": ">=18" } }, - "node_modules/cborg": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.7.tgz", - "integrity": "sha512-rGg2MG9zZEUKtKqjkBppIWUecTXf9N1vs1Qru43vJWoDaODhCrtmzdehCaA/aq/c1cPI5A0kPrJ5Tf+jIfhV4w==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1521,13 +1338,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT", - "optional": true - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -1571,13 +1381,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT", - "optional": true - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1598,96 +1401,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/interface-datastore": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-9.0.3.tgz", - "integrity": "sha512-NLZa7Mp+0qn48nSwIY/C36da4uVIKzwG2tuEIpaSJArsuB2RrdyDWwkoDUyjsJ+VrMntXz38VSk9vXTx/ZUpAw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "interface-store": "^7.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-datastore/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/interface-store": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-7.0.2.tgz", - "integrity": "sha512-KYOPcDH+1peaPhSeoZujR5nwkVeola1EdrnrlHTIM0HRNUs9B0aTsUQMH5kTmIjaQq1BOowoUyoCamgL8IMyww==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/ipns": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-10.1.6.tgz", - "integrity": "sha512-mTACIdTBBXY5kbCo3t/M3ycNWbjajLrSXhTvjD0MEGyOUaI3uMv94JbJSHGaJ2xxRlpOrRk1ifToOsyPZiglnA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/logger": "^6.0.4", - "cborg": "^5.1.0", - "interface-datastore": "^9.0.2", - "multiformats": "^13.2.2", - "protons-runtime": "^6.0.1", - "timestamp-nano": "^1.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/protons-runtime": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", - "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/uint8-varint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", - "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/ipns/node_modules/uint8arraylist": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", - "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/ipns/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/libphonenumber-js": { "version": "1.13.9", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.9.tgz", @@ -1965,30 +1678,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/main-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.4.tgz", - "integrity": "sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/ms": { - "version": "4.0.0-nightly.202508271359", - "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", - "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -2022,36 +1711,6 @@ "node": ">=12.20.0" } }, - "node_modules/p-queue": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.1.tgz", - "integrity": "sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventemitter3": "^5.0.4", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2108,25 +1767,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/progress-events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", - "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, "node_modules/rolldown": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", @@ -2192,29 +1832,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2300,44 +1917,6 @@ "node": ">=14.17" } }, - "node_modules/uint8-varint": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", - "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^3.0.1", - "uint8arrays": "^6.1.0" - } - }, - "node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^14.0.0" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.4.tgz", - "integrity": "sha512-+SXItmOUWzT0kjlIfkZ4NawJaC9jW/mPlyn902V9Qgpc7YDwdEiwqbiZxTyvC0XWh1jZguXca3A/WQ+UOPRLUA==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -2345,13 +1924,6 @@ "dev": true, "license": "MIT" }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT", - "optional": true - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -2533,17 +2105,6 @@ } } }, - "node_modules/weald": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", - "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "ms": "^4.0.0-nightly.202508271359", - "supports-color": "^10.0.0" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/bot/package.json b/bot/package.json index b3f1596..40dc82b 100644 --- a/bot/package.json +++ b/bot/package.json @@ -8,10 +8,11 @@ }, "scripts": { "start": "tsx src/index.ts", + "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "dotenv": "^17.4.2", "ws": "^8.21.1" }, diff --git a/bot/src/coins.test.ts b/bot/src/coins.test.ts index 47692b6..b620b63 100644 --- a/bot/src/coins.test.ts +++ b/bot/src/coins.test.ts @@ -1,6 +1,36 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { TokenRegistry } from '@unicitylabs/sphere-sdk'; -import { resolveCoin, toBaseUnits } from './coins'; +import { fromBaseUnits, resolveCoin, toBaseUnits } from './coins'; + +describe('fromBaseUnits', () => { + it('renders a fractional amount and trims trailing zeros', () => { + expect(fromBaseUnits('1500000', 6)).toBe('1.5'); + expect(fromBaseUnits('1000000', 6)).toBe('1'); + }); + + it('renders an amount smaller than one whole unit', () => { + expect(fromBaseUnits('1', 18)).toBe('0.000000000000000001'); + expect(fromBaseUnits('0', 6)).toBe('0'); + }); + + it('is exact past Number.MAX_SAFE_INTEGER', () => { + expect(fromBaseUnits('105000000000000000000', 18)).toBe('105'); + }); + + it('passes through when the coin has no decimals', () => { + expect(fromBaseUnits('100', 0)).toBe('100'); + }); + + it('round-trips with toBaseUnits', () => { + for (const [human, decimals] of [['1.5', 6], ['0.000000000000000001', 18], ['100', 0]] as const) { + expect(fromBaseUnits(toBaseUnits(human, decimals), decimals)).toBe(human); + } + }); + + it('rejects a non-integer input', () => { + expect(() => fromBaseUnits('1.5', 6)).toThrow(); + }); +}); describe('toBaseUnits', () => { it('converts a fractional human amount to base units', () => { diff --git a/bot/src/coins.ts b/bot/src/coins.ts index f6a82ed..bba44af 100644 --- a/bot/src/coins.ts +++ b/bot/src/coins.ts @@ -2,18 +2,21 @@ * Pure coin/amount helpers for the own-wallet bot example. * * - `toBaseUnits` converts a human-readable decimal amount (e.g. "1.5") into - * the integer base-unit string the SDK's `payments.send` / `mintFungibleToken` - * expect. It is string-based and exact — never routes through `Number`/ - * `parseFloat`, which lose precision for high-decimal (e.g. 18-decimal) tokens. + * the integer base-unit string `payments.send` expects (`payments.mint` wants + * the same value as a `bigint` — wrap it in `BigInt(...)`). It is string-based + * and exact — never routes through `Number`/`parseFloat`, which lose precision + * for high-decimal (e.g. 18-decimal) tokens. + * - `fromBaseUnits` is the inverse: it renders a base-unit integer string (what + * `Asset.totalAmount` and `HistoryEntry.amount` carry) back as a human decimal. * - `resolveCoin` accepts either a 64-hex coinId or a symbol (e.g. "UCT") and * resolves it to `{ coinId, decimals }` via the SDK's `TokenRegistry` singleton. * - * TokenRegistry methods used here are verified against - * `sphere-sdk/registry/TokenRegistry.ts` (0.11.14 source): - * - `TokenRegistry.getInstance()` — TokenRegistry.ts:128 - * - `getDefinition(coinId): TokenDefinition | undefined` — TokenRegistry.ts:460 - * - `getDefinitionBySymbol(symbol): TokenDefinition | undefined` — TokenRegistry.ts:470 - * - `getCoinIdBySymbol(symbol): string | undefined` — TokenRegistry.ts:581 + * TokenRegistry methods used here, verified against `@unicitylabs/sphere-sdk` + * **0.14.1** (the version this package pins): + * - `TokenRegistry.getInstance()` + * - `getDefinition(coinId): TokenDefinition | undefined` + * - `getDefinitionBySymbol(symbol): TokenDefinition | undefined` + * - `getCoinIdBySymbol(symbol): string | undefined` */ import { TokenRegistry } from '@unicitylabs/sphere-sdk'; @@ -51,6 +54,33 @@ export function toBaseUnits(human: string, decimals: number): string { return normalized; } +/** + * Render an integer base-unit string as a human decimal — the inverse of + * {@link toBaseUnits}. Exact string arithmetic, no `Number` anywhere: a + * balance of 10^20 base units on an 18-decimal coin exceeds `Number.MAX_SAFE_INTEGER`. + * + * Trailing fractional zeros are trimmed, so 1500000/6 renders as "1.5" and + * 1000000/6 as "1". + * + * @param base - Non-negative integer string, e.g. "1500000". + * @param decimals - Number of base-unit decimal places for the coin. + * @throws if `base` isn't a non-negative integer string. + */ +export function fromBaseUnits(base: string, decimals: number): string { + if (!Number.isInteger(decimals) || decimals < 0) { + throw new Error(`Invalid decimals: ${decimals}`); + } + if (!/^\d+$/.test(base)) { + throw new Error(`Invalid base-unit amount "${base}": expected a non-negative integer string`); + } + if (decimals === 0) return base.replace(/^0+(?=\d)/, ''); + + const padded = base.padStart(decimals + 1, '0'); + const intPart = padded.slice(0, padded.length - decimals).replace(/^0+(?=\d)/, ''); + const fracPart = padded.slice(padded.length - decimals).replace(/0+$/, ''); + return fracPart ? `${intPart}.${fracPart}` : intPart; +} + /** Resolved coin identity: canonical hex coinId + its decimal places. */ export interface ResolvedCoin { coinId: string; diff --git a/bot/src/index.ts b/bot/src/index.ts index b2d4959..94ba0d7 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -1,68 +1,75 @@ /** * Bot runtime behavior — the bot's OWN Sphere wallet (booted via - * `createBotSphere()`, Task 2) wired up to: DM auto-reply, a one-time - * self-mint float, optional incoming-transfer logging, and a small stdin - * command loop for a money-safe demo `send`. + * `createBotSphere()`) wired up to: DM auto-reply, a one-time self-mint + * float, incoming-transfer logging, and a small stdin command loop for a + * money-safe demo `send`. * - * SDK symbols/field names below are verified against `sphere-sdk` 0.11.14 - * source (this repo pins that exact version): + * SDK surface used here, verified against `@unicitylabs/sphere-sdk` **0.14.1**: * - * - `sphere.communications` getter — `core/Sphere.ts:1388`. - * - `communications.onDirectMessage(handler: (m: DirectMessage) => void): () => void` - * — `modules/communications/CommunicationsModule.ts:450`. - * - `communications.sendDM(recipient: string, content: string): Promise` - * — `modules/communications/CommunicationsModule.ts:244`. - * - `DirectMessage` — `{ id, senderPubkey, senderNametag?, recipientPubkey, - * recipientNametag?, content, timestamp, isRead }` — `types/index.ts:337-346`, - * re-exported from the SDK root via `index.ts:134` (`export * from './types'`). - * - `sphere.payments` getter — `core/Sphere.ts:1382`. - * - `payments.mintFungibleToken(coinIdHex: string, amount: bigint): Promise< - * { success: true; token: Token; tokenId: string } | { success: false; error: string }>` - * — `modules/payments/PaymentsModule.ts:4655-4667`. NOTE: `amount` is a - * `bigint`, not a base-unit string — `toBaseUnits()` (Task 1) returns a - * string, so it must be wrapped in `BigInt(...)`. This does NOT throw on - * an ordinary mint failure (e.g. no token engine) — it resolves to - * `{ success: false, error }` — confirmed against the real call site in - * `agentic-chatbot/packages/chess-bot/src/wallet.ts:137-146`, which checks - * `result.success` AND wraps the call in try/catch (it can still throw for - * genuinely exceptional errors). Mirrored here. - * - `payments.getBalance(coinId?: string): Asset[]` — synchronous — - * `modules/payments/PaymentsModule.ts:3525-3527`. - * - `payments.send(request: TransferRequest): Promise` — - * `modules/payments/PaymentsModule.ts:1759-1762`. `TransferRequest` — - * `{ coinId, amount, recipient, memo? }` — `types/index.ts:143-156`. - * `TransferResult.deliveryPending?: boolean` — `types/index.ts:188` — true - * means "sent, delivery pending" (source is terminally spent, recipient - * delivery will land later) — a SUCCESS outcome, never resend. - * - `sphere.on(type: T, handler): () => void` — `core/Sphere.ts:3390`. - * `'transfer:incoming'` payload is `IncomingTransfer` — `{ id, senderPubkey, - * senderNametag?, tokens, memo?, receivedAt }` — `types/index.ts:193-200`, - * confirmed emitted by `PaymentsModule.ts:5239`. - * - Money-safety guard: `isPossiblyCommittedSendOutcome(err: unknown): boolean` - * — `core/errors.ts:240-242` — true for `SphereError`s whose `code` is in - * the possibly-committed set, which ALREADY includes `'SEND_PARTIALLY_COMPLETED'` - * (`core/errors.ts:223-230`). Exported from the SDK root — `index.ts:51` - * (`export { ..., isPossiblyCommittedSendOutcome } from './core'`). The - * brief additionally asks for an explicit `err?.code === 'SEND_PARTIALLY_COMPLETED'` - * check — redundant for a real `PartialSendConflictError` (already covered - * above) but kept as a defensive belt-and-suspenders check for any - * non-`SphereError`-shaped rejection that happens to carry that code. - * - `identity.chainPubkey` — `Identity` — `types/index.ts:30-37` — used to - * skip the bot's own outgoing DMs echoing back to itself. + * - `sphere.payments` is the payments-v2 facade. The members used below: + * `assets(coinId?): Promise` — the balance view (grouped by coin) + * `mint(coinIdHex, amount: bigint): Promise` — self-mint + * `send(req): Promise` — { recipient, amount, coinId, memo? } + * `mint` takes a **bigint**, not a base-unit string, so `toBaseUnits()`'s + * string result must be wrapped in `BigInt(...)`. It resolves to + * `{ success: false, error }` for ordinary failures rather than throwing, + * but can still throw for exceptional ones — hence both checks below. + * - `TransferResult.deliveryPending === true` means "sent, delivery pending": + * the source is terminally spent and the recipient's copy will land later. + * That is a SUCCESS. Never resend it. + * - `mayBeCommitted(err)` (`./sendSafety`) is true for the typed error codes + * whose spend may already be on-chain (`CERTIFICATION_UNCONFIRMED`, + * `SEND_SYNC_PENDING`, `SEND_PARTIALLY_COMPLETED`, the checkpoint codes …). + * It wraps the SDK's `isPossiblyCommittedSendOutcome` with a duck-typed + * fallback — see that file for why. The correct recovery is + * `payments.resumeNow()`, which replays the SAME intent — never a fresh + * `send()`, which would spend a different source and pay the recipient twice. + * - `sphere.on('transfer:incoming', …)` — unchanged across the flip. + * `sphere.on('transfer:updated', …)` is the current lifecycle event; it + * replaces the old `transfer:confirmed` / `transfer:failed` pair. + * - `sphere.communications.onDirectMessage(handler)` / `.sendDM(to, content)` + * — DMs still ride Nostr; only money moved to wallet-api. */ import readline from 'readline'; -import { isPossiblyCommittedSendOutcome, type DirectMessage, type IncomingTransfer } from '@unicitylabs/sphere-sdk'; +import { + type Asset, + type DirectMessage, + type IncomingTransfer, + type TransferResult, +} from '@unicitylabs/sphere-sdk'; import { createBotSphere } from './sphere'; -import { resolveCoin, toBaseUnits } from './coins'; +import { mayBeCommitted } from './sendSafety'; +import { fromBaseUnits, resolveCoin, toBaseUnits } from './coins'; const MINT_SYMBOL = 'UCT'; const MINT_AMOUNT_HUMAN = '100'; +/** + * `Asset.totalAmount` is in BASE units — `fromBaseUnits` puts the decimal point back. + * + * Falls back to the raw base-unit string if the coin's `decimals` is missing or + * nonsensical. Printing a balance must never be able to kill the bot. + */ +function formatAssets(assets: Asset[]): string { + if (assets.length === 0) return '(empty)'; + return assets + .map((a) => { + let amount: string; + try { + amount = fromBaseUnits(a.totalAmount, a.decimals); + } catch { + amount = `${a.totalAmount} (base units)`; + } + return `${amount} ${a.symbol} (${a.tokenCount} token(s))`; + }) + .join('\n '); +} + async function main() { - const { sphere, identity, receivesPayments } = await createBotSphere(); + const { sphere, identity } = await createBotSphere(); console.log('Bot identity:', identity); - // --- 1. DMs (always): echo back anything sent to the bot, skip self-DMs --- + // --- 1. DMs: echo back anything sent to the bot, skip self-DMs --- sphere.communications.onDirectMessage(async (m: DirectMessage) => { console.log(`[dm] from ${m.senderNametag ?? m.senderPubkey}: ${m.content}`); if (m.senderPubkey === identity.chainPubkey) { @@ -79,7 +86,7 @@ async function main() { try { const { coinId, decimals } = resolveCoin(MINT_SYMBOL); const amount = BigInt(toBaseUnits(MINT_AMOUNT_HUMAN, decimals)); - const result = await sphere.payments.mintFungibleToken(coinId, amount); + const result = await sphere.payments.mint(coinId, amount); if (result.success) { console.log(`[mint] self-minted ${MINT_AMOUNT_HUMAN} ${MINT_SYMBOL} (tokenId ${result.tokenId})`); } else { @@ -88,21 +95,49 @@ async function main() { } catch (err) { console.error('[mint] self-mint threw:', err instanceof Error ? err.message : err); } - console.log('[balance]', JSON.stringify(sphere.payments.getBalance(), null, 2)); - - // --- 3. Receive: only wired if the wallet-api mailbox rail was composed in --- - if (receivesPayments) { - sphere.on('transfer:incoming', (t: IncomingTransfer) => { - console.log(`[receive] incoming transfer ${t.id} from ${t.senderNametag ?? t.senderPubkey}`); - sphere.communications.sendDM(t.senderPubkey, 'thanks for the tokens!').catch((err) => { - console.error('[receive] failed to send thank-you DM:', err instanceof Error ? err.message : err); - }); + // `assets()` is a view over the wallet-api inventory, and the server credits a + // fresh mint asynchronously — so this first read can legitimately come back empty + // even though the mint above certified on-chain. `inventory:updated` is the signal + // that the server's view caught up; that is what a UI refreshes on. + console.log('[balance]\n ' + formatAssets(await sphere.payments.assets())); + + // Two inventory updates in flight means two overlapping assets() reads, which can + // resolve out of order — printing a stale balance AFTER a fresher one. The epoch + // guard drops any result that a later read already superseded. A UI refreshing on + // this event needs the same rule. + let balanceEpoch = 0; + sphere.on('inventory:updated', () => { + const epoch = ++balanceEpoch; + void sphere.payments + .assets() + .then((assets) => { + if (epoch !== balanceEpoch) return; // superseded by a newer read + console.log('[balance]\n ' + formatAssets(assets)); + }) + .catch((err) => console.error('[balance] read failed:', err instanceof Error ? err.message : err)); + }); + + // --- 3. Incoming transfers (tokens are delivered to the wallet-api mailbox) --- + sphere.on('transfer:incoming', (t: IncomingTransfer) => { + console.log(`[receive] incoming transfer ${t.id} from ${t.senderNametag ?? t.senderPubkey}`); + sphere.communications.sendDM(t.senderPubkey, 'thanks for the tokens!').catch((err) => { + console.error('[receive] failed to send thank-you DM:', err instanceof Error ? err.message : err); }); - } else { - console.log('WALLET_API_URL not set — this bot will not receive tokens from the hosted wallet (see README).'); - } + }); + + // The current lifecycle event for anything this wallet sends, receives or + // mints. It replaced transfer:confirmed / transfer:failed in 0.14. + sphere.on('transfer:updated', (t: TransferResult) => { + console.log(`[transfer] ${t.id} -> ${t.status}${t.deliveryPending ? ' (delivery pending)' : ''}`); + }); + + // The wallet-api session's connectivity. 'degraded'/'offline' means reads + // may be stale and sends will queue — it is not a failure. + sphere.on('connection:status', ({ status }) => { + console.log(`[connection] ${status}`); + }); - // --- 4. Demo command loop: send / balance / help / exit --- + // --- 4. Demo command loop: send / balance / pending / resume / help / exit --- const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function showPrompt() { @@ -135,8 +170,11 @@ async function main() { console.log('Send result:', JSON.stringify(result, null, 2)); } } catch (err) { - if (isPossiblyCommittedSendOutcome(err) || (err as { code?: string } | undefined)?.code === 'SEND_PARTIALLY_COMPLETED') { - console.log('spend may already be committed — do NOT retry'); + if (mayBeCommitted(err)) { + console.log( + 'spend may already be committed — do NOT re-send. ' + + 'Run "resume" to replay the same intent.', + ); } else { console.error('Send failed:', err instanceof Error ? err.message : err); } @@ -144,7 +182,19 @@ async function main() { break; } case 'balance': { - console.log('Balance:', JSON.stringify(sphere.payments.getBalance(), null, 2)); + console.log('Balance:\n ' + formatAssets(await sphere.payments.assets())); + break; + } + case 'pending': { + const pending = await sphere.payments.pendingTransfers(); + console.log(pending.length === 0 ? 'Nothing pending.' : JSON.stringify(pending, null, 2)); + break; + } + case 'resume': { + // The ONLY safe retry for a possibly-committed send: it replays the + // SAME intent (same transferId) instead of issuing a new spend. + await sphere.payments.resumeNow(); + console.log('Resume pass complete.'); break; } case 'exit': @@ -160,6 +210,8 @@ async function main() { Commands: send @to - Send L3 tokens (money-safe: no auto-resend on a possibly-committed outcome) balance - Show current balance + pending - Show transfers still converging + resume - Replay open intents (the safe retry — never re-sends) help - Show this help exit - Shut down and exit `); diff --git a/bot/src/provisionAggregatorKey.ts b/bot/src/provisionAggregatorKey.ts index 9ee6b48..5b29477 100644 --- a/bot/src/provisionAggregatorKey.ts +++ b/bot/src/provisionAggregatorKey.ts @@ -6,18 +6,15 @@ * the static `AGGREGATOR_API_KEY` testnet2 key (and its rate limit) with * every other example wallet. * - * SDK symbols verified against `sphere-sdk` 0.11.14 source (this repo pins - * that exact version): - * - `getPublicKey(privateKey, compressed?)` — `core/crypto.ts:258`, exported - * at the SDK root via `index.ts:99`. - * - `signMessage(privateKeyHex, message)` — `core/crypto.ts:502`, exported - * at the SDK root via `index.ts:52`. - * - `NETWORKS` — `constants.ts:394`, exported at the SDK root via - * `index.ts:342`. `NETWORKS.testnet2.aggregatorUrl` = SGW base URL - * (`constants.ts:417-421`, `'https://gateway.testnet2.unicity.network'`). - * - `Sphere` — exported at the SDK root via `index.ts:51`. - * - `sphere.deriveAddress(0)` — `core/Sphere.ts:2972`, returns `AddressInfo` - * (`{ privateKey, publicKey, path, index }`, `core/crypto.ts:64`). + * SDK symbols verified against `@unicitylabs/sphere-sdk` **0.14.1** (the version + * this package pins), all exported from the SDK root: + * - `getPublicKey(privateKey, compressed?)` and `signMessage(privateKeyHex, message)` + * — pure local secp256k1 crypto; no aggregator key needed to run them, which is + * what makes the bootstrap (sign the challenge that provisions the key) possible. + * - `NETWORKS` — `NETWORKS.testnet2.aggregatorUrl` is the SGW base URL + * (`https://gateway.testnet2.unicity.network`). + * - `sphere.deriveAddress(0)` returns `AddressInfo` + * (`{ privateKey, publicKey, path, index }`). */ import type { Sphere } from '@unicitylabs/sphere-sdk'; import { getPublicKey, signMessage, NETWORKS } from '@unicitylabs/sphere-sdk'; diff --git a/bot/src/sendSafety.test.ts b/bot/src/sendSafety.test.ts new file mode 100644 index 0000000..c5276ca --- /dev/null +++ b/bot/src/sendSafety.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest'; +import { SphereError, isPossiblyCommittedSendOutcome } from '@unicitylabs/sphere-sdk'; +import { POSSIBLY_COMMITTED_CODES, mayBeCommitted } from './sendSafety'; + +describe('mayBeCommitted', () => { + it('agrees with the SDK predicate on a real SphereError', () => { + for (const code of POSSIBLY_COMMITTED_CODES) { + const err = new SphereError('boom', code as never); + expect(isPossiblyCommittedSendOutcome(err)).toBe(true); + expect(mayBeCommitted(err)).toBe(true); + } + }); + + /** + * The reason this helper exists. A duplicated SDK copy makes `instanceof + * SphereError` false, so the SDK predicate returns false for a genuinely + * possibly-committed outcome. Without the duck-typed fallback the bot would + * print "Send failed" and a caller would retry — a double pay. + */ + it('still catches a possibly-committed outcome when instanceof fails', () => { + const foreign = Object.assign(new Error('cross-bundle copy'), { + code: 'SEND_PARTIALLY_COMPLETED', + }); + expect(isPossiblyCommittedSendOutcome(foreign)).toBe(false); + expect(mayBeCommitted(foreign)).toBe(true); + }); + + it('catches every code across the bundle boundary, not just the partial one', () => { + for (const code of POSSIBLY_COMMITTED_CODES) { + expect(mayBeCommitted(Object.assign(new Error('x'), { code }))).toBe(true); + } + }); + + it('leaves a clean failure retryable', () => { + expect(mayBeCommitted(new SphereError('nope', 'SEND_INSUFFICIENT_BALANCE'))).toBe(false); + expect(mayBeCommitted(Object.assign(new Error('x'), { code: 'INVALID_RECIPIENT' }))).toBe(false); + expect(mayBeCommitted(new Error('network down'))).toBe(false); + }); + + it('does not throw on null/undefined/non-objects', () => { + expect(mayBeCommitted(null)).toBe(false); + expect(mayBeCommitted(undefined)).toBe(false); + expect(mayBeCommitted('SEND_PARTIALLY_COMPLETED')).toBe(false); + expect(mayBeCommitted({ code: 123 })).toBe(false); + }); + + /** + * Pins our mirrored set against the SDK's own. If a future SDK adds a + * possibly-committed code, this fails instead of silently letting the new + * code fall through to the retryable branch. + */ + it('mirrors the SDK set exactly', () => { + const notCommitted = ['SEND_INSUFFICIENT_BALANCE', 'INVALID_RECIPIENT', 'TRANSFER_CONFLICT', 'TIMEOUT']; + for (const code of notCommitted) { + expect(isPossiblyCommittedSendOutcome(new SphereError('x', code as never))).toBe(false); + expect(POSSIBLY_COMMITTED_CODES.has(code)).toBe(false); + } + for (const code of POSSIBLY_COMMITTED_CODES) { + expect(isPossiblyCommittedSendOutcome(new SphereError('x', code as never))).toBe(true); + } + }); +}); diff --git a/bot/src/sendSafety.ts b/bot/src/sendSafety.ts new file mode 100644 index 0000000..6080080 --- /dev/null +++ b/bot/src/sendSafety.ts @@ -0,0 +1,49 @@ +/** + * The single money-safety decision a bot has to get right. + * + * When `payments.send()` rejects, there are two very different worlds: + * + * 1. **Nothing was committed** — insufficient balance, unknown recipient, a + * pre-submit validation error. Retrying is fine. + * 2. **The spend may already be on-chain** — the submit was accepted (or + * thrown) and the proof fetch was inconclusive. Retrying is a DOUBLE PAY: + * a fresh `send()` gets a new `transferId` over different source tokens, + * so the recipient can be paid twice for one intent. + * + * The only safe recovery for (2) is `payments.resumeNow()`, which replays the + * SAME intent rather than issuing a second spend. + * + * `isPossiblyCommittedSendOutcome` is the SDK's own predicate and is + * authoritative — but it gates on `instanceof SphereError`, which silently + * returns `false` when two copies of the SDK end up in one dependency tree + * (the same hazard this repo's `browser/src/lib/connectErrors.ts` calls out for + * `ConnectError`). A false negative there routes a possibly-committed send into + * the "failed, safe to retry" branch, which is the one mistake that costs money. + * + * So we duck-type the same code set as a fallback and deliberately err toward + * "may be committed": labelling a committed send as failed invites the retry; + * labelling a failed send as possibly-committed only costs a `resume` call. + */ +import { isPossiblyCommittedSendOutcome } from '@unicitylabs/sphere-sdk'; + +/** + * The SDK's `POSSIBLY_COMMITTED_SEND_CODES`, mirrored for the duck-typed path. + * Verified against `@unicitylabs/sphere-sdk` 0.14.1; `sendSafety.test.ts` pins + * it so an SDK bump that adds a code fails loudly here instead of silently + * downgrading a new possibly-committed outcome to "retryable". + */ +export const POSSIBLY_COMMITTED_CODES: ReadonlySet = new Set([ + 'SEND_SYNC_PENDING', + 'CERTIFICATION_UNCONFIRMED', + 'CHECKPOINT_PERSIST_FAILED', + 'SPLIT_CHECKPOINT_LOST', + 'CHECKPOINT_TRUSTBASE_MISMATCH', + 'SEND_PARTIALLY_COMPLETED', +]); + +/** True when the spend behind a rejected `send()` may already be on-chain. Never re-send; `resumeNow()`. */ +export function mayBeCommitted(err: unknown): boolean { + if (isPossiblyCommittedSendOutcome(err)) return true; + const code = (err as { code?: unknown } | null | undefined)?.code; + return typeof code === 'string' && POSSIBLY_COMMITTED_CODES.has(code); +} diff --git a/bot/src/smoke.ts b/bot/src/smoke.ts index 7e97f8c..f6628b6 100644 --- a/bot/src/smoke.ts +++ b/bot/src/smoke.ts @@ -3,18 +3,17 @@ * * Boots the bot's own Sphere wallet against the real testnet2 network and * prints the resulting identity, then exits. Used to verify `createBotSphere()` - * (Task 2 of the own-wallet bot plan) against the live aggregator/Nostr — - * there is no unit test for `sphere.ts` since it's an integration boot, not - * pure logic (see `.superpowers/sdd/task-b2-brief.md`). + * against the live aggregator / wallet-api / Nostr — there is no unit test for + * `sphere.ts` since it's an integration boot, not pure logic. * - * Run: `npx tsx src/smoke.ts` + * Run: `npx tsx src/smoke.ts` (needs WALLET_API_URL in .env). */ import { createBotSphere } from './sphere'; async function main() { - const { sphere, identity, receivesPayments } = await createBotSphere(); + const { sphere, identity } = await createBotSphere(); console.log('Bot identity:', identity); - console.log('receivesPayments (wallet-api mailbox rail composed):', receivesPayments); + console.log('Assets:', await sphere.payments.assets()); await sphere.destroy(); process.exit(0); } diff --git a/bot/src/sphere.ts b/bot/src/sphere.ts index 5ae56ec..d252582 100644 --- a/bot/src/sphere.ts +++ b/bot/src/sphere.ts @@ -1,67 +1,48 @@ /** * Bot wallet init — the bot's OWN Sphere wallet (own keys, direct SDK usage, - * not a Connect dApp), with an optional wallet-api receive rail. + * not a Connect dApp). * - * SDK symbols/field names below are verified against - * `sphere-sdk` 0.11.14 source (this repo pins that exact version): + * Verified against `@unicitylabs/sphere-sdk` **0.14.1** (the version this + * package pins). The composition below is the ONLY supported one since the + * payments-v2 flip: * - * - `createNodeProviders(config)` — `impl/nodejs/index.ts:171`. `config.network` - * is REQUIRED (throws `INVALID_CONFIG` — `impl/nodejs/index.ts:192-195`). - * Storage dirs are `dataDir` / `tokensDir` (NodeProvidersConfig — - * `impl/nodejs/index.ts:99,104`), oracle key is `oracle.apiKey` - * (`impl/nodejs/index.ts:108`, consumed at :250-258). - * - `createOwnStorageWalletApiProviders(base, config)` — - * `impl/shared/wallet-api/composition.ts:178`. This is the "Inventory - * Custody (Bring Your Own Storage)" preset (`docs/INTEGRATION.md:227-248`): - * it keeps `base`'s own `tokenStorage` (the file-based storage `createNodeProviders` - * already built) and adds ONLY `delivery` (wallet-api mailbox, custody - * `'external'` — zero server inventory writes on claim, composition.ts:183) - * + `walletApi` (the auth-session client). Deliberately NOT - * `createWalletApiProviders` (composition.ts:153): that "External Custody" - * preset REPLACES `tokenStorage` with a thin provider backed by the - * wallet-api server's own encrypted archive (docs/INTEGRATION.md:205-217) — - * the opposite of this bot's "own keys, own storage" identity described in - * `bot/README.md` and `bot/.env.example`. `WalletApiCompositionConfig` - * (composition.ts:84-110) takes `baseUrl` + `network` (plain `string`, e.g. - * `'testnet2'`) + optional `deviceId`; it has no `tokenStorage` field — - * own-storage custody works by simply not swapping `base.tokenStorage`. - * - `network` fields on both factories above are a plain `NetworkType` / - * `string` (`'testnet2'`), NOT the `SPHERE_NETWORKS.testnet2` object - * (`{ id: 4, name: 'testnet2' }`, `constants.ts:460-462`). That object is - * the Connect-protocol network descriptor (`ConnectClient`/`autoConnect`, - * `docs/CONNECT.md:41-57`) — passing it here would hand an object where a - * string key is expected. Testnet2's networkId (4) is reachable purely via - * the string network key; `SPHERE_NETWORKS` is intentionally unused here. - * - `Sphere.init(options): Promise` — `core/Sphere.ts:711`. - * `SphereInitOptions` (`core/Sphere.ts:362-434`) takes the provider bundle - * spread directly (`storage`, `transport`, `oracle`, `delivery?`, `walletApi?`, - * `tokenStorage?`, ...) plus `mnemonic?: string` (:384), `autoGenerate?: boolean` - * (:386) and `network?: NetworkType` (:398). Mirrors the canonical - * `docs/QUICKSTART-NODEJS.md:149-192` shape: `Sphere.init({ ...providers, ... })`. - * `SphereInitResult` (`core/Sphere.ts:436-444`) exposes `sphere`, `created` - * and `generatedMnemonic?` (only set when `autoGenerate` produced a new one). - * - `sphere.identity` getter (`core/Sphere.ts:1418-1424`) returns `Identity | null` - * (`types/index.ts:30-37`, re-exported from the SDK root via `index.ts:134` - * `export * from './types'`): `{ chainPubkey, directAddress?, ipnsName?, nametag? }`. - * - `sphere.setOracleApiKey(apiKey): Promise` — `core/Sphere.ts:1584`, public. - * Re-keys the LIVE oracle + rebuilds the token engine without a full - * Sphere rebuild (transport/socket/discovery stay up) — used below to apply - * a saved/provisioned key discovered AFTER init. - * - Bootstrap-init finding (verified against `oracle/UnicityAggregatorProvider.ts`): - * `UnicityAggregatorProviderConfig.apiKey` is `apiKey?: string` (:37) and - * defaults to `''` at construction (:84, `config.apiKey ?? ''`) — the - * provider does NOT validate/require a non-empty key at construction time, - * it only affects request headers later. `createNodeProviders` / - * `Sphere.init` never throw on an empty oracle key. So booting with - * `oracle: { apiKey: '' }` when there's no env key yet is safe: init - * succeeds, `sphere.deriveAddress(0)` + `signMessage` (pure local crypto, - * no oracle involved) can sign the SGW provisioning challenge, and - * `setOracleApiKey` re-keys the live oracle once a key is resolved. + * createNodeProviders(...) // storage + Nostr transport + oracle + * -> createWalletApiProviders(base, { baseUrl, network, deviceId? }) + * -> Sphere.init({ ...providers }) + * + * Two facts drive that shape: + * + * - **Money moves only through wallet-api custody.** Own-storage custody + * (`tokenStorage` / `tokensDir`, `FileTokenStorageProvider`, + * `IndexedDBTokenStorageProvider`) was DELETED in 0.14.0. `Sphere.init` + * throws `INVALID_CONFIG` when no `walletApi` config is present, so + * `WALLET_API_URL` is REQUIRED here — it is not an optional receive rail + * any more. See `sphere-sdk/docs/MIGRATION-PAYMENTS-V2.md` §3. + * - **The payments composition is single-network.** `walletApi.network` must + * equal the network the providers/engine run on, or init throws + * `INVALID_CONFIG`. Both come from the one `TESTNET2` constant below. + * + * `network` on both factories is the plain network key (`'testnet2'`), NOT the + * `SPHERE_NETWORKS.testnet2` object — that object is the Connect-protocol + * network descriptor a dApp hands to `ConnectClient`, which this bot is not. + * + * Local `StorageProvider` state (mnemonic, keys, identity, nametag cache) is + * unchanged and still lives on disk under `dataDir`. Only *token custody* + * moved to the server. + * + * Bootstrap-init note: `createNodeProviders` accepts an empty `oracle.apiKey` + * and never validates it at construction — it only affects request headers + * later. That matters because the wallet must exist before it can sign the SGW + * challenge that provisions a key. So the bot boots with whatever key it has + * (possibly `''`), resolves the real one, then calls + * `sphere.setOracleApiKey()`, which re-keys the live oracle and rebuilds the + * token engine in place — the payments facade re-reads the engine per + * operation, so no restart is needed. */ import 'dotenv/config'; import { Sphere, type Identity } from '@unicitylabs/sphere-sdk'; import { createNodeProviders } from '@unicitylabs/sphere-sdk/impl/nodejs'; -import { createOwnStorageWalletApiProviders } from '@unicitylabs/sphere-sdk/impl/shared/wallet-api'; +import { createWalletApiProviders } from '@unicitylabs/sphere-sdk/impl/shared/wallet-api'; import { resolveAggregatorKey } from './aggregatorKey'; const TESTNET2 = 'testnet2' as const; @@ -69,13 +50,14 @@ const TESTNET2 = 'testnet2' as const; export interface BotSphere { sphere: Sphere; identity: Identity; - /** True when the wallet-api mailbox delivery rail was composed in (WALLET_API_URL set). */ - receivesPayments: boolean; } /** * Boot the bot's own Sphere wallet against testnet2. * + * - `WALLET_API_URL` — **required**. The wallet-api instance that holds the + * bot's token inventory and mailbox (e.g. + * `https://wallet-api.unicity.network`). Money does not move without it. * - `AGGREGATOR_API_KEY` — OPTIONAL testnet2 aggregator key (see * `.env.example`). Leave it unset and the bot provisions + persists its * OWN per-wallet free-plan key from the SGW on first boot (reused on every @@ -83,36 +65,39 @@ export interface BotSphere { * `resolveAggregatorKey` / `provisionAggregatorKey`. * - `BOT_MNEMONIC` — persists the bot's identity across runs. Leave empty to * auto-generate a fresh one (printed once — save it to persist). - * - `BOT_DATA_DIR` — local file storage root for wallet + token data - * (default `./.bot-data`); split into `/wallet` and `/tokens`. - * - `WALLET_API_URL` — optional. When set, composes the wallet-api mailbox - * delivery rail (own-storage custody preset) so the bot can RECEIVE tokens - * sent from a hosted Sphere wallet, which deliver via that mailbox, not - * Nostr. Left unset, the bot can still send + DM over Nostr, just not - * receive mailbox-delivered payments. + * - `BOT_DATA_DIR` — local file storage root for keys/identity state + * (default `./.bot-data`; the wallet lives in `/wallet`). + * - `BOT_DEVICE_ID` — optional stable device label for the wallet-api refresh + * token. Omit it and every run does a fresh challenge sign-in. */ export async function createBotSphere(): Promise { const envKey = process.env.AGGREGATOR_API_KEY?.trim() || undefined; const botMnemonic = process.env.BOT_MNEMONIC || undefined; const botDataDir = process.env.BOT_DATA_DIR || './.bot-data'; - const walletApiUrl = process.env.WALLET_API_URL || undefined; + const deviceId = process.env.BOT_DEVICE_ID?.trim() || undefined; + const walletApiUrl = process.env.WALLET_API_URL?.trim(); + + if (!walletApiUrl) { + throw new Error( + 'WALLET_API_URL is required: sphere-sdk 0.14 holds token custody in wallet-api, ' + + 'and Sphere.init throws INVALID_CONFIG without it. ' + + 'Set it in .env, e.g. WALLET_API_URL=https://wallet-api.unicity.network', + ); + } - // Bootstrap init: the wallet must exist (to sign the SGW provisioning - // challenge with sphere.deriveAddress(0)) before a key can be resolved, - // but createNodeProviders wants SOME oracle apiKey up front. An empty - // string is safe here — see the bootstrap-init finding in the file-header - // comment above; it's re-keyed via setOracleApiKey below once resolved. const base = createNodeProviders({ network: TESTNET2, dataDir: `${botDataDir}/wallet`, - tokensDir: `${botDataDir}/tokens`, oracle: { apiKey: envKey ?? '' }, }); - const receivesPayments = Boolean(walletApiUrl); - const providers = walletApiUrl - ? createOwnStorageWalletApiProviders(base, { baseUrl: walletApiUrl, network: TESTNET2 }) - : base; + // Attaches the `walletApi` transport config Sphere.init composes the + // payments vertical from. `network` must match the providers' network. + const providers = createWalletApiProviders(base, { + baseUrl: walletApiUrl, + network: TESTNET2, + deviceId, + }); const { sphere, generatedMnemonic } = await Sphere.init({ ...providers, @@ -149,5 +134,5 @@ export async function createBotSphere(): Promise { await sphere.setOracleApiKey(apiKey); } - return { sphere, identity, receivesPayments }; + return { sphere, identity }; } diff --git a/browser/CONNECT.md b/browser/CONNECT.md index 4482fbf..5712cfb 100644 --- a/browser/CONNECT.md +++ b/browser/CONNECT.md @@ -2,6 +2,26 @@ This guide explains how to integrate a browser dApp with the Sphere wallet using the Sphere Connect protocol. +> ## ⚠ Your dApp needs `@unicitylabs/sphere-sdk` **≥ 0.14.1** +> +> Wallet hosts from 0.14.1 onward enforce an **SDK version floor at the handshake**. `ConnectHost` +> applies a built-in default of `0.14.1-0` (a host may raise it via +> `ConnectHostConfig.minSdkVersion`). `ConnectClient` reports its own npm version +> in the handshake; a client below the floor — or one old enough not to report a version at all, +> which is every release before 0.14.1 — is refused with `UNSUPPORTED_PROTOCOL_VERSION` (**4007**) +> before any approval UI appears: +> +> ```json +> { "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 } } +> ``` +> +> The fix is a dependency bump and a rebuild — there is no protocol change to make. Connect is +> still **2.1**. Read `data.requiredSdk` / `data.actualSdk` and put them in your error copy; +> `describeConnectFailure()` in `src/lib/connectErrors.ts` does exactly that, so the user is told +> *which* version is needed instead of a bare "incompatible". + ## Quick Start ```typescript @@ -189,7 +209,11 @@ const { signature } = await wallet.intent('sign_message', { }); ``` -> **Note:** Invoice / accounting intents are experimental and are **not** supported by the Sphere wallet. Do not use them. +> **Removed in sphere-sdk 0.14:** the invoice / accounting surface is gone — +> `sphere_getInvoices`, `sphere_getInvoiceStatus`, the nine invoice intents and the +> `invoice:read` / `invoice:write` scopes no longer exist. They were never enabled in any wallet +> host. Connect stays at protocol **2.1**; the surface is simply 14 queries, 6 intents and 13 +> permission scopes now. --- @@ -206,7 +230,25 @@ const unsub = wallet.on('transfer:incoming', (data) => { return () => unsub(); ``` -Available events: auto-pushed `wallet:locked`, `wallet:unlocked`, `wallet:disconnected` and `identity:changed`, plus subscribable `transfer:incoming`, `transfer:confirmed`, `transfer:failed`. The full set is larger — see EventLogPanel for the complete list. +Available events: auto-pushed `wallet:locked`, `wallet:unlocked`, `wallet:disconnected` and `identity:changed`, plus subscribable `transfer:incoming`, `transfer:updated`, `transfer:attention`, `inventory:updated`, `payment_request:updated`, `connection:status` and more. The full set is larger — see EventLogPanel for the complete list. + +### Event names changed in sphere-sdk 0.14 + +The payments rebuild collapsed several events into fewer, better-typed ones. **Use the new +names in new code** — they are what the wallet actually emits: + +| Before 0.14 | Now | +|---|---| +| `transfer:confirmed`, `transfer:delivery_pending`, `transfer:failed` | `transfer:updated` (read `status` / `deliveryPending`) | +| `split:checkpoint-stuck`, `delivery:undeliverable`, `delivery:deferred` | `transfer:attention` `{ transferId, code, detail? }` | +| `sync:completed`, `sync:remote-update` | `inventory:updated` | +| `realtime:status`, `storage:degraded` | `connection:status` `{ status: 'connected' \| 'degraded' \| 'offline' }` | +| `payment_request:paid`, `:rejected`, `:expired` | `payment_request:updated` `{ id, status }` | +| `transfer:incoming` | unchanged | + +Every old name still works: the wallet host re-emits each one from the new event through a +compatibility adapter, so a dApp built before 0.14 keeps receiving them. Nothing a dApp +subscribes to silently stopped firing. The four events in `AUTO_PUSHED_EVENTS` — `wallet:locked`, `wallet:unlocked`, `wallet:disconnected`, `identity:changed` — are pushed by `ConnectHost` unconditionally. Never route them through `sphere_subscribe`: `Sphere.on()` accepts any string and would silently never emit, so the subscribe would succeed and deliver nothing forever. See [Wallet Lock Handling](#wallet-lock-handling-wallet_eventslocked) below. @@ -424,11 +466,11 @@ chrome carries the passive "N requests blocked — Unlock" badge. This is where session-preserving lock actually pays off — the host outlives both the lock and a reload of the framed page. -A locked wallet that HOLDS your session serves four of the sixteen `RPC_METHODS`: +A locked wallet that HOLDS your session serves four of the fourteen `RPC_METHODS`: `sphere_getIdentity` (from a frozen snapshot), `sphere_subscribe`, `sphere_unsubscribe` and -`sphere_disconnect`. The other **twelve, and every intent**, are refused `WALLET_LOCKED` (4009) — -the five money reads, `sphere_resolve`, **all four DM reads** (`getConversations`, `getMessages`, -`getDMUnreadCount`, `markAsRead`) and both invoice reads. Nothing is cached, and **messaging does +`sphere_disconnect`. The other **ten, and every intent**, are refused `WALLET_LOCKED` (4009) — +the five money reads, `sphere_resolve`, and **all four DM reads** (`getConversations`, +`getMessages`, `getDMUnreadCount`, `markAsRead`). Nothing is cached, and **messaging does not keep working while locked**. So a dApp must **stop issuing reads while `isWalletLocked`** and resume on `unlockEpoch`, rather than polling into refusals: every refusal increments the wallet's blocked-request badge. diff --git a/browser/README.md b/browser/README.md index 96cb57a..b1742ab 100644 --- a/browser/README.md +++ b/browser/README.md @@ -5,6 +5,11 @@ the Connect protocol and exercises the full surface: every read query, every intent, and live events. The wallet stays separate — this app never sees a private key; it asks the wallet to act, and the user approves. +> **Needs `@unicitylabs/sphere-sdk` ≥ 0.14.1.** Wallet hosts from 0.14.1 enforce +> an SDK version floor at the handshake and refuse older clients with +> `UNSUPPORTED_PROTOCOL_VERSION` (4007) before any approval UI shows. See +> [CONNECT.md](CONNECT.md#-your-dapp-needs-unicitylabssphere-sdk--0141). + ## When to use this Reach for this shape when **your app runs in a browser** and **the user is @@ -59,8 +64,7 @@ each panel drives one query / intent / event. > `deliveryPending: true` and **no `transferId`** — see the Send panel; never > re-send that (it would pay twice). -``` -**Events** (real-time push): auto-pushed `wallet:locked` · `wallet:unlocked` · `wallet:disconnected` · `identity:changed`; subscribable `transfer:incoming` · `transfer:confirmed` · `transfer:failed` · and more. A lock does **not** disconnect — see [CONNECT.md](CONNECT.md#wallet-lock-handling-wallet_eventslocked). +**Events** (real-time push): auto-pushed `wallet:locked` · `wallet:unlocked` · `wallet:disconnected` · `identity:changed`; subscribable `transfer:incoming` · `transfer:updated` · `transfer:attention` · `inventory:updated` · `payment_request:updated` · `connection:status` · and more. Those are the sphere-sdk 0.14 names — the pre-0.14 ones (`transfer:confirmed`, `sync:*`, …) still fire, re-emitted by the host's compatibility adapter. A lock does **not** disconnect — see [CONNECT.md](CONNECT.md#wallet-lock-handling-wallet_eventslocked). ## How the connection is made diff --git a/browser/package-lock.json b/browser/package-lock.json index ac9b143..b32edf7 100644 --- a/browser/package-lock.json +++ b/browser/package-lock.json @@ -14,7 +14,7 @@ "@tailwindcss/vite": "^4.1.16", "@tanstack/react-query": "^5.101.4", "@tanstack/react-table": "^8.21.3", - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "@unicitylabs/sphere-ui": "^0.1.34", "lucide-react": "^0.400.0", "react": "^19.1.1", @@ -388,13 +388,6 @@ "node": ">=6.9.0" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT", - "optional": true - }, "node_modules/@csstools/color-helpers": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", @@ -588,20 +581,6 @@ "react": ">=16.8.0" } }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "optional": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -609,7 +588,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -626,7 +604,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -643,7 +620,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -660,7 +636,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -677,7 +652,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -694,7 +668,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -711,7 +684,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -728,7 +700,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -745,7 +716,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -762,7 +732,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -779,7 +748,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -796,7 +764,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -813,7 +780,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -830,7 +796,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -847,7 +812,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -864,7 +828,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -881,7 +844,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -898,7 +860,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -915,7 +876,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -932,7 +892,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -949,7 +908,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -966,7 +924,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -983,7 +940,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1000,7 +956,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1017,7 +972,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1034,7 +988,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1107,156 +1060,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT", - "optional": true - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.21.tgz", - "integrity": "sha512-GipsBinthJuk7DpwthTUixZHSaKogcxm2glPCP0VkYkEpzZrfEvBEekRvlP5+1Ak8pReaHcz4gPKFA9X6MG0WQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/crypto/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.3", - "main-event": "^1.0.1", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/logger": { - "version": "6.2.11", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.11.tgz", - "integrity": "sha512-zZ4m2EKyyRY2G8GAzmG/ZJm4CIqmaJucl5X7zTmGs1SXlb4Ayj4aP1vsaP/cVWadf/RuPWjgmIJP+Y/sZoE7Rg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-datastore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", - "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2", - "interface-store": "^8.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-store": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", - "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2" - } - }, - "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.13.tgz", - "integrity": "sha512-A3sP3QUjunp7Wo5aGOkRsuYtGJZbLD4KPlHKObMam1fUCx+gHKnfyKeaWMQLkjeaUNETz9R6llquWyi41QzkLg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.1.21", - "@libp2p/interface": "^3.2.5", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/@noble/ciphers": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz", @@ -1270,13 +1073,25 @@ } }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" + }, + "engines": { + "node": ">= 20.19.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "license": "MIT", "engines": { "node": ">= 20.19.0" }, @@ -1336,7 +1151,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1350,7 +1164,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1364,7 +1177,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1378,7 +1190,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1392,7 +1203,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1406,7 +1216,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1420,7 +1229,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1434,7 +1242,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1448,7 +1255,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1462,7 +1268,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1476,7 +1281,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1490,7 +1294,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1504,7 +1307,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1518,7 +1320,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1532,7 +1333,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1546,7 +1346,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1560,7 +1359,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1574,7 +1372,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1588,7 +1385,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1602,7 +1398,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1616,7 +1411,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1630,7 +1424,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1644,7 +1437,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1658,7 +1450,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1672,7 +1463,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2201,14 +1991,13 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -2286,16 +2075,16 @@ } }, "node_modules/@unicitylabs/sphere-sdk": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.13.1.tgz", - "integrity": "sha512-i/cnbr9Puk30b7z/JANTYN8hhp5OmVCpmo/MA2dS0SwV9wtBasQYI3sLosRXaVMCEX2n269K1y3M+BccU1KBxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.14.1.tgz", + "integrity": "sha512-8Jxro6A9bAvNYhFTAweYq08paIEVkDZayOl0MNBHmbED0ef2Si2oe+JK92VYL2DBlqfUeK2kWlJW+lAckQIZtw==", "license": "MIT", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@unicitylabs/nostr-js-sdk": "^0.6.0", - "@unicitylabs/state-transition-sdk": "2.0.1", + "@unicitylabs/state-transition-sdk": "2.0.2", "bip39": "^3.1.0", "buffer": "^6.0.3", "canonicalize": "^3.0.0", @@ -2304,32 +2093,10 @@ "engines": { "node": ">=22.0.0" }, - "optionalDependencies": { - "@libp2p/crypto": "^5.1.13", - "@libp2p/peer-id": "^6.0.4", - "ipns": "^10.0.0", - "multiformats": "^13.4.2" - }, "peerDependencies": { - "@libp2p/crypto": ">=5.0.0", - "@libp2p/peer-id": ">=6.0.0", - "ipns": ">=10.0.0", - "multiformats": ">=13.0.0", "ws": ">=8.0.0" }, "peerDependenciesMeta": { - "@libp2p/crypto": { - "optional": true - }, - "@libp2p/peer-id": { - "optional": true - }, - "ipns": { - "optional": true - }, - "multiformats": { - "optional": true - }, "ws": { "optional": true } @@ -2361,9 +2128,9 @@ } }, "node_modules/@unicitylabs/state-transition-sdk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.1.tgz", - "integrity": "sha512-O0pjxaL32VgHYjSM3Ei64UWBG2qwBzdQ9aIdlyhzTnPdI+kKu3h9saSMF1uV9XTfqKHsNGUpE/2dFUulNfmdmg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.2.tgz", + "integrity": "sha512-SYCCShlwSUqeRhOocGkg9QCx0lacxelpRa6w3PQTLdXO8BO6Xo6Qh34ABZW2UcRfMDDPaXA0KSIyUo+UbiM9FQ==", "license": "ISC", "dependencies": { "@noble/curves": "2.2.0", @@ -2374,6 +2141,21 @@ "node": ">=22" } }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -2508,13 +2290,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2732,16 +2507,6 @@ "node": ">=18" } }, - "node_modules/cborg": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.8.tgz", - "integrity": "sha512-enonPtLyeEF8M8GF/5NdjLHmRGslrwKR2LvkN+yFYQJw7mzt6iOoPvkVs3vcb3rUvltzL0QfvtM6FQ44o8nhSg==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -2818,7 +2583,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/d3-array": { @@ -3077,7 +2842,6 @@ "version": "0.27.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3155,7 +2919,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3212,7 +2975,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3239,13 +3001,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT", - "optional": true - }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -3317,34 +3072,6 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/interface-datastore": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-9.0.3.tgz", - "integrity": "sha512-NLZa7Mp+0qn48nSwIY/C36da4uVIKzwG2tuEIpaSJArsuB2RrdyDWwkoDUyjsJ+VrMntXz38VSk9vXTx/ZUpAw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "interface-store": "^7.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-datastore/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/interface-store": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-7.0.2.tgz", - "integrity": "sha512-KYOPcDH+1peaPhSeoZujR5nwkVeola1EdrnrlHTIM0HRNUs9B0aTsUQMH5kTmIjaQq1BOowoUyoCamgL8IMyww==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3354,68 +3081,6 @@ "node": ">=12" } }, - "node_modules/ipns": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-10.1.6.tgz", - "integrity": "sha512-mTACIdTBBXY5kbCo3t/M3ycNWbjajLrSXhTvjD0MEGyOUaI3uMv94JbJSHGaJ2xxRlpOrRk1ifToOsyPZiglnA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/logger": "^6.0.4", - "cborg": "^5.1.0", - "interface-datastore": "^9.0.2", - "multiformats": "^13.2.2", - "protons-runtime": "^6.0.1", - "timestamp-nano": "^1.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/protons-runtime": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", - "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/uint8-varint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", - "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/ipns/node_modules/uint8arraylist": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", - "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/ipns/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -3809,13 +3474,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/main-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.4.tgz", - "integrity": "sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", @@ -3845,18 +3503,10 @@ "dev": true, "license": "MIT" }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -3907,36 +3557,6 @@ "node": ">=12.20.0" } }, - "node_modules/p-queue": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", - "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventemitter3": "^5.0.4", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -3961,14 +3581,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3981,7 +3599,6 @@ "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4028,13 +3645,6 @@ "dev": true, "license": "MIT" }, - "node_modules/progress-events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", - "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4046,18 +3656,6 @@ "react-is": "^16.13.1" } }, - "node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4223,7 +3821,6 @@ "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -4323,19 +3920,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -4362,16 +3946,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -4399,7 +3973,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4488,44 +4061,6 @@ "node": ">=14.17" } }, - "node_modules/uint8-varint": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", - "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^3.0.1", - "uint8arrays": "^6.1.0" - } - }, - "node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^14.0.0" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -4566,13 +4101,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT", - "optional": true - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -4612,7 +4140,6 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", @@ -4786,27 +4313,6 @@ "node": ">=18" } }, - "node_modules/weald": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", - "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "ms": "^4.0.0-nightly.202508271359", - "supports-color": "^10.0.0" - } - }, - "node_modules/weald/node_modules/ms": { - "version": "4.0.0-nightly.202508271359", - "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", - "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -4862,7 +4368,7 @@ "version": "8.21.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/browser/package.json b/browser/package.json index a05756b..ad4af32 100644 --- a/browser/package.json +++ b/browser/package.json @@ -17,7 +17,7 @@ "@tailwindcss/vite": "^4.1.16", "@tanstack/react-query": "^5.101.4", "@tanstack/react-table": "^8.21.3", - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "@unicitylabs/sphere-ui": "^0.1.34", "lucide-react": "^0.400.0", "react": "^19.1.1", diff --git a/browser/src/components/events/EventLogPanel.test.ts b/browser/src/components/events/EventLogPanel.test.ts index cdacd1f..f337a82 100644 --- a/browser/src/components/events/EventLogPanel.test.ts +++ b/browser/src/components/events/EventLogPanel.test.ts @@ -21,4 +21,49 @@ describe('EventLogPanel event list', () => { expect(typeof EVENT_COLORS[event]).toBe('string'); } }); + + // The demo is teaching material: it must subscribe to the names a current wallet + // actually emits. The pre-0.14 names still work through the host's compat adapter, + // so a stale list fails silently — nothing here would break, the panel would just + // quietly teach the wrong API. Pin both directions. + it('uses the sphere-sdk 0.14 payments event names', () => { + for (const event of [ + 'transfer:incoming', + 'transfer:updated', + 'transfer:attention', + 'inventory:updated', + 'history:updated', + 'payment_request:incoming', + 'payment_request:updated', + 'connection:status', + ]) { + expect(ALL_EVENTS).toContain(event); + } + }); + + it('lists no name the payments-v2 flip replaced', () => { + for (const event of [ + 'transfer:confirmed', + 'transfer:delivery_pending', + 'transfer:failed', + 'split:checkpoint-stuck', + 'delivery:undeliverable', + 'delivery:deferred', + 'sync:started', + 'sync:completed', + 'sync:provider', + 'sync:error', + 'sync:remote-update', + 'realtime:status', + 'storage:degraded', + 'payment_request:paid', + 'payment_request:rejected', + 'payment_request:expired', + // Never existed in any SDK release — the old list carried them anyway. + 'payment_request:accepted', + 'payment_request:response', + ]) { + expect(ALL_EVENTS).not.toContain(event); + } + }); }); diff --git a/browser/src/components/events/EventLogPanel.tsx b/browser/src/components/events/EventLogPanel.tsx index 7bb0aa2..3e95c6e 100644 --- a/browser/src/components/events/EventLogPanel.tsx +++ b/browser/src/components/events/EventLogPanel.tsx @@ -12,30 +12,39 @@ interface Props { on: (event: string, handler: (data: unknown) => void) => () => void; } +/** + * The events this demo subscribes to, using the CURRENT sphere-sdk 0.14 names. + * + * The payments-v2 flip renamed most of the money-side events: + * transfer:confirmed + transfer:delivery_pending + transfer:failed -> transfer:updated + * split:checkpoint-stuck / delivery:undeliverable / delivery:deferred -> transfer:attention + * sync:* -> inventory:updated + * realtime:status + storage:degraded -> connection:status + * payment_request:paid / :rejected / :expired -> payment_request:updated + * + * The old names still resolve — a wallet host re-emits every one of them from the new event + * through a compatibility adapter, so a dApp built before 0.14 keeps working. New code should + * use the names below; they are what the wallet actually emits. + */ export const ALL_EVENTS = [ // Transfers 'transfer:incoming', - 'transfer:confirmed', - 'transfer:failed', + 'transfer:updated', + 'transfer:attention', + // Inventory & history + 'inventory:updated', + 'history:updated', // Payment requests 'payment_request:incoming', - 'payment_request:accepted', - 'payment_request:rejected', - 'payment_request:paid', - 'payment_request:response', + 'payment_request:updated', // Messages 'message:dm', 'message:read', 'message:typing', 'composing:started', 'message:broadcast', - // Sync - 'sync:started', - 'sync:completed', - 'sync:provider', - 'sync:error', - 'sync:remote-update', // Connection & wallet state + 'connection:status', 'connection:changed', 'wallet:locked', 'wallet:unlocked', @@ -60,27 +69,23 @@ export const ALL_EVENTS = [ export const EVENT_COLORS: Record = { // Transfers 'transfer:incoming': 'bg-green-500/15 text-green-400', - 'transfer:confirmed': 'bg-green-500/15 text-green-400', - 'transfer:failed': 'bg-red-500/15 text-red-400', + 'transfer:updated': 'bg-green-500/15 text-green-400', + // Amber: "needs a look", not "failed". Never auto-retry a send on one of these. + 'transfer:attention': 'bg-amber-500/15 text-amber-400', + // Inventory & history + 'inventory:updated': 'bg-white/3 text-white/55', + 'history:updated': 'bg-white/3 text-white/55', // Payment requests 'payment_request:incoming': 'bg-orange-500/10 text-orange-400', - 'payment_request:accepted': 'bg-green-500/15 text-green-400', - 'payment_request:rejected': 'bg-red-500/15 text-red-400', - 'payment_request:paid': 'bg-green-500/15 text-green-400', - 'payment_request:response': 'bg-orange-500/10 text-orange-400', + 'payment_request:updated': 'bg-orange-500/10 text-orange-400', // Messages 'message:dm': 'bg-indigo-500/15 text-indigo-400', 'message:read': 'bg-indigo-500/15 text-indigo-400', 'message:typing': 'bg-indigo-500/15 text-indigo-400', 'composing:started': 'bg-indigo-500/15 text-indigo-400', 'message:broadcast': 'bg-indigo-500/15 text-indigo-400', - // Sync - 'sync:started': 'bg-white/3 text-white/55', - 'sync:completed': 'bg-white/3 text-white/55', - 'sync:provider': 'bg-white/3 text-white/55', - 'sync:error': 'bg-red-500/15 text-red-400', - 'sync:remote-update': 'bg-white/3 text-white/55', // Connection & wallet state + 'connection:status': 'bg-yellow-500/15 text-amber-400', 'connection:changed': 'bg-yellow-500/15 text-amber-400', // Amber, not red: a lock is a pause, not a fatal teardown. wallet:disconnected is the red one. 'wallet:locked': 'bg-amber-500/15 text-amber-400', @@ -136,7 +141,9 @@ export function EventLogPanel({ on }: Props) {

Event Log

events -

Real-time wallet events ({ALL_EVENTS.length} subscribed)

+

+ Real-time wallet events ({ALL_EVENTS.length} subscribed) — sphere-sdk 0.14 names +

describe('useWalletConnect — a refused handshake says which version to move to', () => { it('surfaces the SDK floor the wallet compared, not just its bare message', async () => { - // Exactly what a 0.13 wallet sends a 0.11 app: the numbers are in `data`, the message - // (from a wallet on an older SDK) names none of them. + // Exactly what a 0.14.1 wallet sends a pre-0.14.1 app. A client that old reports no + // sdkVersion at all, so `actualSdk` comes back null and only `requiredSdk` is nameable. FakeConnectClient.nextConnectError = new ConnectError( - 'SDK version below the required minimum', + 'SDK version unknown (not reported) is below the required minimum 0.14.1-0', ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, - { reason: 'protocol_incompatible', requiredSdk: '0.12.0-0', actualSdk: '0.11.9' }, + { reason: 'protocol_incompatible', requiredSdk: '0.14.1-0', actualSdk: null }, ); const hook = renderHook(() => useWalletConnect()); @@ -480,8 +480,8 @@ describe('useWalletConnect — a refused handshake says which version to move to await connectPopup(hook.result); expect(hook.result.current.isConnected).toBe(false); - expect(hook.result.current.error).toContain('0.11.9'); - expect(hook.result.current.error).toContain('0.12.0-0'); + expect(hook.result.current.error).toContain('reported no sphere-sdk version'); + expect(hook.result.current.error).toContain('0.14.1-0'); }); it('leaves an ordinary failure message alone', async () => { diff --git a/nodejs/README.md b/nodejs/README.md index e575e8a..36f908a 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -10,6 +10,12 @@ This is the only example that exercises `WebSocketTransport`, the third Connect transport (the other two, `PostMessageTransport` and `ExtensionTransport`, are browser-only). +> **Needs `@unicitylabs/sphere-sdk` ≥ 0.14.1 on both sides.** A 0.14.1 `ConnectHost` +> enforces an SDK version floor at the handshake and refuses older clients with +> `UNSUPPORTED_PROTOCOL_VERSION` (4007) — including the mock wallet server here. +> The refusal carries `data.requiredSdk` / `data.actualSdk`; `describeConnectFailure()` +> in `src/lockResume.ts` is a small example of turning that into useful copy. + ## When to use this Reach for this when your app is a **Node process — a CLI, a desktop tool, a @@ -61,6 +67,15 @@ conversations | messages | unread | read # chat > 64-hex id — the same contract the wallet enforces. A `send` may resolve with > `deliveryPending: true` and no `transferId`; that is a success, not a retry. +### What the mock wallet teaches + +`src/mockSphere.ts` is shaped like a **real sphere-sdk 0.14 wallet**: `payments` is the +payments-v2 facade (`assets()` / `tokens()` / paged `history()`), and `paymentsV2` is the +deprecated alias that `ConnectHost` reads to detect a v2 wallet. The host maps the Connect wire +onto it — `sphere_getBalance` and `sphere_getAssets` both serve `assets()`, `sphere_getHistory` +walks every `history()` page and flattens them — so the **dApp side of the wire did not change +at all** in 0.14. That is the point: the payments rebuild is invisible to a Connect client. + ## How the connection is made ```typescript diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 8d2d837..ea529b4 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -8,7 +8,7 @@ "name": "sphere-connect-nodejs-example", "version": "0.0.0", "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "ws": "^8.18.0" }, "devDependencies": { @@ -19,27 +19,6 @@ "vitest": "^4.1.10" } }, - "node_modules/@chainsafe/is-ip": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@chainsafe/is-ip/-/is-ip-2.1.0.tgz", - "integrity": "sha512-KIjt+6IfysQ4GCv66xihEitBjvhU/bixbbbFxdJ1sqCp4uJ0wuZiYBPhksZoy4lfaF0k9cwNzY5upEW/VWdw3w==", - "license": "MIT", - "optional": true - }, - "node_modules/@dnsquery/dns-packet": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@dnsquery/dns-packet/-/dns-packet-6.1.1.tgz", - "integrity": "sha512-WXTuFvL3G+74SchFAtz3FgIYVOe196ycvGsMgvSH/8Goptb1qpIQtIuM4SOK9G9lhMWYpHxnXyy544ZhluFOew==", - "license": "MIT", - "optional": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.4", - "utf8-codec": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -523,156 +502,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT", - "optional": true - }, - "node_modules/@libp2p/crypto": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@libp2p/crypto/-/crypto-5.1.21.tgz", - "integrity": "sha512-GipsBinthJuk7DpwthTUixZHSaKogcxm2glPCP0VkYkEpzZrfEvBEekRvlP5+1Ak8pReaHcz4gPKFA9X6MG0WQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "multiformats": "^14.0.0", - "protons-runtime": "^7.0.0", - "uint8arraylist": "^3.0.2", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/crypto/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/interface": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@libp2p/interface/-/interface-3.2.5.tgz", - "integrity": "sha512-RdTchTdakBBc4ShKKsvoME/bJYsrCoVDpdYQVdd4TQ30TCb8QwWKGClqAtw7gfLNuaUKm41xz06kASW6AZpbDA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@multiformats/dns": "^1.0.6", - "@multiformats/multiaddr": "^13.0.3", - "main-event": "^1.0.1", - "multiformats": "^14.0.0", - "progress-events": "^1.1.0", - "uint8arraylist": "^3.0.2" - } - }, - "node_modules/@libp2p/interface/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/logger": { - "version": "6.2.11", - "resolved": "https://registry.npmjs.org/@libp2p/logger/-/logger-6.2.11.tgz", - "integrity": "sha512-zZ4m2EKyyRY2G8GAzmG/ZJm4CIqmaJucl5X7zTmGs1SXlb4Ayj4aP1vsaP/cVWadf/RuPWjgmIJP+Y/sZoE7Rg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/interface": "^3.2.5", - "@multiformats/multiaddr": "^13.0.3", - "interface-datastore": "^10.0.1", - "multiformats": "^14.0.0", - "weald": "^1.1.0" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-datastore": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-10.0.1.tgz", - "integrity": "sha512-DYMj/Og5Cz1Qwkx6/x5KRvR8SYEX7rVAv3KKCm2NzTwWSfpNAC4PahjcYbHyoZBP6zPWrhQv5n5wE+vaDdgSAg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2", - "interface-store": "^8.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/logger/node_modules/interface-store": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-8.0.0.tgz", - "integrity": "sha512-e2+s3EEROzM+Wlas4hU3zveTUscvVMf1BOvdsJfpzFm19SoEXLVadpACjWOnM491HqGpvtfFnevyiaN8W+I6Eg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "abort-error": "^1.0.2" - } - }, - "node_modules/@libp2p/logger/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@libp2p/peer-id": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/@libp2p/peer-id/-/peer-id-6.0.13.tgz", - "integrity": "sha512-A3sP3QUjunp7Wo5aGOkRsuYtGJZbLD4KPlHKObMam1fUCx+gHKnfyKeaWMQLkjeaUNETz9R6llquWyi41QzkLg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.1.21", - "@libp2p/interface": "^3.2.5", - "multiformats": "^14.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@libp2p/peer-id/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/@multiformats/dns": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.15.tgz", - "integrity": "sha512-W0zAMABtAn+3chgFcPGvllKND7M6GblMAAFcJQTy+iMmGiyFErZYPzAh2b50Y3SFf340iFV7+ckVgZkGfUyxzA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@dnsquery/dns-packet": "^6.1.1", - "@libp2p/interface": "^3.1.0", - "hashlru": "^2.3.0", - "p-queue": "^9.0.0", - "progress-events": "^1.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.3.tgz", - "integrity": "sha512-mEqqJ4r3a/uuFMTpRkU316wGNIDQNhuVWpm+ebKTQeYsfv9jXbPONWM6VVnj3KGUrwfsX7GZOyp4TFqEA2SPCw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^14.0.0", - "uint8-varint": "^3.0.0", - "uint8arrays": "^6.1.1" - } - }, - "node_modules/@multiformats/multiaddr/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -705,12 +534,12 @@ } }, "node_modules/@noble/curves": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", - "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz", + "integrity": "sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg==", "license": "MIT", "dependencies": { - "@noble/hashes": "2.2.0" + "@noble/hashes": "2.3.0" }, "engines": { "node": ">= 20.19.0" @@ -720,9 +549,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -1133,16 +962,16 @@ } }, "node_modules/@unicitylabs/sphere-sdk": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.13.1.tgz", - "integrity": "sha512-i/cnbr9Puk30b7z/JANTYN8hhp5OmVCpmo/MA2dS0SwV9wtBasQYI3sLosRXaVMCEX2n269K1y3M+BccU1KBxA==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@unicitylabs/sphere-sdk/-/sphere-sdk-0.14.1.tgz", + "integrity": "sha512-8Jxro6A9bAvNYhFTAweYq08paIEVkDZayOl0MNBHmbED0ef2Si2oe+JK92VYL2DBlqfUeK2kWlJW+lAckQIZtw==", "license": "MIT", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/curves": "^2.0.1", "@noble/hashes": "^2.0.1", "@unicitylabs/nostr-js-sdk": "^0.6.0", - "@unicitylabs/state-transition-sdk": "2.0.1", + "@unicitylabs/state-transition-sdk": "2.0.2", "bip39": "^3.1.0", "buffer": "^6.0.3", "canonicalize": "^3.0.0", @@ -1151,41 +980,19 @@ "engines": { "node": ">=22.0.0" }, - "optionalDependencies": { - "@libp2p/crypto": "^5.1.13", - "@libp2p/peer-id": "^6.0.4", - "ipns": "^10.0.0", - "multiformats": "^13.4.2" - }, "peerDependencies": { - "@libp2p/crypto": ">=5.0.0", - "@libp2p/peer-id": ">=6.0.0", - "ipns": ">=10.0.0", - "multiformats": ">=13.0.0", "ws": ">=8.0.0" }, "peerDependenciesMeta": { - "@libp2p/crypto": { - "optional": true - }, - "@libp2p/peer-id": { - "optional": true - }, - "ipns": { - "optional": true - }, - "multiformats": { - "optional": true - }, "ws": { "optional": true } } }, "node_modules/@unicitylabs/state-transition-sdk": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.1.tgz", - "integrity": "sha512-O0pjxaL32VgHYjSM3Ei64UWBG2qwBzdQ9aIdlyhzTnPdI+kKu3h9saSMF1uV9XTfqKHsNGUpE/2dFUulNfmdmg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@unicitylabs/state-transition-sdk/-/state-transition-sdk-2.0.2.tgz", + "integrity": "sha512-SYCCShlwSUqeRhOocGkg9QCx0lacxelpRa6w3PQTLdXO8BO6Xo6Qh34ABZW2UcRfMDDPaXA0KSIyUo+UbiM9FQ==", "license": "ISC", "dependencies": { "@noble/curves": "2.2.0", @@ -1196,6 +1003,33 @@ "node": ">=22" } }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/curves": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.2.0.tgz", + "integrity": "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@unicitylabs/state-transition-sdk/node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -1309,13 +1143,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/abort-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/abort-error/-/abort-error-1.0.2.tgz", - "integrity": "sha512-lVgvB2NyPLqbXXhVmXcYFTC1x5K7CiVdPgdY7LGgFQWC8506oN01sPN3i9cl9ynuwF4iJ0TS9exnR7cZ9FuX4w==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1403,16 +1230,6 @@ "node": ">=18" } }, - "node_modules/cborg": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.8.tgz", - "integrity": "sha512-enonPtLyeEF8M8GF/5NdjLHmRGslrwKR2LvkN+yFYQJw7mzt6iOoPvkVs3vcb3rUvltzL0QfvtM6FQ44o8nhSg==", - "license": "Apache-2.0", - "optional": true, - "bin": { - "cborg": "lib/bin.js" - } - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1505,13 +1322,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT", - "optional": true - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -1568,13 +1378,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/hashlru": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hashlru/-/hashlru-2.3.0.tgz", - "integrity": "sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==", - "license": "MIT", - "optional": true - }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1595,96 +1398,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/interface-datastore": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/interface-datastore/-/interface-datastore-9.0.3.tgz", - "integrity": "sha512-NLZa7Mp+0qn48nSwIY/C36da4uVIKzwG2tuEIpaSJArsuB2RrdyDWwkoDUyjsJ+VrMntXz38VSk9vXTx/ZUpAw==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "interface-store": "^7.0.0", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/interface-datastore/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, - "node_modules/interface-store": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-7.0.2.tgz", - "integrity": "sha512-KYOPcDH+1peaPhSeoZujR5nwkVeola1EdrnrlHTIM0HRNUs9B0aTsUQMH5kTmIjaQq1BOowoUyoCamgL8IMyww==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/ipns": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/ipns/-/ipns-10.1.6.tgz", - "integrity": "sha512-mTACIdTBBXY5kbCo3t/M3ycNWbjajLrSXhTvjD0MEGyOUaI3uMv94JbJSHGaJ2xxRlpOrRk1ifToOsyPZiglnA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "@libp2p/crypto": "^5.0.0", - "@libp2p/interface": "^3.0.2", - "@libp2p/logger": "^6.0.4", - "cborg": "^5.1.0", - "interface-datastore": "^9.0.2", - "multiformats": "^13.2.2", - "protons-runtime": "^6.0.1", - "timestamp-nano": "^1.0.1", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/protons-runtime": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-6.0.2.tgz", - "integrity": "sha512-hiyjyANwGcgmzc+tXc1/ZcSZhKnl5MDjaVNWkISHBgadaU0sjTgKIKZMZ62d9J9zlSTyKHCs/osPkQ/3Z+7yeA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^2.0.4", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/ipns/node_modules/uint8-varint": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-2.0.5.tgz", - "integrity": "sha512-jeFLbL/x30wBRnWjKE1qVBXeumG46r7XmYkpis955lTQ+blccGKFrOsSMHlxePwYB1pI7L8YPHz1t4jLxEs3nA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^2.0.0", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/ipns/node_modules/uint8arraylist": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-2.4.9.tgz", - "integrity": "sha512-KxWjyEFzchzik3aoQlK66oaoxIReoMo5bQRm1fcjBUZvE8xv/tyR3CTKhjh6K/faV8VaF6hd5pjr45CzbwuwkA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^5.0.1" - } - }, - "node_modules/ipns/node_modules/uint8arrays": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-5.1.1.tgz", - "integrity": "sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^13.0.0" - } - }, "node_modules/libphonenumber-js": { "version": "1.13.9", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.13.9.tgz", @@ -1962,30 +1675,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/main-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/main-event/-/main-event-1.0.4.tgz", - "integrity": "sha512-sKazUjIy2Jalv5lkQ446iOcrx8Q7TkaCuk6xfnzg5uUqMusMLDMPmRDmSNE2kjSVpSTJo4j1bQZusS+Ib7Bvrg==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/ms": { - "version": "4.0.0-nightly.202508271359", - "resolved": "https://registry.npmjs.org/ms/-/ms-4.0.0-nightly.202508271359.tgz", - "integrity": "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - } - }, - "node_modules/multiformats": { - "version": "13.4.2", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz", - "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/nanoid": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", @@ -2019,36 +1708,6 @@ "node": ">=12.20.0" } }, - "node_modules/p-queue": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", - "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", - "license": "MIT", - "optional": true, - "dependencies": { - "eventemitter3": "^5.0.4", - "p-timeout": "^7.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", - "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2105,25 +1764,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/progress-events": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.1.0.tgz", - "integrity": "sha512-82DVc5tI36neVB3IjdXR11ztwGuoBc98em9ijzubeZKxI47OlV2Znq6mlPqE5xPDzO2Uw98GHiQSjj2favBCRQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, - "node_modules/protons-runtime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/protons-runtime/-/protons-runtime-7.0.0.tgz", - "integrity": "sha512-r/e72006xoVND4Uvf1aIs4OQOkJaASBU3ip4DzOWAktoKAkTiOYqKoS3TYMBm8HnnYU8+h0uEzr5gIRwk/pmTg==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8-varint": "^3.0.0", - "uint8arraylist": "^3.0.0", - "uint8arrays": "^6.0.0" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -2199,29 +1839,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/timestamp-nano": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz", - "integrity": "sha512-4oGOVZWTu5sl89PtCDnhQBSt7/vL1zVEwAfxH1p49JhTosxzVQWYBYFRFZ8nJmo0G6f824iyP/44BFAwIoKvIA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2308,44 +1925,6 @@ "node": ">=14.17" } }, - "node_modules/uint8-varint": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uint8-varint/-/uint8-varint-3.0.0.tgz", - "integrity": "sha512-S4DdpXBaLwKcFo7f0bWzWfHjbZ/i3QhM842qn+ZvHjxqFCfUcEB9SQNcmI69S+zMlcmIcKxsk9Iyw77S2Kxv6Q==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arraylist": "^3.0.1", - "uint8arrays": "^6.1.0" - } - }, - "node_modules/uint8arraylist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uint8arraylist/-/uint8arraylist-3.0.2.tgz", - "integrity": "sha512-LDVoq9BQaGJzGDUovEnoX6rpKCvnY/Jbtws4ikwnBzjRbq5qBAFpBZevUEbSmMM87aO0Sp+wOZy2ZXf5yODmXQ==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "uint8arrays": "^6.0.0" - } - }, - "node_modules/uint8arrays": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-6.1.1.tgz", - "integrity": "sha512-iz7JN0XCSZYA111lhFG2Ui9EhFvTNekqSRHw3lvMHq+dzwWy1OQftxFQREEh4rffU0oSoXdQHsk2TiHKVm4fsA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "multiformats": "^14.0.0" - } - }, - "node_modules/uint8arrays/node_modules/multiformats": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-14.0.5.tgz", - "integrity": "sha512-vbIm83F2yZ1pWJGS0yl0ysracIvv56LtbrIyiIQHoLdYDJOMoLfVFsXhh9DUH4SFdkdkFhucyWniihsNzVEjkQ==", - "license": "Apache-2.0 OR MIT", - "optional": true - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -2353,13 +1932,6 @@ "dev": true, "license": "MIT" }, - "node_modules/utf8-codec": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/utf8-codec/-/utf8-codec-1.0.0.tgz", - "integrity": "sha512-S/QSLezp3qvG4ld5PUfXiH7mCFxLKjSVZRFkB3DOjgwHuJPFDkInAXc/anf7BAbHt/D38ozDzL+QMZ6/7gsI6w==", - "license": "MIT", - "optional": true - }, "node_modules/uuid": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", @@ -2541,17 +2113,6 @@ } } }, - "node_modules/weald": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/weald/-/weald-1.1.3.tgz", - "integrity": "sha512-vMWtNbYuPb58NeG2+0sKA0Een4VMDwzf+3oHqh68buWRSOMUBlUeRb11LhV28czV+DUpJHRykifijZDuS9bInA==", - "license": "Apache-2.0 OR MIT", - "optional": true, - "dependencies": { - "ms": "^4.0.0-nightly.202508271359", - "supports-color": "^10.0.0" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", diff --git a/nodejs/package.json b/nodejs/package.json index d8a3c48..05e2495 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -6,10 +6,11 @@ "scripts": { "server": "npx tsx src/mock-wallet-server.ts", "client": "npx tsx src/index.ts", + "typecheck": "tsc --noEmit", "test": "vitest run" }, "dependencies": { - "@unicitylabs/sphere-sdk": "0.13.1", + "@unicitylabs/sphere-sdk": "0.14.1", "ws": "^8.18.0" }, "devDependencies": { diff --git a/nodejs/src/mockSphere.ts b/nodejs/src/mockSphere.ts index 331af90..baf0de2 100644 --- a/nodejs/src/mockSphere.ts +++ b/nodejs/src/mockSphere.ts @@ -3,73 +3,68 @@ * * Lives in its own module so a test can construct a ConnectHost around it without importing * mock-wallet-server.ts, which starts a WebSocket server as a side effect of being imported. + * + * It mirrors the shape a REAL sphere-sdk 0.14 wallet hands to `ConnectHost`: `payments` is the + * payments-v2 facade (`assets()` / `tokens()` / `history()`), and `paymentsV2` is the same + * object — the deprecated alias the host reads to decide it is talking to a v2 wallet. + * + * The host only ever reads those three members plus `identity`, `resolve`, `on` and + * `communications`, so this mock implements exactly those. Money movement never reaches the + * facade in a Connect wallet: it arrives as an intent and is answered by `onIntent`. + * + * Wire mapping the host performs on top of this (dApps see it, so it's worth knowing): + * sphere_getBalance / sphere_getAssets -> assets(coinId?) + * sphere_getFiatBalance -> sum of assets()[].fiatValueUsd + * sphere_getTokens -> tokens({ coinId? }) + * sphere_getHistory -> history() pages, flattened to one entry array */ const now = Date.now(); -export const mockSphere = { - networkId: 4, - identity: { - chainPubkey: '02abc123def456789012345678901234567890123456789012345678901234567890', - directAddress: 'DIRECT://abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', - nametag: 'alice', - }, - payments: { - getBalance: (_coinId?: string) => [ - { - coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', decimals: 8, - totalAmount: '500000000', tokenCount: 3, - confirmedAmount: '300000000', unconfirmedAmount: '200000000', - confirmedTokenCount: 2, unconfirmedTokenCount: 1, - }, - { - coinId: 'USDU', symbol: 'USDU', name: 'USD Unicity', decimals: 6, - totalAmount: '1000000000', tokenCount: 1, - confirmedAmount: '1000000000', unconfirmedAmount: '0', - confirmedTokenCount: 1, unconfirmedTokenCount: 0, - }, - ], - getAssets: async (_coinId?: string) => [ - { - coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', decimals: 8, - iconUrl: null, totalAmount: '500000000', tokenCount: 3, - confirmedAmount: '300000000', unconfirmedAmount: '200000000', - confirmedTokenCount: 2, unconfirmedTokenCount: 1, - priceUsd: 0.051, priceEur: 0.047, change24h: 2.4, - fiatValueUsd: 2.55, fiatValueEur: 2.35, - }, - { - coinId: 'USDU', symbol: 'USDU', name: 'USD Unicity', decimals: 6, - iconUrl: null, totalAmount: '1000000000', tokenCount: 1, - confirmedAmount: '1000000000', unconfirmedAmount: '0', - confirmedTokenCount: 1, unconfirmedTokenCount: 0, - priceUsd: 1.0, priceEur: 0.92, change24h: 0.01, - fiatValueUsd: 1000.0, fiatValueEur: 920.0, - }, - ], - getFiatBalance: async () => 1002.55, - getTokens: (_filter?: unknown) => [ - { - id: 'tok-abc123def456', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', - decimals: 8, amount: '200000000', status: 'confirmed', - createdAt: now - 86400000, updatedAt: now - 3600000, - }, - { - id: 'tok-789ghi012jkl', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', - decimals: 8, amount: '100000000', status: 'confirmed', - createdAt: now - 43200000, updatedAt: now - 7200000, - }, - { - id: 'tok-mno345pqr678', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', - decimals: 8, amount: '200000000', status: 'submitted', - createdAt: now - 1800000, updatedAt: now - 600000, - }, - { - id: 'tok-stu901vwx234', coinId: 'USDU', symbol: 'USDU', name: 'USD Unicity', - decimals: 6, amount: '1000000000', status: 'confirmed', - createdAt: now - 172800000, updatedAt: now - 86400000, - }, - ], - getHistory: () => [ +const payments = { + assets: async (_coinId?: string) => [ + { + coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', decimals: 8, + iconUrl: null, totalAmount: '500000000', tokenCount: 3, + confirmedAmount: '300000000', unconfirmedAmount: '200000000', + confirmedTokenCount: 2, unconfirmedTokenCount: 1, + priceUsd: 0.051, priceEur: 0.047, change24h: 2.4, + fiatValueUsd: 2.55, fiatValueEur: 2.35, + }, + { + coinId: 'USDU', symbol: 'USDU', name: 'USD Unicity', decimals: 6, + iconUrl: null, totalAmount: '1000000000', tokenCount: 1, + confirmedAmount: '1000000000', unconfirmedAmount: '0', + confirmedTokenCount: 1, unconfirmedTokenCount: 0, + priceUsd: 1.0, priceEur: 0.92, change24h: 0.01, + fiatValueUsd: 1000.0, fiatValueEur: 920.0, + }, + ], + tokens: (_filter?: { coinId?: string }) => [ + { + id: 'tok-abc123def456', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', + decimals: 8, amount: '200000000', status: 'confirmed', + createdAt: now - 86400000, updatedAt: now - 3600000, + }, + { + id: 'tok-789ghi012jkl', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', + decimals: 8, amount: '100000000', status: 'confirmed', + createdAt: now - 43200000, updatedAt: now - 7200000, + }, + { + id: 'tok-mno345pqr678', coinId: 'UCT', symbol: 'UCT', name: 'Unicity Token', + decimals: 8, amount: '200000000', status: 'submitted', + createdAt: now - 1800000, updatedAt: now - 600000, + }, + { + id: 'tok-stu901vwx234', coinId: 'USDU', symbol: 'USDU', name: 'USD Unicity', + decimals: 6, amount: '1000000000', status: 'confirmed', + createdAt: now - 172800000, updatedAt: now - 86400000, + }, + ], + // `history()` is PAGED in v2. One page is enough here: `more: false` plus a null + // cursor is what ends the host's "fetch every page, flatten" loop. + history: async (_page?: { before?: string; limit?: number }) => ({ + entries: [ { id: 'h1', type: 'SENT', amount: '100000000', coinId: 'UCT', symbol: 'UCT', timestamp: now - 3600000, recipientNametag: 'bob', transferId: 'xfer-001', @@ -84,7 +79,22 @@ export const mockSphere = { timestamp: now - 86400000, }, ], + more: false, + cursor: null, + }), +}; + +export const mockSphere = { + networkId: 4, + identity: { + chainPubkey: '02abc123def456789012345678901234567890123456789012345678901234567890', + directAddress: 'DIRECT://abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', + nametag: 'alice', }, + payments, + // The deprecated alias a 0.14 Sphere still exposes. ConnectHost reads it to detect a v2 + // wallet and route sphere_getBalance/-Assets/-Tokens/-History through the facade above. + paymentsV2: payments, resolve: async (identifier: string) => ({ nametag: identifier.replace('@', ''), chainPubkey: '03fedcba09876543210fedcba09876543210fedcba09876543210fedcba0987654321', From 0adf2886de47f89e154752696638997dbdb77d66 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Fri, 7 Aug 2026 19:35:41 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(browser):=20colour=20transfer:updated?= =?UTF-8?q?=20by=20outcome=20=E2=80=94=20a=20failed=20send=20must=20never?= =?UTF-8?q?=20look=20green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- .../components/events/EventLogPanel.test.ts | 27 ++++++++++++++++++- .../src/components/events/EventLogPanel.tsx | 26 +++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/browser/src/components/events/EventLogPanel.test.ts b/browser/src/components/events/EventLogPanel.test.ts index f337a82..64f8655 100644 --- a/browser/src/components/events/EventLogPanel.test.ts +++ b/browser/src/components/events/EventLogPanel.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { AUTO_PUSHED_EVENTS } from '@unicitylabs/sphere-sdk/connect'; -import { ALL_EVENTS, EVENT_COLORS } from './EventLogPanel'; +import { ALL_EVENTS, EVENT_COLORS, badgeFor } from './EventLogPanel'; describe('EventLogPanel event list', () => { // The host pushes these unconditionally. If the log omits one, the demo silently hides the @@ -67,3 +67,28 @@ describe('EventLogPanel event list', () => { } }); }); + +describe('badgeFor — transfer:updated is the COMBINED outcome event', () => { + it('colours a FAILED transfer red, never the success green (the name alone lies)', () => { + const failed = badgeFor('transfer:updated', { id: 't1', status: 'failed', error: 'insufficient balance' }); + expect(failed).toContain('red'); + expect(failed).not.toBe(EVENT_COLORS['transfer:updated']); + }); + + it('colours a still-converging transfer amber (deliveryPending or pending), not green', () => { + expect(badgeFor('transfer:updated', { status: 'confirmed', deliveryPending: true })).toContain('amber'); + expect(badgeFor('transfer:updated', { status: 'pending' })).toContain('amber'); + }); + + it('keeps the success colour for a delivered transfer and for every other event', () => { + expect(badgeFor('transfer:updated', { status: 'delivered', deliveryPending: false })) + .toBe(EVENT_COLORS['transfer:updated']); + expect(badgeFor('transfer:incoming', {})).toBe(EVENT_COLORS['transfer:incoming']); + expect(badgeFor('unknown:event', {})).toBe('bg-white/3 text-white/55'); + }); + + it('does not crash on a null/undefined payload', () => { + expect(badgeFor('transfer:updated', null)).toBe(EVENT_COLORS['transfer:updated']); + expect(badgeFor('transfer:updated', undefined)).toBe(EVENT_COLORS['transfer:updated']); + }); +}); diff --git a/browser/src/components/events/EventLogPanel.tsx b/browser/src/components/events/EventLogPanel.tsx index 3e95c6e..4e110fd 100644 --- a/browser/src/components/events/EventLogPanel.tsx +++ b/browser/src/components/events/EventLogPanel.tsx @@ -66,6 +66,11 @@ export const ALL_EVENTS = [ 'groupchat:connection', ]; +/** + * `transfer:updated` is the COMBINED outcome event (0.14): it replaced + * transfer:confirmed AND transfer:failed AND transfer:delivery_pending, so the + * name alone says nothing about whether money moved — see badgeFor(). + */ export const EVENT_COLORS: Record = { // Transfers 'transfer:incoming': 'bg-green-500/15 text-green-400', @@ -108,6 +113,25 @@ export const EVENT_COLORS: Record = { 'groupchat:connection': 'bg-teal-500/15 text-teal-400', }; +/** + * Badge style for one logged event. The name alone is not enough for + * `transfer:updated`: a FAILED send carries the same event name as a confirmed + * one, and green-for-failed is the miscolour that actively misleads — a dApp + * dev reads this log to answer "did it go through?". Failed → red; + * still-converging (deliveryPending / pending) → amber; else the table colour. + */ +export function badgeFor(event: string, data: unknown): string { + const fallback = EVENT_COLORS[event] ?? 'bg-white/3 text-white/55'; + if (event !== 'transfer:updated') return fallback; + const result = data as { status?: unknown; deliveryPending?: unknown } | null | undefined; + if (result?.status === 'failed') return 'bg-red-500/15 text-red-400'; + if (result?.deliveryPending === true || result?.status === 'pending') { + return 'bg-amber-500/15 text-amber-400'; + } + return fallback; +} + + let nextId = 0; export function EventLogPanel({ on }: Props) { @@ -169,7 +193,7 @@ export function EventLogPanel({ on }: Props) {
) : ( filtered.map((entry) => { - const badgeStyle = EVENT_COLORS[entry.event] ?? 'bg-white/3 text-white/55'; + const badgeStyle = badgeFor(entry.event, entry.data); return (