@unicitylabs/sphere-sdk 0.17.6 → 0.18.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/connect/chunks/{chunk-T4DZCXWS.js → chunk-FGQ6CKKK.js} +2 -2
  2. package/dist/connect/chunks/{chunk-T4DZCXWS.js.map → chunk-FGQ6CKKK.js.map} +1 -1
  3. package/dist/connect/chunks/{chunk-BRYRG2TL.js → chunk-JYBI36GK.js} +2 -2
  4. package/dist/connect/chunks/{chunk-MA32MTCD.js → chunk-OAAY2JEA.js} +2 -2
  5. package/dist/connect/index.cjs +1 -1
  6. package/dist/connect/index.cjs.map +1 -1
  7. package/dist/connect/index.js +3 -3
  8. package/dist/connect/internal/host.js +2 -2
  9. package/dist/core/index.cjs +304 -47
  10. package/dist/core/index.cjs.map +1 -1
  11. package/dist/core/index.d.cts +392 -320
  12. package/dist/core/index.d.ts +392 -320
  13. package/dist/core/index.js +304 -47
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/impl/browser/connect/index.cjs +1 -1
  16. package/dist/impl/browser/connect/index.cjs.map +1 -1
  17. package/dist/impl/browser/connect/index.js +2 -2
  18. package/dist/impl/wallet-api-v2/index.cjs +2 -0
  19. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  20. package/dist/impl/wallet-api-v2/index.d.cts +30 -30
  21. package/dist/impl/wallet-api-v2/index.d.ts +30 -30
  22. package/dist/impl/wallet-api-v2/index.js +2 -0
  23. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  24. package/dist/index.cjs +304 -47
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +279 -154
  27. package/dist/index.d.ts +279 -154
  28. package/dist/index.js +304 -47
  29. package/dist/index.js.map +1 -1
  30. package/dist/modules/payments-v2/index.cjs +249 -28
  31. package/dist/modules/payments-v2/index.cjs.map +1 -1
  32. package/dist/modules/payments-v2/index.d.cts +252 -166
  33. package/dist/modules/payments-v2/index.d.ts +252 -166
  34. package/dist/modules/payments-v2/index.js +249 -28
  35. package/dist/modules/payments-v2/index.js.map +1 -1
  36. package/dist/token-engine/index.cjs +455 -427
  37. package/dist/token-engine/index.cjs.map +1 -1
  38. package/dist/token-engine/index.d.cts +26 -1
  39. package/dist/token-engine/index.d.ts +26 -1
  40. package/dist/token-engine/index.js +455 -427
  41. package/dist/token-engine/index.js.map +1 -1
  42. package/package.json +1 -1
  43. /package/dist/connect/chunks/{chunk-BRYRG2TL.js.map → chunk-JYBI36GK.js.map} +0 -0
  44. /package/dist/connect/chunks/{chunk-MA32MTCD.js.map → chunk-OAAY2JEA.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../connect/index.ts","../../core/logger.ts","../../core/errors.ts","../../connect/semver.ts","../../constants.ts","../../connect/protocol.ts","../../connect/compatibility.ts","../../connect/version.ts","../../connect/permissions.ts","../../connect/host/payments-compat.ts","../../connect/host/host-state.ts","../../connect/host/WalletSnapshot.ts","../../connect/host/InFlightRegistry.ts","../../connect/host/ConnectHost.ts","../../connect/client/ConnectClient.ts","../../core/base64.ts","../../connect/nft-wire.ts"],"sourcesContent":["/**\n * Sphere Connect — Core (transport-agnostic)\n *\n * Host (wallet side):\n * import { ConnectHost } from '@unicitylabs/sphere-sdk/connect';\n *\n * Client (dApp side):\n * import { ConnectClient } from '@unicitylabs/sphere-sdk/connect';\n */\n\nexport { ConnectHost } from './host/ConnectHost';\nexport { ConnectClient, ConnectError } from './client/ConnectClient';\n\n// Protocol\nexport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n HOST_READY_TYPE,\n HOST_READY_TIMEOUT,\n RPC_METHODS,\n INTENT_ACTIONS,\n ERROR_CODES,\n WALLET_EVENTS,\n AUTO_PUSHED_EVENTS,\n isAutoPushedEvent,\n SPHERE_NETWORKS,\n isSphereConnectMessage,\n createRequestId,\n} from './protocol';\n\nexport type {\n RpcMethod,\n IntentAction,\n ErrorCode,\n WalletEvent,\n SphereRpcRequest,\n SphereRpcResponse,\n SphereIntentRequest,\n SphereIntentResult,\n SphereEventMessage,\n SphereHandshake,\n SphereRpcError,\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\n WalletLockedData,\n WalletLockedPayload,\n WalletUnlockedPayload,\n WalletDisconnectedPayload,\n WalletIdentityChangedPayload,\n NetworkInfo,\n} from './protocol';\n\n// Permissions\nexport {\n PERMISSION_SCOPES,\n ALL_PERMISSIONS,\n DEFAULT_PERMISSIONS,\n METHOD_PERMISSIONS,\n INTENT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n validatePermissions,\n} from './permissions';\n\nexport type { PermissionScope } from './permissions';\n\n// NFT wire form (mint_nft intent)\nexport { nftContentToWire, nftContentFromWire } from './nft-wire';\nexport type {\n MintNftIntentParams,\n MintNftIntentResult,\n WireNftContent,\n WireNftMedia,\n WireNftMetadata,\n} from './nft-wire';\nexport type {\n NftAttribute,\n NftContent,\n NftLink,\n NftMedia,\n NftMediaRef,\n NftMetadata,\n} from '../token-engine/nft-payload';\n\n// Types\nexport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n ConnectClientConfig,\n ConnectResult,\n ConnectEventHandler,\n WalletState,\n LockedRequestContext,\n IntentContext,\n} from './types';\n","/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * SDK Error Types\n *\n * Structured error codes for programmatic error handling in UI.\n * UI can switch on error.code to show appropriate user-facing messages.\n *\n * Errors thrown by provider code (the `./impl/*` bundles) are a different `SphereError` class\n * copy, so `instanceof SphereError` / `isSphereError()` is false for them: read `code` structurally.\n *\n * @example\n * ```ts\n * import { PartialSendConflictError, isPossiblyCommittedSendOutcome } from '@unicitylabs/sphere-sdk';\n *\n * try {\n * await sphere.payments.send({ recipient: '@alice', amount: '1000000', coinId });\n * } catch (err) {\n * if (err instanceof PartialSendConflictError) {\n * // Part of the amount already left the wallet and is final. Only err.remainingAmount is still owed:\n * // if you pay it, do it as a NEW send of exactly that amount, never the original amount.\n * showToast(`Partly sent: ${err.remainingAmount} base units were not sent`);\n * } else if (isPossiblyCommittedSendOutcome(err)) {\n * // The money may already have left the wallet: never call send() again for this payment.\n * showToast('Sent, waiting for confirmation');\n * } else {\n * switch ((err as { code?: unknown } | null)?.code) {\n * case 'SEND_INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;\n * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;\n * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;\n * default: showToast(err instanceof Error ? err.message : String(err));\n * }\n * }\n * }\n * ```\n */\n\nexport type SphereErrorCode =\n | 'NOT_INITIALIZED'\n | 'ALREADY_INITIALIZED'\n | 'INVALID_CONFIG'\n | 'INVALID_IDENTITY'\n // Not thrown by the SDK: a send short of funds throws SEND_INSUFFICIENT_BALANCE.\n | 'INSUFFICIENT_BALANCE'\n | 'INVALID_RECIPIENT'\n | 'TRANSFER_FAILED'\n // The source state was consumed by a DIFFERENT transaction (lost race, not a\n // resume) — raised as TransferConflictError (token-engine/errors.ts, Part E.2).\n | 'TRANSFER_CONFLICT'\n // The op's certification is INDETERMINATE (submit accepted / proof fetch\n // inconclusive) — raised as ProofUnconfirmedError; keep the intent OPEN (#631).\n | 'CERTIFICATION_UNCONFIRMED'\n // Split burn checkpoint (sdk-changes E.4, sphere-sdk#501) — all keep-open (token-engine/errors.ts):\n // burn certified but the checkpoint could not be persisted (no mint submitted);\n | 'CHECKPOINT_PERSIST_FAILED'\n // a certified mint leaf has no reproducing stored bytes, or a stored checkpoint fails byte-binding;\n | 'SPLIT_CHECKPOINT_LOST'\n // a byte-bound checkpoint proof no longer verifies against the current trust base (validator rotation).\n | 'CHECKPOINT_TRUSTBASE_MISMATCH'\n | 'STORAGE_ERROR'\n // #665: the on-chain spend committed but the post-commit wallet-api mirror\n // sync (inventory apply / blob upload / save) failed. NOT a lost payment —\n // the intent is kept open and resume converges the mirror (idempotent apply,\n // #664). Surfaced distinctly so a UI can reassure (\"sent — wallet catching\n // up\") instead of showing a hard send failure.\n | 'SEND_SYNC_PENDING'\n // #677: a multi-leg send lost a source to a concurrent transfer AFTER ≥1\n // earlier leg had already certified on-chain AND been journaled/delivered.\n // The delivered value has irreversibly left the wallet, so this is NOT a\n // plain, re-sendable failure: surfacing a bare TransferConflictError makes\n // the caller re-send the FULL amount and pay the delivered leg twice. Raised\n // as PartialSendConflictError; the caller MUST re-plan ONLY the remainder\n // under a NEW transferId, never the whole amount.\n | 'SEND_PARTIALLY_COMPLETED'\n | 'TRANSPORT_ERROR'\n | 'AGGREGATOR_ERROR'\n | 'VALIDATION_ERROR'\n | 'INVALID_AMOUNT'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'DECRYPTION_ERROR'\n | 'MODULE_NOT_AVAILABLE'\n // #793: this Sphere was initialised with `walletApi: 'none'` — the explicit\n // messaging-only composition. Distinct from NOT_INITIALIZED, which is transient\n // (init in flight, mid address-switch, destroyed): this one never resolves.\n | 'PAYMENTS_NOT_COMPOSED'\n | 'SIGNING_ERROR'\n // Token Spend Queue error codes\n | 'SEND_QUEUE_TIMEOUT'\n | 'SEND_INSUFFICIENT_BALANCE'\n | 'SEND_RESERVATION_CANCELLED'\n | 'SEND_QUEUE_FULL'\n | 'MODULE_DESTROYED'\n | 'REENTRANT_GATE'\n // Invoice / Accounting error codes\n | 'INVOICE_NO_TARGETS'\n | 'INVOICE_INVALID_ADDRESS'\n | 'INVOICE_NO_ASSETS'\n | 'INVOICE_INVALID_ASSET'\n | 'INVOICE_INVALID_AMOUNT'\n | 'INVOICE_INVALID_COIN'\n | 'INVOICE_INVALID_NFT'\n | 'INVOICE_PAST_DUE_DATE'\n | 'INVOICE_DUPLICATE_ADDRESS'\n | 'INVOICE_DUPLICATE_COIN'\n | 'INVOICE_DUPLICATE_NFT'\n | 'INVOICE_MINT_FAILED'\n | 'INVOICE_INVALID_PROOF'\n | 'INVOICE_WRONG_TOKEN_TYPE'\n | 'INVOICE_INVALID_DATA'\n | 'INVOICE_ALREADY_EXISTS'\n | 'INVOICE_NOT_FOUND'\n | 'INVOICE_NOT_TARGET'\n | 'INVOICE_ALREADY_CLOSED'\n | 'INVOICE_ALREADY_CANCELLED'\n | 'INVOICE_ORACLE_REQUIRED'\n | 'INVOICE_TERMINATED'\n | 'INVOICE_INVALID_TARGET'\n | 'INVOICE_INVALID_ASSET_INDEX'\n | 'INVOICE_RETURN_EXCEEDS_BALANCE'\n | 'INVOICE_INVALID_DELIVERY_METHOD'\n | 'INVOICE_INVALID_REFUND_ADDRESS'\n | 'INVOICE_INVALID_CONTACT'\n | 'INVOICE_INVALID_ID'\n | 'INVOICE_TOO_MANY_TARGETS'\n | 'INVOICE_TOO_MANY_ASSETS'\n | 'INVOICE_MEMO_TOO_LONG'\n | 'INVOICE_TERMS_TOO_LARGE'\n | 'INVOICE_NOT_TERMINATED'\n | 'INVOICE_NOT_CANCELLED'\n | 'INVOICE_STORAGE_FAILED'\n | 'RATE_LIMITED'\n | 'COMMUNICATIONS_UNAVAILABLE'\n // Swap error codes\n | 'SWAP_INVALID_DEAL'\n | 'SWAP_INVALID_MANIFEST'\n | 'SWAP_NOT_FOUND'\n | 'SWAP_WRONG_STATE'\n | 'SWAP_RESOLVE_FAILED'\n | 'SWAP_DM_SEND_FAILED'\n | 'SWAP_ESCROW_REJECTED'\n | 'SWAP_DEPOSIT_FAILED'\n | 'SWAP_PAYOUT_VERIFICATION_FAILED'\n | 'SWAP_ALREADY_EXISTS'\n | 'SWAP_ALREADY_COMPLETED'\n | 'SWAP_ALREADY_CANCELLED'\n | 'SWAP_TIMEOUT'\n | 'SWAP_LIMIT_EXCEEDED'\n | 'SWAP_ALREADY_INITIALIZED'\n | 'SWAP_MODULE_DESTROYED'\n | 'SWAP_NOT_INITIALIZED';\n\nexport class SphereError extends Error {\n readonly code: SphereErrorCode;\n readonly cause?: unknown;\n /**\n * #441 deferred-paid linkage: the transferId of the possibly-committed send,\n * stamped by `sendOnce` before the error leaves so a payment-request consumer\n * can durably journal request→transfer and resolve 'paid' only when that\n * transfer actually completes (never optimistically). Present ONLY on\n * possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).\n */\n transferId?: string;\n\n constructor(message: string, code: SphereErrorCode, cause?: unknown) {\n super(message);\n this.name = 'SphereError';\n this.code = code;\n this.cause = cause;\n }\n}\n\n/**\n * #677: a send that PARTIALLY completed before losing a source to a concurrent\n * transfer. At least one earlier leg certified on-chain and was journaled for\n * delivery (its {@link committedTokenIds}) — that value has irreversibly left\n * the wallet — but a LATER leg raised `TransferConflictError` (a lost race), and\n * `send()` could NOT COMPLETE the {@link remainingAmount} from the remaining live\n * sources. This is the fallback after the internal remainder re-plan gave up —\n * either insufficient funds OR a transient/transport error during the re-plan\n * (the underlying reason is on `cause`), not necessarily an \"out of funds\" state.\n *\n * Distinct from `TransferConflictError` on purpose: a bare conflict makes the\n * caller re-send the FULL amount, paying the already-delivered leg a second\n * time. Catching THIS type (or `code === 'SEND_PARTIALLY_COMPLETED'`) tells the\n * caller/UI: the delivered legs are final, the conflicted intent stays OPEN\n * (resume converges its certified legs), and only the REMAINDER\n * ({@link remainingAmount}) may be re-planned — under a NEW transferId, never\n * re-sending the whole amount.\n *\n * NOT a subclass of `TransferConflictError` by design: existing conflict\n * handlers that re-send in full must NOT treat this as an ordinary conflict.\n */\nexport class PartialSendConflictError extends SphereError {\n /**\n * The FIRST partial attempt's transferId — its intent stays open for resume,\n * and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across\n * SEVERAL internal attempts before giving up, so {@link committedTokenIds} can\n * span MULTIPLE transferIds — they do NOT all map to this single id.\n */\n readonly transferId: string;\n /**\n * Source token ids whose spend already certified on-chain (and whose finished\n * output blob is journaled for delivery). Their value has already been\n * delivered; NEVER re-send these. May accumulate across several internal\n * remainder-re-plan attempts (so not all are journaled under {@link transferId}).\n */\n readonly committedTokenIds: readonly string[];\n /**\n * The still-undelivered portion (base units, decimal string) — `send()` could\n * not COMPLETE it from the remaining live sources (insufficient funds or a\n * transient error; see `cause`). Re-plan ONLY this amount, under a new\n * transferId; the delivered legs converge via the recipient's §6 claim.\n */\n readonly remainingAmount: string;\n\n constructor(\n message: string,\n transferId: string,\n committedTokenIds: readonly string[],\n remainingAmount: string,\n cause?: unknown,\n ) {\n super(message, 'SEND_PARTIALLY_COMPLETED', cause);\n this.name = 'PartialSendConflictError';\n this.transferId = transferId;\n this.committedTokenIds = [...committedTokenIds];\n this.remainingAmount = remainingAmount;\n }\n}\n\n/**\n * Error codes for a send outcome where money has (or may have) already left the\n * wallet on-chain, so the send is NOT a clean, re-sendable failure. A consumer\n * that persists a \"paid\" flag (e.g. a payment request) must keep the request\n * NON-payable on any of these — reverting to a re-payable state would double-pay\n * (#441). Covers the post-commit mirror-sync-pending / keep-open certification\n * states AND the partial-conflict outcome.\n */\nconst POSSIBLY_COMMITTED_SEND_CODES: ReadonlySet<SphereErrorCode> = new Set([\n 'SEND_SYNC_PENDING',\n 'CERTIFICATION_UNCONFIRMED',\n 'CHECKPOINT_PERSIST_FAILED',\n 'SPLIT_CHECKPOINT_LOST',\n 'CHECKPOINT_TRUSTBASE_MISMATCH',\n 'SEND_PARTIALLY_COMPLETED',\n]);\n\n/**\n * #441: true when a send failed with an outcome where the payment has (or may\n * have) irreversibly left the wallet — a post-commit sync-pending / keep-open\n * certification state, or a `PartialSendConflictError`. Such a send completes (or\n * has already partially completed) via resume/claim and MUST NOT be re-sent, so\n * a persisted \"paid\" state must stay non-payable. A `false` result is a clean\n * pre-commit failure — nothing left the wallet, safe to re-pay.\n */\nexport function isPossiblyCommittedSendOutcome(err: unknown): boolean {\n return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);\n}\n\n/**\n * Type guard to check if an error is a SphereError\n */\nexport function isSphereError(err: unknown): err is SphereError {\n return err instanceof SphereError;\n}\n","// connect/semver.ts\n// Tiny, dependency-free semver helpers for the Connect compatibility gate.\n\n/** MAJOR component of a semver string (e.g. '2.7.3' -> 2). NaN if unparseable. */\nexport function majorOf(v: string): number {\n return parseInt(String(v).split('.')[0], 10);\n}\n\n/**\n * Compare two semver strings, prerelease-aware. Returns -1 | 0 | 1.\n * A release outranks its own prerelease: compareSemver('1.2.3', '1.2.3-rc.1') === 1.\n */\nexport function compareSemver(a: string, b: string): number {\n const parse = (v: string) => {\n const [core, pre] = String(v).split('-', 2) as [string, string | undefined];\n const nums = core.split('.').map((n) => parseInt(n, 10) || 0);\n return { nums, pre };\n };\n const pa = parse(a);\n const pb = parse(b);\n for (let i = 0; i < 3; i++) {\n const d = (pa.nums[i] ?? 0) - (pb.nums[i] ?? 0);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n if (pa.pre === undefined && pb.pre === undefined) return 0;\n if (pa.pre === undefined) return 1; // release > prerelease\n if (pb.pre === undefined) return -1;\n const sa = pa.pre.split('.');\n const sb = pb.pre.split('.');\n for (let i = 0; i < Math.max(sa.length, sb.length); i++) {\n const x = sa[i];\n const y = sb[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n const nx = Number(x);\n const ny = Number(y);\n if (!Number.isNaN(nx) && !Number.isNaN(ny)) {\n if (nx !== ny) return nx < ny ? -1 : 1;\n } else if (x !== y) {\n return x < y ? -1 : 1;\n }\n }\n return 0;\n}\n","/**\n * SDK2 Constants\n * Default configuration values and storage keys\n */\n\n// =============================================================================\n// Storage Keys\n// =============================================================================\n\n/** Default prefix for all storage keys */\nexport const STORAGE_PREFIX = 'sphere_' as const;\n\n/**\n * Legacy key: Sphere's private decrypt() tries it only as a fallback for wallets written by old\n * SDK versions when no password is given. Nothing is encrypted with it; without a password the\n * mnemonic or master key is stored as plaintext.\n */\nexport const DEFAULT_ENCRYPTION_KEY = 'sphere-default-key' as const;\n\n/**\n * Global storage keys (one per wallet, no address index)\n * Final key format: sphere_{key}\n */\nexport const STORAGE_KEYS_GLOBAL = {\n /** Encrypted BIP39 mnemonic */\n MNEMONIC: 'mnemonic',\n /** Encrypted master private key */\n MASTER_KEY: 'master_key',\n /** BIP32 chain code */\n CHAIN_CODE: 'chain_code',\n /** HD derivation path (full path like m/44'/0'/0'/0/0) */\n DERIVATION_PATH: 'derivation_path',\n /** Base derivation path (like m/44'/0'/0' without chain/index) */\n BASE_PATH: 'base_path',\n /** Derivation mode: bip32, wif_hmac, legacy_hmac */\n DERIVATION_MODE: 'derivation_mode',\n /** Wallet source: mnemonic, file, unknown */\n WALLET_SOURCE: 'wallet_source',\n /** Wallet existence flag */\n WALLET_EXISTS: 'wallet_exists',\n /** Current active address index */\n CURRENT_ADDRESS_INDEX: 'current_address_index',\n /** Nametag cache per address (separate from tracked addresses registry) */\n ADDRESS_NAMETAGS: 'address_nametags',\n /** Active addresses registry (JSON: TrackedAddressesStorage) */\n TRACKED_ADDRESSES: 'tracked_addresses',\n /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */\n LAST_WALLET_EVENT_TS: 'last_wallet_event_ts',\n /** Last processed Nostr DM (gift-wrap) event timestamp (unix seconds), keyed per pubkey */\n LAST_DM_EVENT_TS: 'last_dm_event_ts',\n /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */\n GROUP_CHAT_RELAY_URL: 'group_chat_relay_url',\n /** Cached token registry JSON (fetched from remote) */\n TOKEN_REGISTRY_CACHE: 'token_registry_cache',\n /** Timestamp of last token registry cache update (ms since epoch) */\n TOKEN_REGISTRY_CACHE_TS: 'token_registry_cache_ts',\n /** Cached price data JSON (from CoinGecko or other provider) */\n PRICE_CACHE: 'price_cache',\n /** Timestamp of last price cache update (ms since epoch) */\n PRICE_CACHE_TS: 'price_cache_ts',\n} as const;\n\n/**\n * Per-address storage keys (one per derived address)\n * Final key format: sphere_{DIRECT_xxx_yyy}_{key}\n * Example: sphere_DIRECT_abc123_xyz789_pending_transfers\n *\n * Note: Token data is server custody (wallet-api backend), not here; the\n * payments vertical's durable client state self-prefixes `pv2g2:{network}:{pubkey}:`.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Transfer outbox for this address (pre-flip key name; kept as the network-scoping witness) */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Group chat: joined groups for this address */\n GROUP_CHAT_GROUPS: 'group_chat_groups',\n /** Group chat: messages for this address */\n GROUP_CHAT_MESSAGES: 'group_chat_messages',\n /** Group chat: members for this address */\n GROUP_CHAT_MEMBERS: 'group_chat_members',\n /** Group chat: processed event IDs for deduplication */\n GROUP_CHAT_PROCESSED_EVENTS: 'group_chat_processed_events',\n /** Auto-return settings (pre-flip key name; kept as the network-scoping witness) */\n AUTO_RETURN: 'auto_return',\n /** Auto-return dedup ledger (pre-flip key name; kept as the network-scoping witness) */\n AUTO_RETURN_LEDGER: 'auto_return_ledger',\n /** Per-swap key prefix (pre-flip key name; kept as the network-scoping witness) */\n SWAP_RECORD_PREFIX: 'swap:',\n} as const;\n\n/**\n * Per-address keys that are ALSO per-network: token/payment operational state. Mixing these\n * across networks is unsafe. Chat/identity per-address keys (CONVERSATIONS/MESSAGES/\n * GROUP_CHAT_*) are network-AGNOSTIC and deliberately NOT listed. The payments vertical\n * self-prefixes `pv2g2:{network}:{pubkey}:` and never rides this mechanism; the entries left\n * here are the pre-flip key names the network-isolation tests pin the provider behavior\n * with (P11 stage-2 note: the full isNetworkScopedAddressKey cut needs an owner call on\n * those tests first).\n */\nconst NETWORK_SCOPED_ADDRESS_KEYS: readonly string[] = [\n STORAGE_KEYS_ADDRESS.OUTBOX,\n STORAGE_KEYS_ADDRESS.AUTO_RETURN,\n STORAGE_KEYS_ADDRESS.AUTO_RETURN_LEDGER,\n];\n\n/** Composite per-address key prefixes (modules store `{addressId}_{prefix}{id}`) — per-network. */\nconst NETWORK_SCOPED_ADDRESS_PREFIXES: readonly string[] = [\n STORAGE_KEYS_ADDRESS.SWAP_RECORD_PREFIX, // 'swap:'\n 'inv_ledger:', // AccountingModule INV_LEDGER_PREFIX\n];\n\n/**\n * True if a storage key is a per-NETWORK token/payment key (so the storage provider adds the\n * network segment). Handles the bare form ('auto_return_ledger'), the module-built addressId\n * form ('DIRECT_a_b_auto_return_ledger'), and composites ('{addressId}_swap:{id}'). Chat/identity\n * keys return false — they remain per-address, network-agnostic.\n */\nexport function isNetworkScopedAddressKey(key: string): boolean {\n for (const k of NETWORK_SCOPED_ADDRESS_KEYS) {\n if (key === k || key.endsWith(`_${k}`)) return true;\n }\n for (const p of NETWORK_SCOPED_ADDRESS_PREFIXES) {\n if (key.startsWith(p) || key.includes(`_${p}`)) return true;\n }\n return false;\n}\n\n/**\n * Build a per-address storage key using address identifier\n * @param addressId - Short identifier for the address (e.g., first 8 chars of pubkey hash, or direct address hash)\n * @param key - The key from STORAGE_KEYS_ADDRESS\n * @returns Key in format: \"{addressId}_{key}\" e.g., \"a1b2c3d4_tokens\"\n */\nexport function getAddressStorageKey(addressId: string, key: string): string {\n return `${addressId}_${key}`;\n}\n\n/**\n * Create a readable address identifier from directAddress or chainPubkey\n * Format: DIRECT_first6_last6 (sanitized for filesystem/storage)\n * @param directAddress - The L3 direct address (DIRECT:xxx) or chainPubkey\n * @returns Sanitized identifier like \"DIRECT_abc123_xyz789\"\n */\nexport function getAddressId(directAddress: string): string {\n // Remove DIRECT:// or DIRECT: prefix if present\n let hash = directAddress;\n if (hash.startsWith('DIRECT://')) {\n hash = hash.slice(9);\n } else if (hash.startsWith('DIRECT:')) {\n hash = hash.slice(7);\n }\n // Format: DIRECT_first6_last6 (sanitized)\n const first = hash.slice(0, 6).toLowerCase();\n const last = hash.slice(-6).toLowerCase();\n return `DIRECT_${first}_${last}`;\n}\n\n// =============================================================================\n// Nostr Defaults\n// =============================================================================\n\n/**\n * Fallback Nostr relays, used only when no relay list is configured\n * (NostrTransportProvider / MultiAddressTransportMux). `relay.unicity.network`\n * was the v1 relay and no longer resolves, so it is not listed here.\n */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.nostr.band',\n] as const;\n\n/** Nostr event kinds used by SDK - must match @unicitylabs/nostr-js-sdk */\nexport const NOSTR_EVENT_KINDS = {\n /** NIP-04 encrypted direct message */\n DIRECT_MESSAGE: 4,\n /** Nametag binding (NIP-78 app-specific data) */\n NAMETAG_BINDING: 30078,\n /** Public broadcast */\n BROADCAST: 1,\n} as const;\n\n/**\n * NIP-29 Event Kinds for relay-based group chat\n * https://github.com/nostr-protocol/nips/blob/master/29.md\n */\nexport const NIP29_KINDS = {\n /** Chat message sent to group */\n CHAT_MESSAGE: 9,\n /** Thread root message */\n THREAD_ROOT: 11,\n /** Thread reply message */\n THREAD_REPLY: 12,\n /** User join request */\n JOIN_REQUEST: 9021,\n /** User leave request */\n LEAVE_REQUEST: 9022,\n /** Admin: add/update user */\n PUT_USER: 9000,\n /** Admin: remove user */\n REMOVE_USER: 9001,\n /** Admin: edit group metadata */\n EDIT_METADATA: 9002,\n /** Admin: delete event */\n DELETE_EVENT: 9005,\n /** Admin: create group */\n CREATE_GROUP: 9007,\n /** Admin: delete group */\n DELETE_GROUP: 9008,\n /** Admin: create invite code */\n CREATE_INVITE: 9009,\n /** Relay-signed group metadata */\n GROUP_METADATA: 39000,\n /** Relay-signed group admins */\n GROUP_ADMINS: 39001,\n /** Relay-signed group members */\n GROUP_MEMBERS: 39002,\n /** Relay-signed group roles */\n GROUP_ROLES: 39003,\n} as const;\n\n// =============================================================================\n// Aggregator (Oracle) Defaults\n// =============================================================================\n\n/**\n * Default aggregator request timeout (ms)\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n * There is no default aggregator URL: every live network names its own gateway.\n */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n\n// =============================================================================\n// Wallet Defaults\n// =============================================================================\n\n/** Default BIP32 base path (without chain/index) */\nexport const DEFAULT_BASE_PATH = \"m/44'/0'/0'\" as const;\n\n/** Default BIP32 derivation path (full path with chain/index) */\nexport const DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0` as const;\n\n/** Coin types */\nexport const COIN_TYPES = {\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Default token registry refresh interval (ms) — 1 hour */\nexport const TOKEN_REGISTRY_REFRESH_INTERVAL = 3_600_000;\n\n// =============================================================================\n// Network Defaults\n// =============================================================================\n\n/** Testnet Nostr relays */\nexport const TEST_NOSTR_RELAYS = [\n 'wss://nostr-relay.testnet.unicity.network',\n] as const;\n\n/** Default group chat relays (NIP-29 Zooid relay) */\nexport const DEFAULT_GROUP_RELAYS = [\n 'wss://sphere-relay.unicity.network',\n] as const;\n\n/**\n * Complete configuration for one network. Every field is required, so adding a\n * network (or a field) that is missing any of these is a COMPILE error at the\n * NETWORKS literal below (via `satisfies`) — a half-configured network can never\n * be defined silently.\n */\nexport interface NetworkConfig {\n readonly name: string;\n readonly aggregatorUrl: string;\n readonly nostrRelays: readonly string[];\n readonly groupRelays: readonly string[];\n readonly tokenRegistryUrl: string;\n /** The NFT vessel token type every NFT mints under (#785), 64 lowercase hex: the single\n * `non-fungible` entry of this network's unicity-ids registry. */\n readonly nftTokenType: string;\n /** Canonical numeric network id (= RootTrustBase.networkId; testnet2 = 4).\n * Optional: only set for networks that have a live v2 trust base. */\n readonly networkId?: number;\n}\n\n/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n networkId: 1,\n // v3 state-transition gateway (networkId 1 comes from the trust base). apiKey is env-injected;\n // unlike testnet2's, a mainnet gateway key is a SECRET — never commit one.\n aggregatorUrl: 'https://gateway.mainnet.unicity.network',\n // Mainnet has no relay of its own yet — it shares the testnet relay until one is stood up.\n // Consequence while shared: nametag bindings for both networks live in ONE namespace, and\n // since bindings carry no network the cross-network recipient guard can only signal (#734).\n nostrRelays: TEST_NOSTR_RELAYS,\n groupRelays: DEFAULT_GROUP_RELAYS,\n // Published, but currently only the non-fungible base token type — no fungible\n // coins yet, so those still miss the registry (decimals 0, symbol falls back to\n // six hex chars). Presentation only: the money path treats coinId as opaque bytes.\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.mainnet.json',\n nftTokenType: '9f190eea6c8d7e1e564c35feb4c289add78be4bedb81bb77fe265e926e5493f4',\n },\n // v1 cutover: 'testnet' now POINTS AT TESTNET2 (the v2 gateway network). The\n // old goggregator testnet spoke the removed v1 protocol — a v2 engine cannot\n // run against it. 'testnet2' stays as an alias of the same configuration.\n testnet: {\n name: 'Testnet',\n networkId: 4,\n // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.\n aggregatorUrl: 'https://gateway.testnet2.unicity.network',\n nostrRelays: TEST_NOSTR_RELAYS, // reuse testnet infra (shared relays/ipfs)\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json',\n nftTokenType: '971a26eef0e3aeb22bd3e7d44c47ce963400037e8df42b50d4d44e1589f83826',\n },\n testnet2: {\n name: 'Testnet',\n networkId: 4,\n // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.\n aggregatorUrl: 'https://gateway.testnet2.unicity.network',\n nostrRelays: TEST_NOSTR_RELAYS, // reuse testnet infra (shared relays/ipfs)\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json',\n nftTokenType: '971a26eef0e3aeb22bd3e7d44c47ce963400037e8df42b50d4d44e1589f83826',\n },\n} as const satisfies Record<string, NetworkConfig>;\n\nexport type NetworkType = keyof typeof NETWORKS;\n\n/**\n * A network descriptor — the canonical way to identify a Unicity network across\n * the SDK and the Connect protocol. `id` is the canonical key (= RootTrustBase\n * networkId, analogous to an EIP-155 chainId); `name` is human-readable metadata.\n * Richer fields (gateway/symbol/explorer/icon) and switch/add-network are deferred.\n */\nexport interface NetworkInfo {\n readonly id: number;\n readonly name?: string;\n}\n\n/**\n * Registry of known networks for dApps and wallets. Single-sourced from NETWORKS\n * so it cannot drift. Use as `network: SPHERE_NETWORKS.testnet2`. Custom networks\n * are the same shape: `network: { id, name }`. Only live v2 networks appear here;\n * the legacy `testnet` alias is intentionally not surfaced.\n */\nexport const SPHERE_NETWORKS = {\n mainnet: { id: NETWORKS.mainnet.networkId as number, name: 'mainnet' },\n testnet2: { id: NETWORKS.testnet2.networkId as number, name: 'testnet2' },\n} as const satisfies Record<string, NetworkInfo>;\n\n// =============================================================================\n// Timeouts & Limits\n// =============================================================================\n\n/** Default timeouts (ms) */\nexport const TIMEOUTS = {\n /** WebSocket connection timeout */\n WEBSOCKET_CONNECT: 10000,\n /** Nostr relay reconnect delay */\n NOSTR_RECONNECT_DELAY: 3000,\n /** Max reconnect attempts */\n MAX_RECONNECT_ATTEMPTS: 5,\n /** Proof polling interval */\n PROOF_POLL_INTERVAL: 1000,\n /** Sync interval */\n SYNC_INTERVAL: 60000,\n} as const;\n\n// =============================================================================\n// Sphere Connect\n// =============================================================================\n\n/** Signal sent by wallet popup to dApp when ConnectHost is ready */\nexport const HOST_READY_TYPE = 'sphere-connect:host-ready' as const;\n\n/** Default timeout (ms) for waiting for the host-ready signal */\nexport const HOST_READY_TIMEOUT = 30_000;\n\n/** Validation limits */\nexport const LIMITS = {\n /** Min nametag length */\n NAMETAG_MIN_LENGTH: 3,\n /** Max nametag length */\n NAMETAG_MAX_LENGTH: 20,\n /** Max memo length */\n MEMO_MAX_LENGTH: 500,\n /** Max message length */\n MESSAGE_MAX_LENGTH: 10000,\n} as const;\n","/**\n * Sphere Connect Protocol\n * JSON-RPC-like message types for wallet ↔ dApp communication.\n */\n\nimport { majorOf } from './semver';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const SPHERE_CONNECT_NAMESPACE = 'sphere-connect';\nexport const SPHERE_CONNECT_VERSION = '2.3'; // Connect protocol version (semver MAJOR.MINOR)\n\n// Default npm-SDK floor a host enforces at the handshake (0.14.1 = the P11 flip:\n// the v1 payments era is gone; pre-flip ConnectClients expect a wallet that no\n// longer exists). '-0' admits every 0.14.1 prerelease (compareSemver: release >\n// prerelease). Override via ConnectHostConfig.minSdkVersion; the claim is\n// compatibility hygiene, not security — a hostile client can lie about it.\nexport const DEFAULT_MIN_CLIENT_SDK_VERSION = '0.14.1-0';\n\nexport { HOST_READY_TYPE, HOST_READY_TIMEOUT, SPHERE_NETWORKS } from '../constants';\n// Import for local use (e.g. SphereHandshake.network) AND re-export for connect consumers.\n// A bare `export type { NetworkInfo } from '../constants'` would re-export without bringing\n// the name into this module's scope, breaking the local references (TS2304).\nimport type { NetworkInfo } from '../constants';\nexport type { NetworkInfo };\n\n// =============================================================================\n// RPC Method Names (query — return data, no UI)\n// =============================================================================\n\nexport const RPC_METHODS = {\n GET_IDENTITY: 'sphere_getIdentity',\n GET_BALANCE: 'sphere_getBalance',\n GET_ASSETS: 'sphere_getAssets',\n GET_FIAT_BALANCE: 'sphere_getFiatBalance',\n GET_TOKENS: 'sphere_getTokens',\n GET_HISTORY: 'sphere_getHistory',\n RESOLVE: 'sphere_resolve',\n SUBSCRIBE: 'sphere_subscribe',\n UNSUBSCRIBE: 'sphere_unsubscribe',\n DISCONNECT: 'sphere_disconnect',\n GET_CONVERSATIONS: 'sphere_getConversations',\n GET_MESSAGES: 'sphere_getMessages',\n GET_DM_UNREAD_COUNT: 'sphere_getDMUnreadCount',\n MARK_AS_READ: 'sphere_markAsRead',\n} as const;\n\nexport type RpcMethod = (typeof RPC_METHODS)[keyof typeof RPC_METHODS];\n\n// =============================================================================\n// Intent Action Names (open wallet UI, require user confirmation)\n// =============================================================================\n\nexport const INTENT_ACTIONS = {\n SEND: 'send',\n DM: 'dm',\n PAYMENT_REQUEST: 'payment_request',\n RECEIVE: 'receive',\n SIGN_MESSAGE: 'sign_message',\n MINT: 'mint',\n // #777: params { to, tokenId, memo? }. Named NFT rather than 'send_token':\n // coins are tokens too, so 'token' does not say which kind moves.\n SEND_NFT: 'send_nft',\n // Connect 2.3: params MintNftIntentParams, result MintNftIntentResult (connect/nft-wire.ts).\n MINT_NFT: 'mint_nft',\n} as const;\n\nexport type IntentAction = (typeof INTENT_ACTIONS)[keyof typeof INTENT_ACTIONS];\n\n// =============================================================================\n// Error Codes\n// =============================================================================\n\nexport const ERROR_CODES = {\n // Standard JSON-RPC\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n\n // Sphere Connect (4xxx)\n NOT_CONNECTED: 4001,\n PERMISSION_DENIED: 4002,\n USER_REJECTED: 4003,\n SESSION_EXPIRED: 4004,\n ORIGIN_BLOCKED: 4005,\n RATE_LIMITED: 4006,\n UNSUPPORTED_PROTOCOL_VERSION: 4007, // Connect MAJOR mismatch (incompatible era)\n INCOMPATIBLE_NETWORK: 4008, // dApp targets a different network than the wallet\n // Wallet locked; THE SESSION IS STILL ALIVE. A QUERY may be retried after wallet:unlocked.\n // An INTENT already delegated to the wallet is NEVER answered with this code — it gets\n // INTENT_OUTCOME_UNKNOWN (4201) instead, because a retry could double-spend.\n WALLET_LOCKED: 4009,\n INSUFFICIENT_BALANCE: 4100,\n INVALID_RECIPIENT: 4101,\n TRANSFER_FAILED: 4102,\n INTENT_CANCELLED: 4200,\n /**\n * The intent was DELEGATED to the wallet and the host lost track of the answer — a host\n * deadline fired, or the wallet locked / logged out mid-flight. **The outcome is UNKNOWN:\n * the money may or may not have moved.**\n *\n * A dApp MUST NOT retry on this code. Reconcile out of band (poll the recipient, the\n * aggregator, or your own backend) and only then decide.\n *\n * This code exists because every other answer would be a lie. `INTENT_CANCELLED` (4200)\n * asserts the user declined and nothing happened; `WALLET_LOCKED` (4009) invites a retry\n * after the unlock. Sending either for an intent the wallet had already submitted is how a\n * paid-but-not-credited order — and then a double spend on retry — happens.\n */\n INTENT_OUTCOME_UNKNOWN: 4201,\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\n/** `data` carried by every WALLET_LOCKED (4009) refusal. */\nexport interface WalletLockedData {\n /** Discriminator. Always the literal 'locked' — reserved for future refusal reasons. */\n readonly reason: 'locked';\n /**\n * Whether the wallet's own unlock surface is currently on screen.\n * DECLARED in the fail-fast release and always absent there; POPULATED in the resume\n * release from ConnectHostConfig.unlockSurface(), evaluated at refusal time (visibility\n * changes over time, so a static value would lie). Feeds\n * ConnectClientConfig.onWalletAttention.\n */\n readonly unlockSurface?: 'visible' | 'background';\n}\n\n// =============================================================================\n// Message Types\n// =============================================================================\n\ninterface SphereMessageBase {\n readonly ns: typeof SPHERE_CONNECT_NAMESPACE;\n readonly v: typeof SPHERE_CONNECT_VERSION;\n}\n\n/** Query request: dApp → Wallet */\nexport interface SphereRpcRequest extends SphereMessageBase {\n readonly type: 'request';\n readonly id: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\n/** Query response: Wallet → dApp */\nexport interface SphereRpcResponse extends SphereMessageBase {\n readonly type: 'response';\n readonly id: string;\n readonly result?: unknown;\n readonly error?: SphereRpcError;\n}\n\n/** Intent request: dApp → Wallet (opens wallet UI) */\nexport interface SphereIntentRequest extends SphereMessageBase {\n readonly type: 'intent';\n readonly id: string;\n readonly action: string;\n readonly params: Record<string, unknown>;\n}\n\n/** Intent result: Wallet → dApp (after user action) */\nexport interface SphereIntentResult extends SphereMessageBase {\n readonly type: 'intent_result';\n readonly id: string;\n readonly result?: unknown;\n readonly error?: SphereRpcError;\n}\n\n/** Event push: Wallet → dApp (unsolicited) */\nexport interface SphereEventMessage extends SphereMessageBase {\n readonly type: 'event';\n readonly event: string;\n readonly data: unknown;\n}\n\n/** Handshake: bidirectional */\nexport interface SphereHandshake extends SphereMessageBase {\n readonly type: 'handshake';\n readonly direction: 'request' | 'response';\n readonly permissions: string[];\n readonly dapp?: DAppMetadata;\n readonly sessionId?: string;\n readonly identity?: PublicIdentity;\n /** If true, wallet must NOT open any approval UI. Immediately reject if origin is not already approved. */\n readonly silent?: boolean;\n /** request: dApp target network; response: wallet active network */\n readonly network?: NetworkInfo;\n /** Informational: the dApp's npm SDK version. */\n readonly sdkVersion?: string;\n /** Response: structured rejection reason when the gate refuses the connection. */\n readonly error?: SphereRpcError;\n /** Response: non-fatal deprecation notice (does not block the connection). */\n readonly warning?: SphereRpcError;\n /** Response only: the wallet is LOCKED and the session is ALIVE. The dApp is connected\n * and must not re-handshake; it will receive `wallet:unlocked` on the same session.\n * Additive and safe for old clients: handleHandshakeResponse reads only sessionId,\n * permissions, identity, network, warning and error and ignores unknown fields. */\n readonly locked?: boolean;\n}\n\nexport interface SphereRpcError {\n readonly code: number;\n readonly message: string;\n readonly data?: unknown;\n}\n\nexport type SphereConnectMessage =\n | SphereRpcRequest\n | SphereRpcResponse\n | SphereIntentRequest\n | SphereIntentResult\n | SphereEventMessage\n | SphereHandshake;\n\n// =============================================================================\n// Shared Types\n// =============================================================================\n\nexport interface DAppMetadata {\n readonly name: string;\n readonly description?: string;\n readonly icon?: string;\n readonly url: string;\n}\n\nexport interface PublicIdentity {\n readonly chainPubkey: string;\n readonly directAddress?: string;\n readonly nametag?: string;\n}\n\n// =============================================================================\n// Wallet-initiated Events (pushed automatically by host, no subscription needed)\n// =============================================================================\n\n/**\n * Events that ConnectHost pushes proactively to connected dApps.\n * dApps can listen with client.on(WALLET_EVENTS.LOCKED, handler) etc.\n * No sphere_subscribe call needed — host sends these unconditionally.\n */\nexport const WALLET_EVENTS = {\n /** Wallet is LOCKED — the session is STILL ALIVE. Requests are answered\n * WALLET_LOCKED (4009) until `wallet:unlocked`. The dApp must NOT disconnect,\n * must NOT clear its sessionId, and must NOT re-handshake.\n * Payload: {@link WalletLockedPayload}. Pushed by ConnectHost.setLocked() and\n * immediately after a handshake response carrying `locked: true`. */\n LOCKED: 'wallet:locked',\n /** Wallet was unlocked — the SAME session continues: no re-handshake, no re-approval,\n * no re-subscribe (the host re-arms the dApp's subscriptions before pushing this).\n * Payload: {@link WalletUnlockedPayload} — carries the CURRENT identity, which may\n * differ from the one the dApp connected with. Pushed by ConnectHost.updateSphere()\n * on the locked -> live edge only. */\n UNLOCKED: 'wallet:unlocked',\n /** The session is GONE (logout, wallet deleted, dApp sphere_disconnect, expiry seen at\n * unlock, a different seed behind the lock screen, host destroy).\n * The dApp must clear its session and re-handshake to continue. Unlocking does not cure it.\n * Payload: {@link WalletDisconnectedPayload}. Pushed by ConnectHost.revokeSession(). */\n DISCONNECTED: 'wallet:disconnected',\n /** Active wallet address changed. dApp should update displayed identity.\n * Pushed automatically by ConnectHost — no sphere_subscribe needed. */\n IDENTITY_CHANGED: 'identity:changed',\n} as const;\n\nexport type WalletEvent = (typeof WALLET_EVENTS)[keyof typeof WALLET_EVENTS];\n\n/** Payload of {@link WALLET_EVENTS.LOCKED}. Intentionally empty — matches today's push,\n * so an old dApp sees no shape change. */\nexport type WalletLockedPayload = Record<string, never>;\n\n/** Payload of {@link WALLET_EVENTS.DISCONNECTED}. Intentionally empty. */\nexport type WalletDisconnectedPayload = Record<string, never>;\n\n/** Payload of {@link WALLET_EVENTS.UNLOCKED}. */\nexport interface WalletUnlockedPayload {\n /** The wallet's public identity at unlock time. Absent when the rebound Sphere has no\n * identity yet (JSON transports drop undefined keys).\n * Unlock is NOT implicitly the same wallet: the lock screen's \"Forgot password ->\n * restore from recovery phrase\" installs a DIFFERENT seed while origin approvals are\n * keyed by origin alone. The host's own lock-edge guard is authoritative (it revokes\n * instead of pushing this event on a mismatch); this field is what lets a dApp render\n * honestly and what the client-side retry queue checks before draining. */\n readonly identity?: PublicIdentity;\n}\n\n/** Payload of {@link WALLET_EVENTS.IDENTITY_CHANGED} — unchanged: the host forwards\n * Sphere's own event data verbatim and pushes a PublicIdentity from updateSphere().\n * Typed as the union for honesty. */\nexport type WalletIdentityChangedPayload = PublicIdentity | unknown;\n\n/** Events the host pushes unconditionally. They must NEVER be routed through\n * `sphere_subscribe`: Sphere.on() accepts any string and would silently never emit,\n * so the subscribe would succeed and deliver nothing forever. */\nexport const AUTO_PUSHED_EVENTS: readonly WalletEvent[] = [\n WALLET_EVENTS.LOCKED,\n WALLET_EVENTS.UNLOCKED,\n WALLET_EVENTS.DISCONNECTED,\n WALLET_EVENTS.IDENTITY_CHANGED,\n];\n\n/** True for an event the host pushes unconditionally (see {@link AUTO_PUSHED_EVENTS}). */\nexport function isAutoPushedEvent(event: string): event is WalletEvent {\n return (AUTO_PUSHED_EVENTS as readonly string[]).includes(event);\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if a message belongs to the Sphere Connect protocol.\n * Handshakes are accepted at ANY version (the version decision happens in the\n * handshake handler so an incompatible peer gets a clean typed error instead of a\n * silently-dropped message → timeout). All other (session) traffic must share the\n * same MAJOR (so MINOR versions interoperate, e.g. 2.0 ↔ 2.1). */\nexport function isSphereConnectMessage(msg: unknown): msg is SphereConnectMessage {\n if (!msg || typeof msg !== 'object') return false;\n const m = msg as Record<string, unknown>;\n if (m.ns !== SPHERE_CONNECT_NAMESPACE) return false;\n if (m.type === 'handshake') return true;\n if (typeof m.v !== 'string') return false;\n return majorOf(m.v) === majorOf(SPHERE_CONNECT_VERSION);\n}\n\n/** Create a unique request ID */\nexport function createRequestId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","// connect/compatibility.ts — pure, dependency-free compatibility decision for the Connect gate.\nimport { ERROR_CODES } from './protocol';\nimport type { SphereRpcError, NetworkInfo } from './protocol';\nimport { majorOf, compareSemver } from './semver';\n\nexport type IncompatibleReason = 'protocol_incompatible' | 'network_incompatible';\n\nexport interface CompatInput {\n clientProtocol: string; // inbound msg.v\n walletProtocol: string; // SPHERE_CONNECT_VERSION\n clientNetwork: NetworkInfo | undefined; // inbound msg.network\n walletNetworkId: number; // wallet active network\n minMinor?: number; // optional MINOR floor within the MAJOR\n clientSdkVersion?: string; // optional secondary npm floor\n minSdkVersion?: string;\n}\n\nexport type CompatResult = { ok: true } | { ok: false; error: SphereRpcError };\n\nfunction fail(\n code: number,\n reason: IncompatibleReason,\n message: string,\n extra: Record<string, unknown>,\n): CompatResult {\n return { ok: false, error: { code, message, data: { reason, ...extra } } };\n}\n\n/**\n * Decide whether a connecting peer is compatible. Runs four ordered checks\n * (protocol MAJOR → optional MINOR floor → optional SDK floor → network) and\n * returns {ok:true} or {ok:false, error}. A malformed clientProtocol (NaN MAJOR)\n * is treated as protocol-incompatible.\n *\n * Every refusal message NAMES the versions involved. The same numbers are also in\n * `error.data` for anything that wants to branch on them, but `data` alone is not\n * enough: the whole fleet renders `error.message` and nothing else, so a floor that\n * only said \"below the required minimum\" told a developer to upgrade without ever\n * saying to what.\n */\nexport function checkCompatibility(input: CompatInput): CompatResult {\n const { clientProtocol, walletProtocol, minMinor, clientSdkVersion, minSdkVersion } = input;\n\n // 1. Protocol MAJOR must match.\n if (majorOf(clientProtocol) !== majorOf(walletProtocol)) {\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `Incompatible Connect protocol version: app speaks ${clientProtocol}, wallet speaks ${walletProtocol}`,\n { walletProtocol, clientProtocol });\n }\n\n const walletMajor = majorOf(walletProtocol);\n\n // 2. Optional MINOR floor within the MAJOR.\n if (minMinor !== undefined && compareSemver(clientProtocol, `${walletMajor}.${minMinor}`) < 0) {\n const requiredProtocol = `${walletMajor}.${minMinor}`;\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `Connect protocol ${clientProtocol} is below the required minimum ${requiredProtocol}`,\n { walletProtocol, clientProtocol, requiredProtocol });\n }\n\n // 3. Optional secondary npm-SDK floor (rarely used).\n if (minSdkVersion !== undefined && (!clientSdkVersion || compareSemver(clientSdkVersion, minSdkVersion) < 0)) {\n // A client that reported nothing is a distinct failure from one that reported an old\n // version — say which it was, or the developer checks a version that was never sent.\n const actual = clientSdkVersion ?? 'unknown (not reported)';\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `SDK version ${actual} is below the required minimum ${minSdkVersion}`,\n { walletProtocol, clientProtocol, requiredSdk: minSdkVersion, actualSdk: clientSdkVersion ?? null });\n }\n\n // 4. Network must match (a missing network is treated as a mismatch).\n if (!input.clientNetwork || input.clientNetwork.id !== input.walletNetworkId) {\n return fail(ERROR_CODES.INCOMPATIBLE_NETWORK, 'network_incompatible',\n 'dApp targets a different network than the wallet',\n { walletNetwork: { id: input.walletNetworkId }, clientNetwork: input.clientNetwork ?? null });\n }\n\n return { ok: true };\n}\n\n","// AUTO-GENERATED by scripts/gen-version.mjs — do not edit.\nexport const SDK_VERSION = '0.17.6';\n","/**\n * Sphere Connect Permission System\n * Defines scopes, maps methods/intents to required permissions.\n */\n\nimport { RPC_METHODS, INTENT_ACTIONS } from './protocol';\n\n// =============================================================================\n// Permission Scopes\n// =============================================================================\n\nexport const PERMISSION_SCOPES = {\n IDENTITY_READ: 'identity:read',\n BALANCE_READ: 'balance:read',\n TOKENS_READ: 'tokens:read',\n HISTORY_READ: 'history:read',\n EVENTS_SUBSCRIBE: 'events:subscribe',\n RESOLVE_PEER: 'resolve:peer',\n TRANSFER_REQUEST: 'transfer:request',\n DM_REQUEST: 'dm:request',\n DM_READ: 'dm:read',\n DM_MANAGE: 'dm:manage',\n PAYMENT_REQUEST: 'payment:request',\n SIGN_REQUEST: 'sign:request',\n MINT_REQUEST: 'mint:request',\n /** #777: moving a coinless token (an NFT). Distinct from transfer:request. */\n NFT_TRANSFER: 'nft:transfer',\n /** Minting an NFT signs dApp-chosen content as the user; mint:request and nft:transfer do not imply it. */\n NFT_MINT: 'nft:mint',\n} as const;\n\nexport type PermissionScope = (typeof PERMISSION_SCOPES)[keyof typeof PERMISSION_SCOPES];\n\n/** All available permission scopes */\nexport const ALL_PERMISSIONS: readonly PermissionScope[] = Object.values(PERMISSION_SCOPES);\n\n/** Permissions always granted on connect */\nexport const DEFAULT_PERMISSIONS: readonly PermissionScope[] = [\n PERMISSION_SCOPES.IDENTITY_READ,\n];\n\n// =============================================================================\n// Method → Permission Mapping\n// =============================================================================\n\nexport const METHOD_PERMISSIONS: Record<string, PermissionScope> = {\n [RPC_METHODS.GET_IDENTITY]: PERMISSION_SCOPES.IDENTITY_READ,\n [RPC_METHODS.GET_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_ASSETS]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_FIAT_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_TOKENS]: PERMISSION_SCOPES.TOKENS_READ,\n [RPC_METHODS.GET_HISTORY]: PERMISSION_SCOPES.HISTORY_READ,\n [RPC_METHODS.RESOLVE]: PERMISSION_SCOPES.RESOLVE_PEER,\n [RPC_METHODS.SUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE,\n [RPC_METHODS.UNSUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE,\n [RPC_METHODS.GET_CONVERSATIONS]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.GET_MESSAGES]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.GET_DM_UNREAD_COUNT]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.MARK_AS_READ]: PERMISSION_SCOPES.DM_MANAGE,\n};\n\n// =============================================================================\n// Intent → Permission Mapping\n// =============================================================================\n\nexport const INTENT_PERMISSIONS: Record<string, PermissionScope> = {\n [INTENT_ACTIONS.SEND]: PERMISSION_SCOPES.TRANSFER_REQUEST,\n [INTENT_ACTIONS.DM]: PERMISSION_SCOPES.DM_REQUEST,\n [INTENT_ACTIONS.PAYMENT_REQUEST]: PERMISSION_SCOPES.PAYMENT_REQUEST,\n [INTENT_ACTIONS.RECEIVE]: PERMISSION_SCOPES.IDENTITY_READ,\n [INTENT_ACTIONS.SIGN_MESSAGE]: PERMISSION_SCOPES.SIGN_REQUEST,\n [INTENT_ACTIONS.MINT]: PERMISSION_SCOPES.MINT_REQUEST,\n [INTENT_ACTIONS.SEND_NFT]: PERMISSION_SCOPES.NFT_TRANSFER,\n [INTENT_ACTIONS.MINT_NFT]: PERMISSION_SCOPES.NFT_MINT,\n};\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if granted permissions allow calling a method */\nexport function hasMethodPermission(granted: ReadonlySet<string>, method: string): boolean {\n const required = METHOD_PERMISSIONS[method];\n if (!required) return false;\n return granted.has(required);\n}\n\n/** Check if granted permissions allow an intent action */\nexport function hasIntentPermission(granted: ReadonlySet<string>, action: string): boolean {\n const required = INTENT_PERMISSIONS[action];\n if (!required) return false;\n return granted.has(required);\n}\n\n/** Validate that all requested permissions are known scopes */\nexport function validatePermissions(permissions: string[]): permissions is PermissionScope[] {\n const validScopes = new Set<string>(ALL_PERMISSIONS);\n return permissions.every((p) => validScopes.has(p));\n}\n","// docs/PAYMENTS-V2-DESIGN.md §4 \"Compatibility\": old wire queries are served from\n// `sphere.payments`, and every old dApp-subscribable event name is re-emitted from the\n// facade's bus — dApps change NOTHING. Old names stay plain string literals: post-flip\n// this wire surface is their only home.\n\nimport type { TransferResult } from '../../types';\nimport type { PaymentRequestView, PaymentsV2 } from '../../modules/payments-v2/api';\nimport type { SphereInstance } from './SphereInstance';\n\ntype Forward = (data: unknown) => void;\ntype Attach = (sphere: SphereInstance, forward: Forward) => () => void;\n\ntype PaymentRequestUpdated = {\n id: string;\n status: 'pending' | 'settling' | 'paid' | 'rejected' | 'expired';\n};\ntype TransferAttention = { transferId: string; code: string; detail?: string };\ntype ConnectionStatus = { status: 'connected' | 'degraded' | 'offline' };\n\n// Old `sphere_getFiatBalance` semantics, held exactly: sum of priced assets, null when NO\n// asset carries a price — the \"no price data\" signal a dApp may branch on.\nexport function sumFiatUsd(\n assets: ReadonlyArray<{ fiatValueUsd: number | null }>,\n): number | null {\n let total = 0;\n let priced = false;\n for (const asset of assets) {\n if (asset.fiatValueUsd != null) {\n total += asset.fiatValueUsd;\n priced = true;\n }\n }\n return priced ? total : null;\n}\n\nconst SETTLED_STATUSES: ReadonlySet<TransferResult['status']> = new Set([\n 'confirmed',\n 'delivered',\n 'completed',\n]);\n\nconst REALTIME_STATUS: Record<ConnectionStatus['status'], string> = {\n connected: 'connected',\n degraded: 'reconnecting',\n offline: 'closed',\n};\n\n// The ONE view → legacy `IncomingPaymentRequest` mapping (incoming AND the per-status\n// rebuilds): symbol is registry-resolved when known, else '' — never absent on the wire.\nfunction toLegacyRequest(view: PaymentRequestView, status: string): Record<string, unknown> {\n return { ...view, symbol: view.symbol ?? '', status };\n}\n\n// Rebuilds the old per-status `IncomingPaymentRequest` payload: full view from\n// `requests.list()` (processed requests stay listed until dismissProcessed()); if already\n// gone, id + status still carry the signal and unknowable fields are empty.\n/** The facade, or null while none runs — `sphere.payments` throws between verticals.\n * Callers here are EVENT callbacks, whose throw Sphere's dispatcher swallows. */\nfunction paymentsOrNull(sphere: SphereInstance): PaymentsV2 | null {\n try {\n return sphere.payments;\n } catch {\n return null;\n }\n}\n\nfunction legacyRequestPayload(\n sphere: SphereInstance,\n update: PaymentRequestUpdated,\n): Record<string, unknown> {\n const view: PaymentRequestView | undefined = paymentsOrNull(sphere)\n ?.requests.list()\n .find((request) => request.id === update.id);\n if (!view) {\n return {\n id: update.id,\n requestId: update.id,\n senderPubkey: '',\n amount: '',\n coinId: '',\n symbol: '',\n timestamp: Date.now(),\n status: update.status,\n };\n }\n return toLegacyRequest(view, update.status);\n}\n\nfunction requestStatusAttacher(status: 'paid' | 'rejected' | 'expired'): Attach {\n return (sphere, forward) =>\n sphere.on('payment_request:updated', (update: PaymentRequestUpdated) => {\n if (update.status === status) forward(legacyRequestPayload(sphere, update));\n });\n}\n\n// The v2 attention `code` IS the old event name (machine/journal.ts ATTENTION_* constants).\nfunction attentionAttacher(code: string, toLegacy: (a: TransferAttention) => unknown): Attach {\n return (sphere, forward) =>\n sphere.on('transfer:attention', (attention: TransferAttention) => {\n if (attention.code === code) forward(toLegacy(attention));\n });\n}\n\nfunction remoteUpdateAttacher(sphere: SphereInstance, forward: Forward): () => void {\n let sequence = 0;\n return sphere.on('inventory:updated', () => {\n sequence += 1;\n forward({ providerId: 'wallet-api', name: 'wallet-api', sequence, cid: '', added: 0, removed: 0 });\n });\n}\n\n// A Map, not a plain object: the key is dApp-controlled, and an object lookup would reach\n// the prototype chain ('constructor' would come back as a callable \"attacher\").\nconst COMPAT_ATTACHERS: ReadonlyMap<string, Attach> = new Map<string, Attach>([\n // Old completion split, held: deliveryPending ? delivery_pending : confirmed, plus the\n // failed arm (manifest judgment call #1). Payloads are the TransferResult, unchanged.\n ['transfer:confirmed', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (SETTLED_STATUSES.has(result.status) && result.deliveryPending !== true) forward(result);\n })],\n ['transfer:delivery_pending', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (result.status !== 'failed' && result.deliveryPending === true) forward(result);\n })],\n ['transfer:failed', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (result.status === 'failed') forward(result);\n })],\n // Same name on both wires, different payload: the raw v2 view has optional `symbol`;\n // legacy subscribers get the IncomingPaymentRequest shape via the shared mapping.\n ['payment_request:incoming', (sphere, forward) =>\n sphere.on('payment_request:incoming', (view: PaymentRequestView) => {\n forward(toLegacyRequest(view, view.status));\n })],\n ['payment_request:paid', requestStatusAttacher('paid')],\n ['payment_request:rejected', requestStatusAttacher('rejected')],\n ['payment_request:expired', requestStatusAttacher('expired')],\n // detail carries the old inner code (SPLIT_CHECKPOINT_LOST / CHECKPOINT_TRUSTBASE_MISMATCH).\n ['split:checkpoint-stuck', attentionAttacher('split:checkpoint-stuck', (attention) => ({\n transferId: attention.transferId,\n code: attention.detail ?? '',\n error: attention.detail ?? '',\n }))],\n ['delivery:undeliverable', attentionAttacher('delivery:undeliverable', (attention) => ({\n transferId: attention.transferId,\n recipientPubkey: '',\n attempts: 0,\n error: attention.detail ?? '',\n }))],\n ['delivery:deferred', attentionAttacher('delivery:deferred', (attention) => ({\n transferId: attention.transferId,\n recipientPubkey: '',\n reason: attention.detail ?? attention.code,\n deferredUntil: 0,\n }))],\n ['realtime:status', (sphere, forward) =>\n sphere.on('connection:status', (connection: ConnectionStatus) => {\n forward({ status: REALTIME_STATUS[connection.status] ?? 'closed' });\n })],\n // The server IS storage on the v2 vertical — a degraded connection is degraded storage.\n ['storage:degraded', (sphere, forward) =>\n sphere.on('connection:status', (connection: ConnectionStatus) => {\n if (connection.status !== 'degraded') return;\n forward({ providerId: 'wallet-api', error: 'wallet-api connection degraded' });\n })],\n ['sync:completed', (sphere, forward) =>\n sphere.on('inventory:updated', () => {\n forward({ source: 'payments', count: paymentsOrNull(sphere)?.tokens().length ?? 0 });\n })],\n ['sync:remote-update', remoteUpdateAttacher],\n]);\n\n// Attach ONE old-name subscription fed from its v2 source event; `forward` sends the wire\n// frame under the OLD name. Null when `eventName` is not an adapted name (the caller falls\n// through to a direct `sphere.on`).\nexport function attachCompatEvent(\n sphere: SphereInstance,\n eventName: string,\n forward: Forward,\n): (() => void) | null {\n const attach = COMPAT_ATTACHERS.get(eventName);\n return attach ? attach(sphere, forward) : null;\n}\n","/**\n * Wallet-binding FSM + the single total gate decision.\n *\n * PURE: no I/O, no timers, no Sphere reference, no `await` — a transition\n * table + assertTransition.\n *\n * Nothing here is re-exported from connect/index.ts — it is host-internal.\n */\n\nimport { SphereError } from '../../core/errors';\nimport { ERROR_CODES, RPC_METHODS } from '../protocol';\nimport type { WalletLockedData } from '../protocol';\nimport type { WalletState } from '../types';\n\n// ===========================================================================\n// Wire text — RECOMMENDATIONS, not contracts\n// ===========================================================================\n\n/** Recommended refusal text for WALLET_LOCKED. Discriminate on code 4009 and on\n * data.reason, NEVER on this string. Deliberately does not match the reference dApp's\n * teardown regex /not.connected|timeout|transport|closed|session/i. */\nexport const WALLET_LOCKED_MESSAGE = 'Wallet is locked';\n\n/** Fixed text for a non-SphereError throw. A raw JS message must never cross the trust\n * boundary into a third-party dApp. */\nexport const INTERNAL_ERROR_MESSAGE = 'Internal wallet error';\n/**\n * Recommended text for INTENT_OUTCOME_UNKNOWN (4201). Like every message on this wire it is\n * NOT a contract — consumers discriminate on the code — but the wording matters here because a\n * developer reading it must not conclude the intent was cancelled.\n */\nexport const INTENT_UNKNOWN_MESSAGE =\n 'Intent outcome unknown — do not retry; reconcile before acting';\n\n/** Today's literal on the host's not-connected refusals — kept byte-identical.\n * `unavailable` answers this too: unlocking cannot cure it, so promising an unlock\n * would be a lie. */\nexport const NOT_CONNECTED_MESSAGE = 'Not connected';\n\n// ===========================================================================\n// Transitions\n// ===========================================================================\n\n/** Legal edges. NO self-transitions by design: every verb must early-return when it is\n * already in the target state, so assertWalletTransition() enforces idempotency at the\n * table level rather than by reviewer diligence. */\nexport const VALID_WALLET_TRANSITIONS: Record<WalletState, readonly WalletState[]> = {\n live: ['locked', 'unavailable'],\n locked: ['live', 'unavailable'],\n unavailable: ['live', 'locked'],\n};\n\nexport function isValidWalletTransition(from: WalletState, to: WalletState): boolean {\n return VALID_WALLET_TRANSITIONS[from].includes(to);\n}\n\n/** Throws SphereError('Invalid wallet state transition: from -> to', 'VALIDATION_ERROR')\n * — an EXISTING SphereErrorCode; core/errors.ts is not touched. */\nexport function assertWalletTransition(from: WalletState, to: WalletState): void {\n if (!isValidWalletTransition(from, to)) {\n throw new SphereError(`Invalid wallet state transition: ${from} -> ${to}`, 'VALIDATION_ERROR');\n }\n}\n\n// ===========================================================================\n// Locked allow-list\n// ===========================================================================\n\n/**\n * The four methods answerable while locked. Everything else gets 4009.\n *\n * - sphere_getIdentity -> 'serve-from-snapshot'. Those exact bytes were already handed\n * to this origin in the handshake response and DEFAULT_PERMISSIONS\n * grants identity:read unconditionally, so refusing re-states a\n * revealed fact for zero security while making every dApp that\n * reads identity once on mount render \"disconnected\" for the whole\n * lock.\n * - sphere_subscribe -> 'serve'. Refusing it is a BUG, not a policy: ConnectClient.on()\n * fires sphere_subscribe fire-and-forget and NEVER retries (the\n * error goes to logger.debug), so the event stream would die\n * forever after one lock.\n * - sphere_unsubscribe -> 'serve'. Needs no Sphere at all.\n * - sphere_disconnect -> 'serve'. Without it a session can never be dropped while\n * locked; the client swallows the error and calls cleanup()\n * anyway, so the dApp shows \"disconnected\" while onDisconnect —\n * the only hook that revokes the persisted origin approval —\n * never fires.\n *\n * WHAT IS BLOCKED — the honest count, because an enumeration of only the money reads reads as\n * if messaging still works. RPC_METHODS has FOURTEEN entries; four are above. The other TEN,\n * and EVERY intent, are refused:\n *\n * sphere_getBalance, sphere_getAssets, sphere_getFiatBalance, sphere_getTokens,\n * sphere_getHistory — money state a locked wallet cannot honour. Never cached either: a\n * dApp holding a stale balance is a dApp about to offer an\n * unpayable spend.\n * sphere_resolve — nametag resolution needs the live transport.\n * sphere_getConversations, sphere_getMessages, sphere_getDMUnreadCount, sphere_markAsRead\n * — DMs are decrypted with keys that left memory. Messaging does NOT\n * keep working while locked; a dApp must stop polling and wait.\n *\n * AND THE CASE WITH NO 4009 AT ALL. Everything above assumes a host that HOLDS a session. A\n * wallet that COLD-STARTS locked (a page reload, a fresh popup — the password is memory-only,\n * so this is the common path) has no session and an EMPTY snapshot, so `handleHandshake`\n * refuses at step 0 with an errorless empty response: the dApp's connect() rejects with a bare\n * \"Connection rejected by wallet\" carrying NO code. There is nothing to match 4009 against.\n * That silence is deliberate — the refusal must reveal nothing about the wallet to an origin\n * holding no approval — so a dApp cannot distinguish it from a user pressing Reject and must\n * treat it as \"not ready yet\": keep waiting for HOST_READY, which the wallet emits when a\n * human unlocks it.\n */\nexport const LOCKED_ALLOWLIST: ReadonlySet<string> = new Set<string>([\n RPC_METHODS.GET_IDENTITY,\n RPC_METHODS.SUBSCRIBE,\n RPC_METHODS.UNSUBSCRIBE,\n RPC_METHODS.DISCONNECT,\n]);\n\n// ===========================================================================\n// Gate\n// ===========================================================================\n\nexport type RequestKind = 'query' | 'intent' | 'handshake';\n\nexport type GateDecision =\n | { readonly kind: 'serve' }\n /** Answer from WalletSnapshot. If the required snapshot field is absent the caller\n * REFUSES (4009 for a query, the empty refusal for a handshake) — it never invents a\n * fact about a wallet we have not seen, and never sends undefined-as-success. */\n | { readonly kind: 'serve-from-snapshot' }\n | {\n readonly kind: 'refuse';\n readonly error: {\n readonly code: number;\n readonly message: string;\n readonly data?: WalletLockedData;\n };\n };\n\nconst REFUSE_NOT_CONNECTED: GateDecision = {\n kind: 'refuse',\n error: { code: ERROR_CODES.NOT_CONNECTED, message: NOT_CONNECTED_MESSAGE },\n};\n\nconst REFUSE_LOCKED: GateDecision = {\n kind: 'refuse',\n error: {\n code: ERROR_CODES.WALLET_LOCKED,\n message: WALLET_LOCKED_MESSAGE,\n data: { reason: 'locked' },\n },\n};\n\n/**\n * The single total gate decision. Positional, four required arguments, no I/O.\n * `name` is an RPC method, an intent action, or the literal 'handshake'.\n *\n * Called at step 4 of the ordering contract, AFTER the session check, the expiry check and\n * the rate limit, and BEFORE the permission check — because hasMethodPermission() is false\n * for anything unmapped, so a locked unknown method would otherwise answer\n * PERMISSION_DENIED 4002, an un-retryable code that tells the dApp its permissions are\n * wrong.\n *\n * The resume release merges `unlockSurface` into `error.data` AFTER this returns — the gate\n * stays pure.\n */\nexport function gate(\n walletState: WalletState,\n hasActiveSession: boolean,\n requestKind: RequestKind,\n name: string,\n): GateDecision {\n // 'unavailable' is a dead end: revoke + wallet:disconnected already happened, and\n // unlocking does not cure it. Never 4009 here.\n if (walletState === 'unavailable') return REFUSE_NOT_CONNECTED;\n\n if (requestKind === 'handshake') {\n // While locked EVERY handshake is answered from the snapshot and forced silent, so an\n // origin without an approval still gets today's empty refusal with no UI.\n return walletState === 'locked' ? { kind: 'serve-from-snapshot' } : { kind: 'serve' };\n }\n\n // A query or an intent without a session is 4001 in every wallet state: the lock is\n // observable ONLY to an origin that already holds an approval.\n if (!hasActiveSession) return REFUSE_NOT_CONNECTED;\n\n if (walletState === 'live') return { kind: 'serve' };\n\n // locked, with an active session.\n if (requestKind === 'query' && LOCKED_ALLOWLIST.has(name)) {\n return name === RPC_METHODS.GET_IDENTITY\n ? { kind: 'serve-from-snapshot' }\n : { kind: 'serve' };\n }\n return REFUSE_LOCKED;\n}\n","/**\n * Immutable public facts about the wallet binding.\n *\n * Holds NO reference to Sphere, so \"nothing is read from a destroyed Sphere while locked\"\n * is a property of the types rather than of code review.\n *\n * NOT named SessionSnapshot and carries NO sessionId: it must exist BEFORE any session\n * does — sendHandshakeResponse reads networkId and is the transport for EVERY handshake\n * response, including the refusals.\n *\n * The lock-edge identity binding IS `snapshot.identity?.chainPubkey`; there is no separate\n * chainPubkey field and no separate boundIdentity field.\n */\n\nimport type { PublicIdentity } from '../protocol';\nimport type { SphereInstance } from './SphereInstance';\n\nexport interface WalletSnapshot {\n readonly networkId?: number;\n readonly identity?: PublicIdentity;\n /** Epoch ms of capture. 0 means \"never captured\" — see EMPTY_WALLET_SNAPSHOT. */\n readonly capturedAt: number;\n}\n\n/** capturedAt 0, no networkId, no identity. Used for a host built locked with no prior\n * binding (cold start with an encrypted wallet), after setUnavailable(), and after\n * destroy(). A handshake against an empty snapshot gets today's empty refusal —\n * including a resume. \"Never invent a fact about a wallet we have not seen.\" */\nexport const EMPTY_WALLET_SNAPSHOT: WalletSnapshot = Object.freeze({ capturedAt: 0 });\n\n/**\n * Build from a bound Sphere. `null` returns EMPTY_WALLET_SNAPSHOT.\n *\n * If sphere.identity is null the snapshot has NO identity, and sphere_getIdentity while\n * locked answers 4009 rather than undefined-as-success — a dApp reads an `undefined`\n * success result as \"the wallet has no identity\".\n */\nexport function buildWalletSnapshot(sphere: SphereInstance | null): WalletSnapshot {\n if (!sphere) return EMPTY_WALLET_SNAPSHOT;\n const id = sphere.identity;\n return Object.freeze({\n ...(typeof sphere.networkId === 'number' ? { networkId: sphere.networkId } : {}),\n ...(id\n ? {\n identity: Object.freeze({\n chainPubkey: id.chainPubkey,\n directAddress: id.directAddress,\n nametag: id.nametag,\n }),\n }\n : {}),\n capturedAt: Date.now(),\n });\n}\n","/**\n * Every accepted request id, with its own bounded host-side timer.\n *\n * The single convergence point for every exit: router resolved, router threw, onIntent\n * settled or threw, host deadline, setLocked, revokeSession, setUnavailable, destroy.\n * Because settle() returns null the second time, \"exactly one frame per id\" is a property\n * of this class rather than of every call site.\n *\n * SYNCHRONOUS by design — no `await` anywhere inside. Frame-agnostic: it knows deadlines\n * and kinds, never how to send.\n */\n\nimport { logger } from '../../core/logger';\n\nexport type InFlightKind = 'query' | 'intent';\n\nexport interface InFlightEntry {\n readonly id: string;\n readonly kind: InFlightKind;\n /** Epoch ms. */\n readonly deadline: number;\n /** Aborted by settle() / settleAll() / expiry. Feeds IntentContext.signal. */\n readonly controller: AbortController;\n}\n\nexport interface InFlightRegistryOptions {\n /** Called when an entry's own timer fires. The entry is ALREADY removed and aborted, so\n * the sink just sends the frame (INTERNAL_ERROR -32603 for a query, INTENT_OUTCOME_UNKNOWN\n * 4201 for an intent — never INTENT_CANCELLED, which would assert that nothing happened). */\n readonly onExpire: (entry: InFlightEntry) => void;\n}\n\ninterface Slot {\n readonly entry: InFlightEntry;\n readonly timer: ReturnType<typeof setTimeout>;\n}\n\nexport class InFlightRegistry {\n private readonly slots = new Map<string, Slot>();\n private readonly onExpire: (entry: InFlightEntry) => void;\n\n constructor(options: InFlightRegistryOptions) {\n this.onExpire = options.onExpire;\n }\n\n get size(): number {\n return this.slots.size;\n }\n\n has(id: string): boolean {\n return this.slots.has(id);\n }\n\n /** Register BEFORE the first await and arm the timer AT INSERTION TIME.\n * A duplicate id is a protocol violation: warn and reuse the existing entry, so a\n * hostile or buggy client cannot arm unbounded timers by replaying one id. */\n add(id: string, kind: InFlightKind, deadlineMs: number): InFlightEntry {\n const existing = this.slots.get(id);\n if (existing) {\n logger.warn('InFlightRegistry', `Duplicate request id, reusing entry: ${id}`);\n return existing.entry;\n }\n\n const entry: InFlightEntry = {\n id,\n kind,\n deadline: Date.now() + deadlineMs,\n controller: new AbortController(),\n };\n const timer = setTimeout(() => {\n this.slots.delete(id);\n entry.controller.abort();\n this.onExpire(entry);\n }, deadlineMs);\n\n this.slots.set(id, { entry, timer });\n return entry;\n }\n\n /** Remove + abort BEFORE the caller sends. Returns the entry, or null when it was\n * already settled — in which case the caller MUST send nothing. */\n settle(id: string): InFlightEntry | null {\n const slot = this.slots.get(id);\n if (!slot) {\n logger.warn('InFlightRegistry', `Already settled, dropping second answer: ${id}`);\n return null;\n }\n this.slots.delete(id);\n clearTimeout(slot.timer);\n slot.entry.controller.abort();\n return slot.entry;\n }\n\n /** Settle every entry, in insertion order, aborting each. The caller sends one frame per\n * returned entry. Used by setLocked (4009), revokeSession (4001), destroy (4001). */\n settleAll(): InFlightEntry[] {\n const out: InFlightEntry[] = [];\n for (const slot of this.slots.values()) {\n clearTimeout(slot.timer);\n slot.entry.controller.abort();\n out.push(slot.entry);\n }\n this.slots.clear();\n return out;\n }\n\n /** Clear all timers WITHOUT invoking onExpire. Host teardown only. */\n destroy(): void {\n for (const slot of this.slots.values()) clearTimeout(slot.timer);\n this.slots.clear();\n }\n}\n","/**\n * ConnectHost — Wallet side of Sphere Connect.\n *\n * Wraps a Sphere instance and exposes its API through a ConnectTransport.\n * Handles permission checking, rate limiting, session management,\n * and delegates intents to the wallet app via callbacks.\n */\n\nimport { logger } from '../../core/logger';\nimport { SphereError } from '../../core/errors';\nimport type { SphereEventType } from '../../types';\nimport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n WalletState,\n LockedRequestContext,\n IntentContext,\n} from '../types';\nimport type {\n SphereConnectMessage,\n SphereRpcRequest,\n SphereIntentRequest,\n SphereHandshake,\n PublicIdentity,\n SphereRpcError,\n NetworkInfo,\n WalletLockedData,\n WalletLockedPayload,\n WalletUnlockedPayload,\n WalletDisconnectedPayload,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n INTENT_ACTIONS,\n ERROR_CODES,\n WALLET_EVENTS,\n createRequestId,\n isAutoPushedEvent,\n DEFAULT_MIN_CLIENT_SDK_VERSION,\n} from '../protocol';\nimport { checkCompatibility } from '../compatibility';\nimport { SDK_VERSION } from '../version';\nimport {\n DEFAULT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n} from '../permissions';\nimport type { PermissionScope } from '../permissions';\nimport type { SphereInstance, ConnectDirectMessage } from './SphereInstance';\nimport { attachCompatEvent, sumFiatUsd } from './payments-compat';\nimport {\n assertWalletTransition,\n gate,\n WALLET_LOCKED_MESSAGE,\n INTERNAL_ERROR_MESSAGE,\n INTENT_UNKNOWN_MESSAGE,\n NOT_CONNECTED_MESSAGE,\n} from './host-state';\nimport type { WalletSnapshot } from './WalletSnapshot';\nimport { EMPTY_WALLET_SNAPSHOT, buildWalletSnapshot } from './WalletSnapshot';\nimport { InFlightRegistry } from './InFlightRegistry';\nimport type { InFlightEntry } from './InFlightRegistry';\n\nconst DEFAULT_SESSION_TTL_MS = 86400000; // 24 hours\nconst DEFAULT_MAX_RPS = 20;\n/**\n * Codes a wallet must never use to answer an intent the host already handed it. Both describe\n * the CHANNEL, not the operation: by the time `onIntent` has been called the wallet owns the\n * decision, so \"wallet locked\" or \"not connected\" says nothing true about the spend while\n * inviting a retry — 4009's documented advice is literally \"retry after wallet:unlocked\". They\n * are downgraded to INTENT_OUTCOME_UNKNOWN.\n *\n * Deliberately NOT here: USER_REJECTED and INTENT_CANCELLED. A user who declines the prompt\n * BEFORE anything is submitted is a real, retryable rejection; downgrading it would make every\n * declined payment un-retryable, which is both false and worse UX than the bug. Ensuring the\n * wallet cannot report a cancel AFTER submitting is the WALLET's job — it must not offer a\n * cancel control once the transfer is on the wire. The general contract for third-party wallets,\n * an explicit `ctx.commit()` marking the point of no return after which these two are\n * downgraded as well, is a follow-up rather than something to fake here.\n */\nconst CHANNEL_ONLY_CODES: ReadonlySet<number> = new Set<number>([\n ERROR_CODES.WALLET_LOCKED,\n ERROR_CODES.NOT_CONNECTED,\n]);\n\n/** Intents that always reach `onIntent`: minting an NFT signs dApp-chosen content as the user, so no auto-approval may stand in for the prompt. */\nconst ALWAYS_ASK_INTENTS: ReadonlySet<string> = new Set<string>([INTENT_ACTIONS.MINT_NFT]);\n\nconst DEFAULT_REQUEST_DEADLINE_MS = 25000;\n// LONGER than ConnectClient's own DEFAULT_INTENT_TIMEOUT (120 s). The host must never be the\n// first to give up on a delegated intent: whoever answers first defines the outcome for the\n// dApp, and the host cannot know whether the wallet has already submitted the transfer. With\n// the client timing out first, the dApp learns \"outcome unknown\" from its own clock instead of\n// being told, authoritatively and falsely, that nothing happened.\nconst DEFAULT_INTENT_DEADLINE_MS = 180000;\nconst DEFAULT_HANDSHAKE_DEADLINE_MS = 120000;\n\n/** Resolve `promise`, or `fallback()` after `ms`. Used ONLY for onConnectionRequest, which\n * has no id and therefore cannot live in InFlightRegistry. A rejection still propagates,\n * so handleHandshake's own error handling is unchanged. */\nfunction withDeadline<T>(promise: Promise<T>, ms: number, fallback: () => T): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => resolve(fallback()), ms);\n promise.then(\n (value) => { clearTimeout(timer); resolve(value); },\n (error) => { clearTimeout(timer); reject(error); },\n );\n });\n}\n\nexport class ConnectHost {\n /** Null whenever _walletState is 'locked' or 'unavailable' (invariant B). */\n private sphere: SphereInstance | null;\n\n /** The wallet-binding axis. Underscored because `walletState` is the public getter.\n * ORTHOGONAL to `session` — a locked wallet keeps its session, a live wallet may have\n * none. Written only by the WALLET (setLocked / setUnavailable / updateSphere / destroy);\n * `session` is written by the dApp handshake, sphere_disconnect and expiry. */\n private _walletState: WalletState;\n\n /** Immutable public facts about the current binding. Refreshed on every bind\n * (constructor, updateSphere); FROZEN by setLocked(); EMPTY after setUnavailable() and\n * destroy(). Never read from Sphere while locked — that is a property of the types\n * here, not of code review. */\n private snapshot: WalletSnapshot;\n\n /** Subscription KEYS captured by setLocked() BEFORE the unsub closures are detached.\n * Sphere.destroy() kills those closures, so the keys are the only recoverable\n * information. Excludes 'identity:changed' (autoSubscribeIdentityChanged re-arms it).\n * A Set, not an array: handleSubscribe may be called twice for the same key while\n * locked. */\n private suspendedSubscriptions: Set<string> = new Set();\n\n /** Every accepted id, with its own host-side timer. The single convergence point for\n * lock / revoke / unavailable / destroy / deadline. */\n private readonly inFlight: InFlightRegistry;\n\n private readonly transport: ConnectTransport;\n private readonly config: ConnectHostConfig;\n\n private session: ConnectSession | null = null;\n private grantedPermissions: Set<string> = new Set();\n\n // Event subscription management\n private eventSubscriptions: Map<string, () => void> = new Map(); // eventName → unsub\n\n // Intent auto-approve: action → handler that bypasses wallet UI\n private autoApprovedIntents = new Map<\n string,\n (action: string, params: Record<string, unknown>, session: ConnectSession) => Promise<{ result?: unknown; error?: { code: number; message: string; data?: unknown } }>\n >();\n\n // Rate limiting\n private rateLimitCounter = 0;\n private rateLimitResetAt = 0;\n\n private unsubscribeTransport: (() => void) | null = null;\n\n constructor(config: ConnectHostConfig) {\n this.transport = config.transport;\n this.config = config;\n\n this._walletState = config.initialWalletState ?? 'live';\n this.sphere = (config.sphere ?? null) as SphereInstance | null;\n if (this._walletState === 'live' && !this.sphere) {\n // Fail LOUD but soft: a throw here breaks the wallet's React mount.\n logger.warn(\n 'ConnectHost',\n 'Constructed live with sphere === null; coercing to unavailable. ' +\n 'Pass initialWalletState: \"locked\" when the wallet is locked at construction time.',\n );\n this._walletState = 'unavailable';\n }\n if (this._walletState !== 'live') this.sphere = null; // invariant B, unconditionally\n this.snapshot = buildWalletSnapshot(this.sphere); // EMPTY_WALLET_SNAPSHOT when null\n\n this.inFlight = new InFlightRegistry({ onExpire: (e) => this.settleExpired(e) });\n\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\n }\n\n /** The wallet-binding axis. Orthogonal to {@link getSession}. Read-only —\n * transitions go through setLocked() / setUnavailable() / updateSphere(). */\n get walletState(): WalletState {\n return this._walletState;\n }\n\n /** Both axes in one read, for UI that must render \"connected AND locked\".\n * Required by the wallet's ConnectPage, which today renders a green pulsing\n * \"Connected to {dapp}\" with no regard for lock state. */\n getState(): { readonly walletState: WalletState; readonly session: ConnectSession | null } {\n return { walletState: this._walletState, session: this.session };\n }\n\n /** Get current active session */\n getSession(): ConnectSession | null {\n return this.session;\n }\n\n /** Register an auto-approve handler for an intent action (session-scoped). Throws for an intent that always asks the user. */\n setIntentAutoApprove(\n action: string,\n handler: (\n action: string,\n params: Record<string, unknown>,\n session: ConnectSession,\n ) => Promise<{ result?: unknown; error?: { code: number; message: string; data?: unknown } }>,\n ): void {\n if (ALWAYS_ASK_INTENTS.has(action)) {\n throw new SphereError(`Intent ${action} always asks the user and cannot be auto-approved`, 'INVALID_CONFIG');\n }\n this.autoApprovedIntents.set(action, handler);\n }\n\n /** Remove auto-approve for an intent action. */\n clearIntentAutoApprove(action: string): void {\n this.autoApprovedIntents.delete(action);\n }\n\n /**\n * Bind a (new) Sphere instance. This is BOTH the re-arm path after setLocked() /\n * setUnavailable() AND the existing address-switch path in a live wallet.\n *\n * From 'live' (address switch): today's behaviour, unchanged — re-arm identity:changed,\n * push identity:changed. NO identity comparison: an address switch is legal.\n *\n * On the 'locked' -> 'live' edge, in this order:\n * 1. compare snapshot.identity?.chainPubkey with the new Sphere's chainPubkey.\n * MISMATCH => revokeSession() (which pushes wallet:disconnected) and RETURN.\n * Never wallet:unlocked. This is the \"Forgot password -> restore recovery phrase\n * installed a different seed behind an origin-keyed approval\" guard.\n * 2. session.expiresAt passed => revokeSession() and RETURN. A wallet:unlocked into a\n * dead session would make the dApp's next request answer SESSION_EXPIRED 4004.\n * 3. rebind, refresh the snapshot, go live, re-arm identity:changed, replay every\n * suspended sphere_subscribe key, and ONLY THEN push wallet:unlocked with the\n * CURRENT identity. Re-arm BEFORE push, so a dApp reacting synchronously cannot\n * race its own event streams.\n *\n * From 'unavailable' -> 'live': rebind + refresh the snapshot, no identity check\n * (nothing was bound to compare against) and no event (the session is already null).\n */\n updateSphere(newSphere: unknown): void {\n const wasLocked = this._walletState === 'locked';\n const next = (newSphere ?? null) as SphereInstance | null;\n\n // `null` is explicitly anticipated by the coalesce above, and committing 'live' without a\n // Sphere is unrepresentable: every query would dereference null and answer -32603 forever,\n // never 4009, while the client keeps reporting walletLocked with nothing able to clear it.\n // Route it to the verb that actually means \"there is no Sphere\".\n if (!next) {\n if (this._walletState === 'locked') {\n // Stay locked. The dApp is waiting for a real unlock; a spurious 'unavailable' would\n // revoke a session that a genuine unlock could still have served.\n logger.warn('ConnectHost', 'updateSphere(null) while locked — staying locked');\n return;\n }\n logger.warn('ConnectHost', 'updateSphere(null) — treating as a non-lock loss of Sphere');\n this.setUnavailable();\n return;\n }\n\n if (this._walletState === 'live') {\n // THIS rebind never re-runs checkCompatibility: the lock-edge guard below sits\n // behind `wasLocked`. Unchecked, a host switching network without locking keeps\n // the session and serves a chain the dApp never agreed to. Identity is NOT\n // compared here — changing it is what an address switch IS.\n if (this.session?.active && (this.snapshot.networkId ?? null) !== (next.networkId ?? null)) {\n logger.warn(\n 'ConnectHost',\n `Network changed under a live session — revoking instead of rebinding (origin=${this.config.origin ?? 'unverified'})`,\n );\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession();\n return;\n }\n // Address switch on a live host — today's behaviour, verbatim.\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n const existing = this.eventSubscriptions.get(WALLET_EVENTS.IDENTITY_CHANGED);\n if (existing) {\n existing();\n this.eventSubscriptions.delete(WALLET_EVENTS.IDENTITY_CHANGED);\n }\n if (this.session?.active) {\n this.autoSubscribeIdentityChanged();\n // Push the new identity immediately so dApp doesn't have to wait for the next event\n const identity = this.getPublicIdentity();\n if (identity) {\n this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, identity);\n }\n }\n return;\n }\n\n // 1. Lock-edge identity guard — ONLY on locked -> live. From 'unavailable' there is\n // nothing to compare against.\n if (wasLocked && this.session?.active) {\n // FAIL CLOSED. `before && after &&` used to no-op the whole guard whenever either side\n // was absent — and an absent `before` is exactly what a wallet manufactures by calling\n // sphere.destroy() before setLocked(), an ordering the docs ask for but nothing enforces.\n // An unknown identity on either side is a mismatch: it is not evidence of sameness.\n const before = this.snapshot.identity?.chainPubkey ?? null;\n const after = next.identity?.chainPubkey ?? null;\n // A NETWORK change is equally unrecoverable and was not checked at all. On main a lock\n // revoked unconditionally, so a re-handshake ran checkCompatibility and answered 4008;\n // preserving the session across a lock skipped that gate, leaving the dApp served\n // balances from a chain it never agreed to while still reporting the old network.\n const netBefore = this.snapshot.networkId ?? null;\n const netAfter = next.networkId ?? null;\n if (before !== after || netBefore !== netAfter) {\n logger.warn(\n 'ConnectHost',\n `Wallet behind the lock screen is not the one this session was approved for — revoking instead of unlocking (origin=${this.config.origin ?? 'unverified'})`,\n );\n assertWalletTransition(this._walletState, 'live');\n this._walletState = 'live';\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession(); // pushes wallet:disconnected, settles in flight with 4001\n return;\n }\n\n // 2. Expired while locked. The TTL is 24 h by default and expiresAt does not refresh.\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n logger.warn(\n 'ConnectHost',\n `Session expired while locked — re-handshake required (origin=${this.config.origin ?? 'unverified'})`,\n );\n assertWalletTransition(this._walletState, 'live');\n this._walletState = 'live';\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession();\n return;\n }\n }\n\n // 3. Rebind and go live. _walletState is set BEFORE the replay, because\n // handleSubscribe() branches on it and would otherwise put every key straight back\n // into suspendedSubscriptions and arm nothing.\n assertWalletTransition(this._walletState, 'live');\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this._walletState = 'live';\n\n if (!this.session?.active) {\n // Nothing to re-arm and nothing to announce.\n this.suspendedSubscriptions.clear();\n return;\n }\n\n this.autoSubscribeIdentityChanged();\n\n const suspended = [...this.suspendedSubscriptions];\n this.suspendedSubscriptions.clear();\n for (const eventName of suspended) {\n try {\n this.handleSubscribe(eventName);\n } catch (err) {\n logger.warn('ConnectHost', `Re-subscribe failed after unlock: ${eventName}`, err);\n }\n }\n\n logger.debug(\n 'ConnectHost',\n `Wallet unlocked — re-armed ${suspended.length} subscription(s) (origin=${this.config.origin ?? 'unverified'})`,\n );\n\n this.pushClientEvent(WALLET_EVENTS.UNLOCKED, {\n identity: this.getPublicIdentity(),\n } satisfies WalletUnlockedPayload);\n\n // AND identity:changed, which main pushed on every unlock and which the Connect 2.0 docs\n // named as the ONLY unlock signal. checkCompatibility gates on the MAJOR alone, so every\n // already-shipped 2.0 dApp connects to a 2.1 wallet happily — and then, without this, sits\n // showing \"wallet locked\" for the rest of the page's life, because wallet:unlocked is a\n // name it has never heard of. Idempotent for a 2.1 client: it has already written the same\n // identity from the payload above, and its IDENTITY_CHANGED branch just rewrites it.\n const identity = this.getPublicIdentity();\n if (identity) this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, identity);\n }\n\n /**\n * The wallet locked (manual lock, idle auto-lock, cross-tab broadcast, cold start).\n * The session is PRESERVED — a lock is a state, not a teardown. Every request outside\n * the locked allow-list is answered WALLET_LOCKED (4009) until updateSphere().\n *\n * Idempotent: a second call is a no-op and pushes nothing. Required, because\n * SphereProvider.lock(), ConnectPage's `sphere → null` effect and broadcastLock()'s\n * same-tab loopback can all fire it for one user action.\n *\n * ORDERING CONTRACT: call this BEFORE sphere.destroy(). The host drops its Sphere\n * reference here; destroying first leaves in-flight requests reading a dead instance\n * (-32603, or `undefined` returned AS SUCCESS from sphere_getIdentity).\n */\n setLocked(): void {\n if (this._walletState === 'locked') return;\n assertWalletTransition(this._walletState, 'locked');\n\n // Freeze BEFORE dropping. snapshot.identity?.chainPubkey IS the lock-edge identity\n // binding compared in updateSphere() — there is no separate boundIdentity field.\n this.snapshot = buildWalletSnapshot(this.sphere);\n this._walletState = 'locked';\n logger.debug(\n 'ConnectHost',\n `Wallet locked — session preserved (origin=${this.config.origin ?? 'unverified'}, session=${this.session?.id ?? 'none'})`,\n );\n\n if (this.session?.active) {\n this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n }\n\n // Snapshot-then-detach. cleanupEventSubscriptions() ends in .clear() and the Map holds\n // eventName → closure; Sphere.destroy() kills those closures, so the KEYS are the only\n // recoverable information. Exclude identity:changed — autoSubscribeIdentityChanged()\n // re-arms that one itself.\n for (const key of this.eventSubscriptions.keys()) {\n if (key === WALLET_EVENTS.IDENTITY_CHANGED) continue;\n this.suspendedSubscriptions.add(key);\n }\n this.cleanupEventSubscriptions();\n\n // Today only revokeSession() clears these. Under session-preserving semantics an\n // auto-approved intent would otherwise survive a lock and execute unattended after\n // the unlock.\n this.autoApprovedIntents.clear();\n\n this.settleInFlight(ERROR_CODES.WALLET_LOCKED, WALLET_LOCKED_MESSAGE, { reason: 'locked' });\n this.sphere = null;\n }\n\n /**\n * The Sphere instance is gone for a NON-LOCK reason (a generic init failure leaves\n * `sphere === null, isLocked === false` in the wallet).\n * This is a DEAD END: unlocking does not cure it, so it revokes the session and pushes\n * wallet:disconnected rather than promising an unlock that cannot help.\n * Subsequent requests answer NOT_CONNECTED (4001); handshakes get the empty refusal\n * WITHOUT dereferencing a null Sphere.\n *\n * Idempotent. Pushes no 'wallet:unavailable' — there is no such event.\n */\n setUnavailable(): void {\n if (this._walletState === 'unavailable') return;\n assertWalletTransition(this._walletState, 'unavailable');\n\n this._walletState = 'unavailable';\n logger.warn(\n 'ConnectHost',\n `Sphere unavailable (non-lock) — session revoked (origin=${this.config.origin ?? 'unverified'})`,\n );\n\n this.snapshot = EMPTY_WALLET_SNAPSHOT; // nothing may be served from it\n this.suspendedSubscriptions.clear();\n this.revokeSession(); // pushes wallet:disconnected, settles 4001\n this.sphere = null;\n }\n\n /**\n * Destroy the SESSION (logout, wallet deleted, dApp sphere_disconnect, popup\n * beforeunload, expiry, identity mismatch at unlock). Pushes wallet:disconnected BEFORE\n * tearing down, so the dApp stops believing it is connected instead of finding out at\n * its next 4001.\n *\n * This is the TEARDOWN verb. For a lock use setLocked() — a lock never destroys the\n * session. revokeSession() does NOT touch walletState: the two axes are orthogonal.\n */\n revokeSession(): void {\n if (this.session) {\n logger.debug(\n 'ConnectHost',\n `Session revoked (origin=${this.config.origin ?? 'unverified'}, session=${this.session.id})`,\n );\n if (this.session.active) {\n this.pushClientEvent(WALLET_EVENTS.DISCONNECTED, {} as WalletDisconnectedPayload);\n }\n this.session.active = false;\n this.cleanupEventSubscriptions();\n this.autoApprovedIntents.clear();\n this.session = null;\n this.grantedPermissions.clear();\n }\n this.suspendedSubscriptions.clear();\n this.settleInFlight(ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n }\n\n /** Destroy the host, clean up all resources. Idempotent. */\n destroy(): void {\n this.revokeSession(); // pushes wallet:disconnected, settles 4001\n this.inFlight.destroy(); // clear timers WITHOUT invoking onExpire\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\n }\n this.sphere = null;\n this.snapshot = EMPTY_WALLET_SNAPSHOT;\n if (this._walletState !== 'unavailable') {\n assertWalletTransition(this._walletState, 'unavailable');\n this._walletState = 'unavailable';\n }\n }\n\n // ===========================================================================\n // Message Handling\n // ===========================================================================\n\n private async handleMessage(msg: SphereConnectMessage): Promise<void> {\n try {\n if (msg.type === 'handshake' && msg.direction === 'request') {\n await this.handleHandshake(msg);\n return;\n }\n\n if (msg.type === 'request') {\n await this.handleRpcRequest(msg);\n return;\n }\n\n if (msg.type === 'intent') {\n await this.handleIntentRequest(msg);\n return;\n }\n } catch (error) {\n logger.warn('ConnectHost', 'Error handling message:', error);\n // A throw used to send NOTHING, so the dApp hung for its full client timeout and\n // ended with a bare Error('Query timeout: …') / Error('Intent timeout: …') /\n // Error('Connection timeout') carrying no .code.\n this.sendUnhandledError(msg, error);\n }\n }\n\n // ===========================================================================\n // Handshake\n // ===========================================================================\n\n private async handleHandshake(msg: SphereHandshake): Promise<void> {\n const dapp = msg.dapp;\n // A handshake without dapp metadata is malformed — deny silently (no session).\n // The compatibility gate and onConnectionRejected are intentionally not consulted\n // here: there is no app to gate or to surface a rejection reason for.\n if (!dapp) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // STEP 0 — a host with no live binding AND no snapshot knows nothing about a wallet.\n // BEFORE checkCompatibility on purpose: snapshot.networkId is undefined on a\n // cold-start-locked host, so the network check (a missing network is treated as a\n // mismatch) would answer INCOMPATIBLE_NETWORK 4008 to EVERY origin — leaking\n // \"a wallet lives here, network -1\" with no approval, and lying about the cause.\n if (this._walletState !== 'live' && !this.snapshot.identity) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Rate limit. The handshake path was entirely unmetered: an unapproved origin could\n // loop handshakes and open the approval UI without bound. The refusal is today's EMPTY\n // refusal — it carries no error and reveals nothing about the wallet's state.\n if (!this.checkRateLimit()) {\n logger.warn('ConnectHost', 'Handshake rate-limited', { dapp: dapp.name });\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Captured for the pre-await decisions; RE-READ after the approval prompt below, because a\n // human-time await gives the wallet room to lock, unlock or log out underneath us.\n const stateAtPrompt = this._walletState;\n let locked = stateAtPrompt === 'locked';\n\n // Lock gate for the handshake kind. 'unavailable' refuses here WITHOUT dereferencing a\n // null Sphere — which is the whole reason the state exists separately.\n const handshakeDecision = gate(this._walletState, !!this.session?.active, 'handshake', 'handshake');\n if (handshakeDecision.kind === 'refuse') {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Compatibility gate — runs BEFORE resume and BEFORE onConnectionRequest, so an\n // incompatible/old client cannot slip through on a stale sessionId.\n const result = checkCompatibility({\n clientProtocol: msg.v,\n walletProtocol: SPHERE_CONNECT_VERSION,\n clientNetwork: msg.network,\n walletNetworkId: this.snapshot.networkId ?? -1,\n minMinor: this.config.minMinorVersion,\n clientSdkVersion: msg.sdkVersion,\n minSdkVersion: this.config.minSdkVersion ?? DEFAULT_MIN_CLIENT_SDK_VERSION,\n });\n if (!result.ok) {\n logger.warn('ConnectHost', 'Rejected handshake', {\n dapp: dapp.name,\n reason: (result.error.data as { reason?: string } | undefined)?.reason,\n clientProtocol: msg.v,\n walletProtocol: SPHERE_CONNECT_VERSION,\n clientNetwork: msg.network ?? null,\n walletNetwork: this.snapshot.networkId ?? null,\n });\n this.config.onConnectionRejected?.(dapp, result.error, !!msg.silent);\n this.sendHandshakeResponse([], undefined, undefined, result.error, msg.v);\n return;\n }\n\n const clientInfo = { protocolVersion: msg.v, network: msg.network, sdkVersion: msg.sdkVersion };\n\n // Session resumption: if the client presents a valid existing sessionId,\n // skip the approval popup and restore the session without user interaction.\n // A resume DURING A LOCK succeeds and carries locked: true — a reloaded dApp is the\n // most common entry into this feature, and refusing it would leave that dApp with no\n // channel at all (it is not subscribed, so it would never receive wallet:unlocked).\n if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {\n const identity = locked ? this.snapshotIdentity() : this.getPublicIdentity();\n this.sendHandshakeResponse(\n [...this.grantedPermissions],\n this.session.id,\n identity,\n undefined,\n undefined,\n undefined,\n locked ? true : undefined,\n );\n if (locked) {\n this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n this.notifyLockedRequest('handshake', 'handshake');\n }\n return;\n }\n\n const requestedPermissions = msg.permissions as PermissionScope[];\n\n // FORCED SILENT while locked: the wallet's existing `if (silent) return { approved:\n // false }` branch refuses an unapproved origin with NO UI, and a previously approved\n // origin is approved from persisted state. No dApp request may ever raise a credential\n // surface, so a locked handshake must never be able to open one.\n const silent = msg.silent === true || locked;\n\n const { approved, grantedPermissions } = await withDeadline(\n Promise.resolve(\n this.config.onConnectionRequest(dapp, requestedPermissions, silent, clientInfo),\n ),\n this.config.handshakeDeadlineMs ?? DEFAULT_HANDSHAKE_DEADLINE_MS,\n () => {\n logger.warn('ConnectHost', 'Connection approval prompt timed out', { dapp: dapp.name });\n return { approved: false, grantedPermissions: [] as PermissionScope[] };\n },\n );\n\n // `locked` was read BEFORE a human-time await. Re-read the axis now, because the wallet can\n // have moved under us while the modal was open — an idle auto-lock, a cross-tab lock, a\n // logout, or an unlock.\n //\n // Getting this wrong in either direction is bad. Landing in 'locked' or 'unavailable' used\n // to mint an ACTIVE session on a non-live host while telling the dApp it was rejected: the\n // wallet then believes an origin is connected that has forgotten it exists, and under\n // 'unavailable' the gate refuses sphere_disconnect too, so that session is unclearable for\n // its whole TTL and onDisconnect never revokes the persisted origin approval. Landing in\n // 'live' (an unlock during the prompt) used to leave the dApp permanently walletLocked,\n // because the unlock edge had no session to notify at the time it fired.\n const stateAfterPrompt = this._walletState;\n if (stateAfterPrompt !== 'live' && stateAfterPrompt !== stateAtPrompt) {\n logger.warn(\n 'ConnectHost',\n `Wallet left 'live' while the approval prompt was open — refusing the handshake instead of minting a session (state=${stateAfterPrompt}, origin=${this.config.origin ?? 'unverified'})`,\n );\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n // Recompute so the response's `locked` flag and the trailing push describe NOW, not then.\n locked = stateAfterPrompt !== 'live';\n\n if (!approved) {\n // Deliberately NO notifyLockedRequest here. This origin was DENIED — it holds no\n // approval, so there is nothing for the user to unlock *for*, and counting it would put\n // an unvetted name in the wallet's own chrome and leak the lock to it. The notify stays\n // on the resume path and the approved path, where the origin demonstrably holds one.\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Create session\n const sessionId = createRequestId();\n const allPermissions = [...new Set([...DEFAULT_PERMISSIONS, ...grantedPermissions])];\n const ttl = this.config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;\n\n this.session = {\n id: sessionId,\n dapp,\n permissions: allPermissions,\n createdAt: Date.now(),\n expiresAt: ttl > 0 ? Date.now() + ttl : 0,\n active: true,\n };\n this.grantedPermissions = new Set(allPermissions);\n\n // Auto-push identity:changed to dApp whenever the wallet switches address.\n // MetaMask pattern: no sphere_subscribe needed — host pushes it unconditionally.\n // SKIPPED while locked: there is no Sphere to attach to. updateSphere() arms it on the\n // locked -> live edge, so no extra bookkeeping is needed.\n if (!locked) this.autoSubscribeIdentityChanged();\n\n // Build public identity — from the snapshot while locked.\n const identity = locked ? this.snapshotIdentity() : this.getPublicIdentity();\n\n this.sendHandshakeResponse(\n allPermissions,\n sessionId,\n identity,\n undefined,\n undefined,\n undefined,\n locked ? true : undefined,\n );\n if (locked) this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n }\n\n // `warning` is a forward-compatible deprecation-notice slot (see SphereHandshake.warning);\n // no call site emits one yet — reserved for the deprecation-window policy.\n private sendHandshakeResponse(\n permissions: string[],\n sessionId: string | undefined,\n identity: PublicIdentity | undefined,\n error?: SphereRpcError,\n echoV?: string,\n warning?: SphereRpcError,\n locked?: boolean,\n ): void {\n const network: NetworkInfo | undefined =\n typeof this.snapshot.networkId === 'number' ? { id: this.snapshot.networkId } : undefined;\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: (error && echoV ? echoV : SPHERE_CONNECT_VERSION) as typeof SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'response',\n permissions,\n sessionId,\n identity,\n network,\n sdkVersion: SDK_VERSION,\n error,\n warning,\n ...(locked ? { locked: true } : {}),\n });\n }\n\n // ===========================================================================\n // RPC Requests (query)\n // ===========================================================================\n\n private async handleRpcRequest(msg: SphereRpcRequest): Promise<void> {\n // 1. Session check\n if (!this.session?.active) {\n this.sendError(msg.id, ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n return;\n }\n\n // 2. Session expiry — BEFORE the lock gate. A dead session must never be advertised\n // as \"retry after unlock\", and the check needs no Sphere.\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n // Answer THIS request before revoking: revokeSession() pushes wallet:disconnected, and\n // a client that cleans up on that event would otherwise reject this id locally before\n // our own frame arrives.\n this.sendError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n this.revokeSession();\n return;\n }\n\n // 3. Rate limit — BEFORE the lock gate, because this is what bounds onLockedRequest\n // volume. A dApp polling in a loop while locked is throttled by the existing\n // limiter, which is why there is no second anti-spam mechanism.\n if (!this.checkRateLimit()) {\n this.sendError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // 4. Disconnect, BEFORE the lock gate. It has to work in EVERY wallet state, and the gate\n // does not allow that: 'unavailable' refuses every query with NOT_CONNECTED, so a\n // session created just before Sphere went away could never be dropped — it sat active\n // for its whole TTL (24 h by default) and onDisconnect, the only hook that revokes the\n // persisted origin approval, never fired. Also before the permission check, because\n // sphere_disconnect has no mapped permission. It stays AFTER the rate limiter so a\n // disconnect flood is still throttled.\n if (msg.method === RPC_METHODS.DISCONNECT) {\n const disconnectedSession = this.session;\n // Answer BEFORE revoking, for the same reason as the expiry branch above.\n this.sendResult(msg.id, { disconnected: true });\n this.revokeSession();\n if (disconnectedSession && this.config.onDisconnect) {\n // Fire-and-forget: don't block the response\n Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch((err) => logger.warn('Connect', 'onDisconnect handler error', err));\n }\n return;\n }\n\n // 5. Lock gate — BEFORE the permission check, because hasMethodPermission() is false\n // for anything unmapped, so a locked unknown method would otherwise answer\n // PERMISSION_DENIED 4002: an un-retryable code that tells the dApp its permissions\n // are wrong.\n const decision = gate(this._walletState, true, 'query', msg.method);\n if (decision.kind === 'refuse') {\n this.sendError(msg.id, decision.error.code, decision.error.message, decision.error.data);\n if (decision.error.code === ERROR_CODES.WALLET_LOCKED) {\n logger.debug('ConnectHost', `Refused query ${msg.method} — WALLET_LOCKED 4009 (origin=${this.config.origin ?? 'unverified'})`);\n this.notifyLockedRequest('query', msg.method);\n }\n return;\n }\n\n\n // 5. Permission check\n if (!hasMethodPermission(this.grantedPermissions, msg.method)) {\n this.sendError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for ${msg.method}`);\n return;\n }\n\n // 6. Snapshot answer. Only sphere_getIdentity reaches this branch (host-state.ts).\n // An absent snapshot identity REFUSES: `undefined` sent as a SUCCESS result reads\n // to a dApp as \"the wallet has no identity\".\n if (decision.kind === 'serve-from-snapshot') {\n const identity = this.snapshotIdentity();\n if (!identity) {\n this.sendError(msg.id, ERROR_CODES.WALLET_LOCKED, WALLET_LOCKED_MESSAGE, { reason: 'locked' });\n this.notifyLockedRequest('query', msg.method);\n return;\n }\n this.sendResult(msg.id, identity);\n return;\n }\n\n // 7. Register BEFORE the first await, with the timer armed at insertion time, then\n // execute. Every send goes through settle(), which returns null when a lock, a\n // revoke, a destroy or the deadline already answered this id — the whole\n // \"exactly one frame per id\" mechanism.\n this.inFlight.add(msg.id, 'query', this.config.requestDeadlineMs ?? DEFAULT_REQUEST_DEADLINE_MS);\n try {\n const result = await this.executeMethod(msg.method, msg.params ?? {});\n if (!this.inFlight.settle(msg.id)) return;\n this.sendResult(msg.id, result);\n } catch (error) {\n if (!this.inFlight.settle(msg.id)) return;\n // Our OWN SphereError messages are DX ('Missing required parameter: identifier',\n // 'Communications module not available') and stay. Anything else is internal JS text\n // that must not cross the trust boundary into a third-party dApp.\n // instanceof is unreliable across bundle copies — check the name too.\n const isSphereError =\n error instanceof SphereError || (error as { name?: string })?.name === 'SphereError';\n if (isSphereError) {\n const e = error as SphereError;\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, e.message, { reason: e.code });\n return;\n }\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n }\n }\n\n // ===========================================================================\n // Intent Requests\n // ===========================================================================\n\n private async handleIntentRequest(msg: SphereIntentRequest): Promise<void> {\n // 1. Session check\n if (!this.session?.active) {\n this.sendIntentError(msg.id, ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n return;\n }\n\n // 2. Session expiry — BEFORE the lock gate (same reason as the query path).\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n // Answer first — see the query path.\n this.sendIntentError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n this.revokeSession();\n return;\n }\n\n // 3. Rate limit. checkRateLimit() was called only from handleRpcRequest, so the intent\n // path — the money path, the one that opens wallet modals — was entirely unmetered.\n if (!this.checkRateLimit()) {\n this.sendIntentError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // 4. Lock gate. NO allow-list: every intent needs a live wallet.\n const decision = gate(this._walletState, true, 'intent', msg.action);\n if (decision.kind === 'refuse') {\n this.sendIntentError(msg.id, decision.error.code, decision.error.message, decision.error.data);\n if (decision.error.code === ERROR_CODES.WALLET_LOCKED) {\n logger.debug('ConnectHost', `Refused intent ${msg.action} — WALLET_LOCKED 4009 (origin=${this.config.origin ?? 'unverified'})`);\n this.notifyLockedRequest('intent', msg.action);\n }\n return;\n }\n\n // 5. Permission check\n if (!hasIntentPermission(this.grantedPermissions, msg.action)) {\n this.sendIntentError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for intent: ${msg.action}`);\n return;\n }\n\n // 6. Register BEFORE the first await, then delegate. The entry owns the deadline and\n // the AbortController, so lock / revoke / unavailable / destroy / deadline all\n // settle this id exactly once.\n const session = this.session;\n const entry = this.inFlight.add(\n msg.id,\n 'intent',\n this.config.intentDeadlineMs ?? DEFAULT_INTENT_DEADLINE_MS,\n );\n const ctx: IntentContext = {\n origin: this.config.origin,\n expiresAt: entry.deadline,\n signal: entry.controller.signal,\n };\n\n try {\n const autoHandler = ALWAYS_ASK_INTENTS.has(msg.action) ? undefined : this.autoApprovedIntents.get(msg.action);\n const response = autoHandler\n ? await autoHandler(msg.action, msg.params, session)\n : await this.config.onIntent(msg.action, msg.params, session, ctx);\n\n if (!this.inFlight.settle(msg.id)) return;\n if (response.error) {\n // Not relayed blindly: two of these codes describe the CHANNEL and say nothing true\n // about a spend the wallet has already taken ownership of. Everything else — including\n // USER_REJECTED, a legitimate pre-submission decline — passes through untouched. See\n // CHANNEL_ONLY_CODES.\n const asserts = CHANNEL_ONLY_CODES.has(response.error.code);\n if (asserts) {\n logger.warn(\n 'ConnectHost',\n `Wallet answered intent ${msg.action} with ${response.error.code}, which describes the channel rather than the spend — downgrading to INTENT_OUTCOME_UNKNOWN (origin=${this.config.origin ?? 'unverified'})`,\n );\n }\n this.sendIntentError(\n msg.id,\n asserts ? ERROR_CODES.INTENT_OUTCOME_UNKNOWN : response.error.code,\n asserts ? INTENT_UNKNOWN_MESSAGE : response.error.message,\n asserts ? undefined : response.error.data,\n );\n } else {\n this.sendIntentResult(msg.id, response.result);\n }\n } catch (error) {\n // A throw from onIntent or from an auto-approve handler used to reach\n // handleMessage's catch, which sent NOTHING — the dApp then hung for its full\n // intentTimeout and ended with a bare Error('Intent timeout: …') carrying no .code.\n logger.warn('ConnectHost', `Intent handler threw: ${msg.action}`, error);\n if (!this.inFlight.settle(msg.id)) return;\n this.sendIntentError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n }\n }\n\n // ===========================================================================\n // Method Router\n // ===========================================================================\n\n private async executeMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n // Subscription bookkeeping needs NO Sphere and is allow-listed while locked\n // (host-state.ts), so it must be answered BEFORE the dereference below — otherwise a\n // locked sphere_unsubscribe dies on requireSphere() with -32603 despite the gate\n // having let it through.\n switch (method) {\n case RPC_METHODS.SUBSCRIBE:\n return this.handleSubscribe(params.event as string);\n case RPC_METHODS.UNSUBSCRIBE:\n return this.handleUnsubscribe(params.event as string);\n }\n\n // ONE dereference for the whole router. The gate has already proven _walletState is\n // 'live' before we get here, so this is defence in depth — and it is what makes the\n // nullable field compile without a single `!`.\n const sphere = this.requireSphere();\n // §4 wire-compat (payments-compat.ts): the old query results are built from\n // facade reads. `sphere.payments` is read per branch, never up here — the\n // getter throws while no vertical runs and GET_IDENTITY must still answer.\n switch (method) {\n case RPC_METHODS.GET_IDENTITY:\n return this.getPublicIdentity();\n\n case RPC_METHODS.GET_BALANCE:\n case RPC_METHODS.GET_ASSETS:\n return sphere.payments.assets(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_FIAT_BALANCE:\n return { fiatBalance: sumFiatUsd(await sphere.payments.assets()) };\n\n case RPC_METHODS.GET_TOKENS:\n return this.stripTokenSdkData(\n sphere.payments.tokens(params.coinId ? { coinId: params.coinId as string } : undefined),\n );\n\n case RPC_METHODS.GET_HISTORY: {\n // Flat entry array on the wire; entries already carry the consumed shape\n // (`timestamp` mapped from the server's `ts`, plus symbol/tokenIds).\n const limit = typeof params.limit === 'number' && Number.isFinite(params.limit)\n ? params.limit\n : undefined;\n if (limit !== undefined) return (await sphere.payments.history({ limit })).entries;\n // INVARIANT: the legacy wire has no cursor — completeness is the contract.\n // Parameterless sphere_getHistory returned the ENTIRE ledger, so follow\n // the facade's cursors until the record is exhausted.\n let page = await sphere.payments.history(undefined);\n const entries = [...page.entries];\n while (page.more && page.cursor !== null) {\n page = await sphere.payments.history({ before: page.cursor });\n entries.push(...page.entries);\n }\n return entries;\n }\n\n case RPC_METHODS.RESOLVE:\n if (!params.identifier) {\n throw new SphereError('Missing required parameter: identifier', 'VALIDATION_ERROR');\n }\n return sphere.resolve(params.identifier as string);\n\n case RPC_METHODS.SUBSCRIBE:\n return this.handleSubscribe(params.event as string);\n\n case RPC_METHODS.UNSUBSCRIBE:\n return this.handleUnsubscribe(params.event as string);\n\n case RPC_METHODS.GET_CONVERSATIONS: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n const convos = comms.getConversations();\n const result: Array<{\n peerPubkey: string;\n peerNametag?: string;\n lastMessage: ConnectDirectMessage;\n unreadCount: number;\n messageCount: number;\n }> = [];\n // Collect conversations and track which ones need nametag resolution\n const needsResolve: Array<{ index: number; peerPubkey: string }> = [];\n for (const [peer, messages] of convos) {\n if (messages.length === 0) continue;\n const last = messages[messages.length - 1];\n // Find peer nametag from any message in the conversation\n const peerNametag =\n messages.find(m => m.senderPubkey === peer && m.senderNametag)?.senderNametag\n ?? messages.find(m => m.recipientPubkey === peer && m.recipientNametag)?.recipientNametag;\n const idx = result.length;\n result.push({\n peerPubkey: peer,\n peerNametag,\n lastMessage: last,\n unreadCount: comms.getUnreadCount(peer),\n messageCount: messages.length,\n });\n if (!peerNametag) {\n needsResolve.push({ index: idx, peerPubkey: peer });\n }\n }\n // Resolve missing nametags via transport (parallel, best-effort)\n if (needsResolve.length > 0) {\n const resolved = await Promise.all(\n needsResolve.map(({ peerPubkey }) =>\n comms.resolvePeerNametag(peerPubkey).catch((err) => { logger.debug('Connect', 'Peer Unicity ID resolution failed', err); return undefined; }),\n ),\n );\n for (let i = 0; i < needsResolve.length; i++) {\n if (resolved[i]) {\n result[needsResolve[i].index].peerNametag = resolved[i];\n }\n }\n }\n result.sort((a, b) => b.lastMessage.timestamp - a.lastMessage.timestamp);\n return result;\n }\n\n case RPC_METHODS.GET_MESSAGES: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n if (!params.peerPubkey) throw new SphereError('Missing required parameter: peerPubkey', 'VALIDATION_ERROR');\n return comms.getConversationPage(\n params.peerPubkey as string,\n {\n limit: params.limit as number | undefined,\n before: params.before as number | undefined,\n },\n );\n }\n\n case RPC_METHODS.GET_DM_UNREAD_COUNT: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n return {\n unreadCount: comms.getUnreadCount(\n params.peerPubkey as string | undefined,\n ),\n };\n }\n\n case RPC_METHODS.MARK_AS_READ: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n if (!params.messageIds || !Array.isArray(params.messageIds)) {\n throw new SphereError('Missing required parameter: messageIds (string[])', 'VALIDATION_ERROR');\n }\n await comms.markAsRead(params.messageIds as string[]);\n return { marked: true, count: (params.messageIds as string[]).length };\n }\n\n default:\n throw new SphereError(`Unknown method: ${method}`, 'VALIDATION_ERROR');\n }\n }\n\n // ===========================================================================\n // Event Subscriptions\n // ===========================================================================\n\n private autoSubscribeIdentityChanged(): void {\n if (this.eventSubscriptions.has(WALLET_EVENTS.IDENTITY_CHANGED)) return;\n const unsub = this.requireSphere().on('identity:changed' as SphereEventType, (data: unknown) => {\n this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, data);\n });\n this.eventSubscriptions.set(WALLET_EVENTS.IDENTITY_CHANGED, unsub);\n }\n\n private handleSubscribe(eventName: string): { subscribed: boolean; event: string } {\n if (!eventName) throw new SphereError('Missing required parameter: event', 'VALIDATION_ERROR');\n\n // The four auto-pushed events must NOT be attached to Sphere.on(): it accepts any string\n // and never emits for them, so the subscription would deliver nothing forever. But the\n // answer is SUCCESS, not an error — the host pushes them unconditionally, so \"you are\n // subscribed\" is true, just satisfied by a different mechanism.\n //\n // Throwing here surfaced as INTERNAL_ERROR -32603 and silently broke every dApp built\n // before 2.1: `client.on('wallet:locked', …)` fires sphere_subscribe fire-and-forget, and\n // on 2.0 that call succeeded. A MINOR bump must not turn a working call into a crash. The\n // 2.1 client skips the call altogether, so this path exists only for older dApps.\n if (isAutoPushedEvent(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n // While locked there is no Sphere to attach to: record the KEY and answer success.\n // Refusing would be a bug, not a policy — ConnectClient.on() fires sphere_subscribe\n // fire-and-forget and never retries, so the stream would die forever after one lock.\n if (this._walletState === 'locked') {\n this.suspendedSubscriptions.add(eventName);\n return { subscribed: true, event: eventName };\n }\n\n if (this.eventSubscriptions.has(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n const sphere = this.requireSphere();\n\n // §4 wire-compat (payments-compat.ts): on a v2-facade host the old event names have no\n // bus emitter — re-emit them from the v2 events so nothing a dApp subscribes to silently\n // stops firing. Keyed under the OLD name, so unsubscribe and the lock snapshot/replay\n // bookkeeping work unchanged.\n {\n const compatUnsub = attachCompatEvent(sphere, eventName, (data) =>\n this.pushClientEvent(eventName, data),\n );\n if (compatUnsub) {\n this.eventSubscriptions.set(eventName, compatUnsub);\n return { subscribed: true, event: eventName };\n }\n }\n\n const unsub = sphere.on(eventName as SphereEventType, (data: unknown) => {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'event',\n event: eventName,\n data,\n });\n });\n\n this.eventSubscriptions.set(eventName, unsub);\n return { subscribed: true, event: eventName };\n }\n\n private handleUnsubscribe(eventName: string): { unsubscribed: boolean; event: string } {\n if (!eventName) throw new SphereError('Missing required parameter: event', 'VALIDATION_ERROR');\n\n const unsub = this.eventSubscriptions.get(eventName);\n if (unsub) {\n unsub();\n this.eventSubscriptions.delete(eventName);\n }\n // Also drop it from the lock snapshot: sphere_unsubscribe is on the locked allow-list,\n // so without this the next updateSphere() re-arm would resurrect the stream.\n this.suspendedSubscriptions.delete(eventName);\n return { unsubscribed: true, event: eventName };\n }\n\n private cleanupEventSubscriptions(): void {\n for (const [, unsub] of this.eventSubscriptions) {\n unsub();\n }\n this.eventSubscriptions.clear();\n }\n\n // ===========================================================================\n // Helpers\n // ===========================================================================\n\n /** Push an event to the dApp without requiring a sphere_subscribe call. */\n private pushClientEvent(event: string, data: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'event',\n event,\n data,\n });\n }\n\n /** The bound Sphere, or a typed refusal. The ONLY way the router may reach Sphere.\n * Unreachable in practice — the gate guarantees 'live' before the router is entered —\n * so this is defence in depth, not the primary mechanism. */\n private requireSphere(): SphereInstance {\n if (!this.sphere) {\n throw new SphereError(\n this._walletState === 'locked' ? WALLET_LOCKED_MESSAGE : 'Wallet unavailable',\n 'NOT_INITIALIZED',\n );\n }\n return this.sphere;\n }\n\n /** SNAPSHOT read. `undefined` means \"we never saw an identity\": callers MUST refuse,\n * never answer undefined-as-success — a dApp reads that as \"the wallet has no\n * identity\". Two explicit methods instead of one dual-mode method, so nobody can serve\n * a snapshot believing it is live. */\n private snapshotIdentity(): PublicIdentity | undefined {\n return this.snapshot.identity;\n }\n\n /** InFlightRegistry.onExpire sink. Filled in a later task; declared here so the\n * constructor can wire it. */\n private settleExpired(entry: InFlightEntry): void {\n logger.warn(\n 'ConnectHost',\n `Host deadline reached, answering on our own: ${entry.kind} ${entry.id} (origin=${this.config.origin ?? 'unverified'})`,\n );\n if (entry.kind === 'query') {\n this.sendError(entry.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n } else {\n // NOT INTENT_CANCELLED. The wallet was handed this intent and may already have submitted\n // the transfer — the deadline says only that we stopped waiting for the answer, never\n // that nothing happened. Asserting a cancel here is how a dApp re-offers a payment that\n // already went through.\n this.sendIntentError(entry.id, ERROR_CODES.INTENT_OUTCOME_UNKNOWN, INTENT_UNKNOWN_MESSAGE);\n }\n }\n\n /** Answer every request already in flight with one coded frame each. A request in flight\n * when the Sphere goes away otherwise answers -32603 with a raw JS message, returns\n * `undefined` AS SUCCESS (sphere_getIdentity), or — for a delegated intent — never\n * answers at all until the client's own 120 s timeout. */\n private settleInFlight(code: number, message: string, data?: WalletLockedData): void {\n for (const entry of this.inFlight.settleAll()) {\n if (entry.kind === 'query') {\n this.sendError(entry.id, code, message, data);\n continue;\n }\n // An INTENT is never answered with the caller's code. 4009 invites a retry after the\n // unlock and 4001 reads as \"never happened\", but this intent was already delegated to\n // the wallet: the user may have confirmed it and the transfer may be on the wire. Only\n // \"outcome unknown\" is true, and it explicitly forbids a retry.\n this.sendIntentError(entry.id, ERROR_CODES.INTENT_OUTCOME_UNKNOWN, INTENT_UNKNOWN_MESSAGE);\n }\n }\n\n /**\n * Notify-only. The host has ALREADY answered and never waits for the wallet.\n *\n * The wallet's only permitted reaction is a PASSIVE badge in its PERMANENT chrome; a\n * dApp request may trigger a CONSENT prompt but never a credential prompt. Volume is\n * bounded by checkRateLimit(), which guards all three entry points — there is no\n * coalescing, no cooldown and no cap by design.\n *\n * A throwing handler must not break the host.\n */\n private notifyLockedRequest(kind: LockedRequestContext['kind'], name: string): void {\n try {\n this.config.onLockedRequest?.({ origin: this.config.origin, kind, name });\n } catch (err) {\n logger.warn('ConnectHost', 'onLockedRequest handler error', err);\n }\n }\n\n /** Last-resort answer for a handler that threw before its own catch could run.\n * Id-bearing frames get a coded error (InFlightRegistry guarantees exactly one answer\n * per id); a handshake gets today's empty refusal, because a failed handshake must\n * reveal nothing. */\n private sendUnhandledError(msg: SphereConnectMessage, error: unknown): void {\n if (msg.type === 'request') {\n if (!this.inFlight.settle(msg.id) && this.inFlight.has(msg.id)) return;\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n return;\n }\n if (msg.type === 'intent') {\n this.inFlight.settle(msg.id);\n this.sendIntentError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n return;\n }\n if (msg.type === 'handshake' && msg.direction === 'request') {\n logger.warn('ConnectHost', 'Handshake handler threw; sending the empty refusal', error);\n this.sendHandshakeResponse([], undefined, undefined);\n }\n }\n\n private getPublicIdentity(): PublicIdentity | undefined {\n const id = this.requireSphere().identity;\n if (!id) return undefined;\n return {\n chainPubkey: id.chainPubkey,\n directAddress: id.directAddress,\n nametag: id.nametag,\n };\n }\n\n private stripTokenSdkData(tokens: unknown[]): unknown[] {\n return tokens.map((t) => {\n const token = t as Record<string, unknown>;\n // Return all fields except internal sdkData\n const { sdkData: _sdkData, ...publicFields } = token;\n return publicFields;\n });\n }\n\n private sendResult(id: string, result: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n result,\n });\n }\n\n private sendError(id: string, code: number, message: string, data?: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n error: { code, message, ...(data !== undefined ? { data } : {}) },\n });\n }\n\n private sendIntentResult(id: string, result: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent_result',\n id,\n result,\n });\n }\n\n private sendIntentError(id: string, code: number, message: string, data?: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent_result',\n id,\n error: { code, message, ...(data !== undefined ? { data } : {}) },\n });\n }\n\n private checkRateLimit(): boolean {\n const maxRps = this.config.maxRequestsPerSecond ?? DEFAULT_MAX_RPS;\n const now = Date.now();\n if (now > this.rateLimitResetAt) {\n this.rateLimitCounter = 0;\n this.rateLimitResetAt = now + 1000;\n }\n this.rateLimitCounter++;\n return this.rateLimitCounter <= maxRps;\n }\n}\n","/**\n * ConnectClient — dApp side of Sphere Connect.\n *\n * Lightweight client that communicates with a wallet's ConnectHost\n * through a ConnectTransport. Provides query and intent methods\n * that mirror the Sphere SDK API.\n *\n * Zero EXTERNAL dependencies: nothing from npm reaches a dApp through this file. It does use a\n * few SDK-internal modules (core/logger, and constants by way of connect/protocol's\n * SPHERE_NETWORKS), which are bundled into the Connect entry points.\n */\n\nimport { logger } from '../../core/logger';\nimport type { ConnectTransport, ConnectClientConfig, ConnectResult, ConnectEventHandler } from '../types';\nimport type {\n SphereConnectMessage,\n SphereHandshake,\n DAppMetadata,\n PublicIdentity,\n NetworkInfo,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n ERROR_CODES,\n WALLET_EVENTS,\n createRequestId,\n isAutoPushedEvent,\n} from '../protocol';\nimport { SDK_VERSION } from '../version';\nimport { ALL_PERMISSIONS } from '../permissions';\nimport type { PermissionScope } from '../permissions';\n\n/** Error thrown by ConnectClient carrying the host's structured rejection.\n * Discriminate on the numeric `.code` (bundle-safe), not `instanceof`. */\nexport class ConnectError extends Error {\n constructor(message: string, public readonly code: number, public readonly data?: unknown) {\n super(message);\n this.name = 'ConnectError';\n }\n}\n\nconst DEFAULT_TIMEOUT = 30000;\nconst DEFAULT_INTENT_TIMEOUT = 120000;\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * A QUERY may be failed with anything truthful — it is idempotent and safe to retry. An\n * INTENT may not: once it reached the wallet, the money may already be moving, so no local\n * teardown is allowed to tell the dApp it did not happen.\n */\n kind: 'query' | 'intent';\n}\n\nexport class ConnectClient {\n private readonly transport: ConnectTransport;\n private readonly dapp: DAppMetadata;\n private readonly requestedPermissions: PermissionScope[];\n private readonly timeout: number;\n private readonly intentTimeout: number;\n\n private readonly resumeSessionId: string | null;\n private readonly silent: boolean;\n private readonly network: NetworkInfo | undefined;\n\n private sessionId: string | null = null;\n private grantedPermissions: PermissionScope[] = [];\n private identity: PublicIdentity | null = null;\n private walletNet: NetworkInfo | null = null;\n private walletProto: string | null = null;\n private locked = false;\n private connected = false;\n\n private pendingRequests: Map<string, PendingRequest> = new Map();\n private eventHandlers: Map<string, Set<ConnectEventHandler>> = new Map();\n private unsubscribeTransport: (() => void) | null = null;\n\n // Handshake resolver (one-shot)\n private handshakeResolver: {\n resolve: (value: ConnectResult) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n } | null = null;\n\n constructor(config: ConnectClientConfig) {\n this.transport = config.transport;\n this.dapp = config.dapp;\n this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;\n this.resumeSessionId = config.resumeSessionId ?? null;\n this.silent = config.silent ?? false;\n this.network = config.network;\n }\n\n // ===========================================================================\n // Connection\n // ===========================================================================\n\n /** Connect to the wallet. Returns session info and public identity. */\n async connect(): Promise<ConnectResult> {\n // Start listening\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\n\n return new Promise<ConnectResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.handshakeResolver = null;\n reject(new Error('Connection timeout'));\n }, this.timeout);\n\n this.handshakeResolver = { resolve, reject, timer };\n\n // Send handshake request\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'request',\n permissions: this.requestedPermissions,\n dapp: this.dapp,\n sdkVersion: SDK_VERSION,\n ...(this.network ? { network: this.network } : {}),\n ...(this.resumeSessionId ? { sessionId: this.resumeSessionId } : {}),\n ...(this.silent ? { silent: true } : {}),\n });\n });\n }\n\n /** Disconnect from the wallet */\n async disconnect(): Promise<void> {\n if (this.connected) {\n try {\n await this.query(RPC_METHODS.DISCONNECT);\n } catch {\n // Ignore errors during disconnect\n }\n }\n this.cleanup();\n }\n\n /** Whether currently connected */\n get isConnected(): boolean {\n return this.connected;\n }\n\n /** Granted permission scopes */\n get permissions(): readonly PermissionScope[] {\n return this.grantedPermissions;\n }\n\n /** Current session ID */\n get session(): string | null {\n return this.sessionId;\n }\n\n /** Public identity received during handshake */\n get walletIdentity(): PublicIdentity | null {\n return this.identity;\n }\n\n /** Wallet's active network, received during handshake. */\n get walletNetwork(): NetworkInfo | null {\n return this.walletNet;\n }\n\n /**\n * The wallet's Connect protocol version, captured from the handshake response `v`.\n * Null before the first handshake response and after a disconnect.\n *\n * Feature-detect with it: compare against '2.1' to decide whether the wallet can be\n * trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys\n * the session on lock and never emits either, so a dApp waiting for them against one\n * waits forever.\n *\n * CAVEAT: on an ERROR response the host echoes the dApp's own `v` back\n * (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the\n * dApp's version rather than the wallet's. Only trust it after a successful handshake.\n */\n get walletProtocol(): string | null {\n return this.walletProto;\n }\n\n /**\n * Whether the wallet was locked at the last handshake or lifecycle event.\n * A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.\n * Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME\n * session — do not disconnect, do not clear the session, do not re-handshake.\n */\n get walletLocked(): boolean {\n return this.locked;\n }\n\n // ===========================================================================\n // Query (read data)\n // ===========================================================================\n\n /** Send a query request and return the result */\n async query<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {\n if (!this.connected) throw new ConnectError('Not connected', ERROR_CODES.NOT_CONNECTED);\n\n const id = createRequestId();\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Query timeout: ${method}`));\n }, this.timeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\n kind: 'query',\n });\n\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'request',\n id,\n method,\n params,\n });\n });\n }\n\n // ===========================================================================\n // Intent (trigger wallet UI)\n // ===========================================================================\n\n /** Send an intent request. The wallet will open its UI for user confirmation. */\n async intent<T = unknown>(action: string, params: Record<string, unknown>): Promise<T> {\n if (!this.connected) throw new ConnectError('Not connected', ERROR_CODES.NOT_CONNECTED);\n\n const id = createRequestId();\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n // TYPED, and specifically NOT a cancellation. We stopped waiting; the wallet may have\n // submitted the transfer. A dApp that retries here can pay twice, so the code says\n // \"outcome unknown\" and the docs forbid a retry (see ERROR_CODES.INTENT_OUTCOME_UNKNOWN).\n reject(\n new ConnectError(\n `Intent outcome unknown — do not retry; reconcile before acting: ${action}`,\n ERROR_CODES.INTENT_OUTCOME_UNKNOWN,\n ),\n );\n }, this.intentTimeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\n kind: 'intent',\n });\n\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent',\n id,\n action,\n params,\n });\n });\n }\n\n // ===========================================================================\n // Events\n // ===========================================================================\n\n /** Subscribe to a wallet event. Returns unsubscribe function. */\n on(event: string, handler: ConnectEventHandler): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n // Tell host to forward this event. Auto-pushed wallet events are NEVER routed through\n // sphere_subscribe: the host pushes them unconditionally and answers that RPC with success\n // without attaching anything (Sphere.on() would accept the name and never emit).\n if (this.connected && !isAutoPushedEvent(event)) {\n this.query(RPC_METHODS.SUBSCRIBE, { event }).catch((err) => logger.debug('Connect', 'Event subscription failed', err));\n }\n }\n this.eventHandlers.get(event)!.add(handler);\n\n return () => {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n this.eventHandlers.delete(event);\n if (this.connected && !isAutoPushedEvent(event)) {\n this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch((err) => logger.debug('Connect', 'Event unsubscription failed', err));\n }\n }\n }\n };\n }\n\n // ===========================================================================\n // Message Handling\n // ===========================================================================\n\n private handleMessage(msg: SphereConnectMessage): void {\n // Handshake response\n if (msg.type === 'handshake' && msg.direction === 'response') {\n this.handleHandshakeResponse(msg);\n return;\n }\n\n // RPC response (query)\n if (msg.type === 'response') {\n this.handlePendingResponse(msg.id, msg.result, msg.error);\n return;\n }\n\n // Intent result\n if (msg.type === 'intent_result') {\n this.handlePendingResponse(msg.id, msg.result, msg.error);\n return;\n }\n\n // Event\n if (msg.type === 'event') {\n // 0. Only an ESTABLISHED session may move this client's authoritative state.\n // These frames now decide `identity`, `locked` and `connected` — and the docs tell a\n // dApp to gate a money replay on comparing that identity. A PostMessage client accepts\n // frames from anything that can reach its window, so an injected iframe could\n // otherwise rewrite `walletIdentity` to an address of its choosing before any\n // handshake had happened. A pre-connect wallet event is meaningless in any case:\n // there is no session for it to describe.\n if (!this.connected || !this.sessionId) {\n logger.warn('Connect', `Ignoring wallet event before a session exists: ${msg.event}`);\n return;\n }\n\n // 1. Unconditional NON-DESTRUCTIVE state update, BEFORE any handler lookup, so a\n // dApp that registered no handler still ends up with correct client state. The\n // client privileged no wallet event before 2.1, so an unhandled\n // wallet:disconnected was dropped entirely.\n if (msg.event === WALLET_EVENTS.LOCKED) {\n this.locked = true;\n } else if (msg.event === WALLET_EVENTS.UNLOCKED) {\n this.locked = false;\n const identity = (msg.data as { identity?: PublicIdentity } | undefined)?.identity;\n if (identity) this.identity = identity;\n } else if (msg.event === WALLET_EVENTS.DISCONNECTED) {\n // So a handler reading isConnected sees the truth.\n this.connected = false;\n } else if (msg.event === WALLET_EVENTS.IDENTITY_CHANGED) {\n const data = msg.data as PublicIdentity | undefined;\n if (data && typeof data.chainPubkey === 'string') this.identity = data;\n }\n\n // 2. Dispatch to registered handlers.\n this.dispatchEvent(msg.event, msg.data);\n\n // 3. Destructive step LAST — cleanup() clears eventHandlers, so running it after\n // dispatch is what keeps the dApp's own wallet:disconnected callback alive.\n if (msg.event === WALLET_EVENTS.DISCONNECTED) {\n this.cleanup();\n }\n }\n }\n\n private dispatchEvent(event: string, data: unknown): void {\n const handlers = this.eventHandlers.get(event);\n if (!handlers) return;\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (err) {\n logger.debug('Connect', 'Event handler error', err);\n }\n }\n }\n\n private handleHandshakeResponse(msg: SphereConnectMessage & { type: 'handshake' }): void {\n if (!this.handshakeResolver) return;\n\n clearTimeout(this.handshakeResolver.timer);\n\n const m = msg as SphereHandshake;\n\n // Captured even on a refusal, but see the getter's caveat: the host echoes the dApp's\n // own `v` on an error response.\n this.walletProto = (msg as { v?: string }).v ?? null;\n\n if (m.error) {\n this.handshakeResolver.reject(new ConnectError(m.error.message, m.error.code, m.error.data));\n this.handshakeResolver = null;\n return;\n }\n\n if (msg.sessionId && msg.identity) {\n this.sessionId = msg.sessionId;\n this.grantedPermissions = msg.permissions as PermissionScope[];\n this.identity = msg.identity;\n this.walletNet = m.network ?? null;\n this.locked = m.locked === true;\n this.connected = true;\n if (m.warning) logger.warn('Connect', 'Wallet deprecation notice', m.warning.message);\n this.handshakeResolver.resolve({\n sessionId: msg.sessionId,\n permissions: this.grantedPermissions,\n identity: msg.identity,\n // A resume DURING a lock succeeds: the dApp is connected on the same session and\n // must not re-handshake. It will get wallet:unlocked when the user unlocks.\n ...(this.locked ? { locked: true } : {}),\n });\n } else {\n this.handshakeResolver.reject(new Error('Connection rejected by wallet'));\n }\n\n this.handshakeResolver = null;\n }\n\n private handlePendingResponse(\n id: string,\n result: unknown,\n error?: { code: number; message: string; data?: unknown },\n ): void {\n const pending = this.pendingRequests.get(id);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this.pendingRequests.delete(id);\n\n if (error) {\n pending.reject(new ConnectError(error.message, error.code, error.data));\n } else {\n pending.resolve(result);\n }\n }\n\n // ===========================================================================\n // Cleanup\n // ===========================================================================\n\n private cleanup(): void {\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\n }\n\n // A handshake in flight has to be settled too. wallet:disconnected arriving mid-connect()\n // left it parked for the full client timeout and then rejected untyped, so a dApp could\n // not tell \"the wallet dropped us\" from \"the wallet is slow\".\n if (this.handshakeResolver) {\n clearTimeout(this.handshakeResolver.timer);\n this.handshakeResolver.reject(new ConnectError('Disconnected', ERROR_CODES.NOT_CONNECTED));\n this.handshakeResolver = null;\n }\n\n // Reject all pending requests with a TYPED error. new Error('Disconnected') carried no\n // .code, so a dApp could not tell it from a transport failure.\n //\n // A pending INTENT is rejected with INTENT_OUTCOME_UNKNOWN, never NOT_CONNECTED: the\n // wallet already had it and may have submitted the transfer, so \"not connected\" would read\n // as \"your spend never ran\" and invite a retry that pays twice.\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(\n pending.kind === 'intent'\n ? new ConnectError(\n 'Intent outcome unknown — do not retry; reconcile before acting',\n ERROR_CODES.INTENT_OUTCOME_UNKNOWN,\n )\n : new ConnectError('Disconnected', ERROR_CODES.NOT_CONNECTED),\n );\n }\n this.pendingRequests.clear();\n this.eventHandlers.clear();\n\n this.connected = false;\n this.sessionId = null;\n this.grantedPermissions = [];\n this.identity = null;\n this.walletNet = null;\n this.walletProto = null;\n this.locked = false;\n }\n}\n","/** Standard base64 (RFC 4648 §4, with padding), hand-rolled: no Buffer or atob, identical in Node and browsers. */\n\nconst ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst INVALID = 0xff;\nconst SEXTETS = new Uint8Array(128).fill(INVALID);\nfor (let i = 0; i < ALPHABET.length; i++) SEXTETS[ALPHABET.charCodeAt(i)] = i;\n\nexport function base64Encode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;\n const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;\n const triple = (bytes[i] << 16) | (b1 << 8) | b2;\n out += ALPHABET[triple >> 18] + ALPHABET[(triple >> 12) & 0x3f];\n out += i + 1 < bytes.length ? ALPHABET[(triple >> 6) & 0x3f] : '=';\n out += i + 2 < bytes.length ? ALPHABET[triple & 0x3f] : '=';\n }\n return out;\n}\n\n/** Decodes only the canonical spelling — padded, standard alphabet, zero padding bits. `null` for anything else. */\nexport function base64DecodeCanonical(text: string): Uint8Array | null {\n if (text.length % 4 !== 0) return null;\n const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0;\n const out = new Uint8Array((text.length / 4) * 3 - padding);\n let buffer = 0;\n let bits = 0;\n let offset = 0;\n for (let i = 0; i < text.length - padding; i++) {\n const code = text.charCodeAt(i);\n const sextet = code < SEXTETS.length ? SEXTETS[code] : INVALID;\n if (sextet === INVALID) return null;\n buffer = ((buffer << 6) | sextet) & 0xfff;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[offset++] = buffer >> bits;\n }\n }\n return (buffer & ((1 << bits) - 1)) === 0 ? out : null;\n}\n","/**\n * JSON wire form of NFT content for the `mint_nft` Connect intent: `NftContent` with every inline\n * `NftMedia.bytes` as base64, because Connect messages are JSON. Shape only — the value rules\n * (text, media types, URIs, the payload cap) stay in `encodeNftContent`.\n */\nimport { base64DecodeCanonical, base64Encode } from '../core/base64';\nimport { SphereError } from '../core/errors';\nimport type {\n NftAttribute,\n NftContent,\n NftLink,\n NftMedia,\n NftMediaRef,\n NftMetadata,\n} from '../token-engine/nft-payload';\n\n/** An inline file; `bytes` is standard base64 (RFC 4648 §4, with padding). */\nexport interface WireNftMedia {\n readonly kind: 'media';\n readonly media_type: string;\n readonly bytes: string;\n}\n\nexport interface WireNftMetadata {\n readonly kind: 'metadata';\n readonly name: string;\n readonly description: string | null;\n readonly image: WireNftMedia | NftLink | null;\n readonly animation_url: WireNftMedia | NftLink | null;\n readonly external_url: string | null;\n readonly attributes: readonly NftAttribute[];\n readonly collection: string | null;\n /** Hex, passed through unchanged; `encodeNftContent` judges its value. */\n readonly collection_id: string | null;\n}\n\nexport type WireNftContent = WireNftMetadata | WireNftMedia | NftLink;\n\n/** A type alias, not an interface, so a typed value passes as `ConnectClient.intent` params. */\nexport type MintNftIntentParams = {\n readonly content: WireNftContent;\n /** Default `true`: the wallet wraps the payload in NftSigned with its chain key as creator. */\n readonly sign?: boolean;\n};\n\nexport interface MintNftIntentResult {\n /** 64 lowercase hex. */\n readonly tokenId: string;\n}\n\ntype WireRecord = Record<string, unknown>;\n\nconst METADATA_FIELDS: readonly string[] = [\n 'kind', 'name', 'description', 'image', 'animation_url', 'external_url', 'attributes', 'collection', 'collection_id',\n];\nconst MEDIA_FIELDS: readonly string[] = ['kind', 'media_type', 'bytes'];\nconst LINK_FIELDS: readonly string[] = ['kind', 'media_type', 'uri', 'sha256'];\nconst ATTRIBUTE_FIELDS: readonly string[] = ['trait_type', 'value'];\nconst CONTENT_KINDS = \"must be 'metadata', 'media' or 'link'\";\nconst MEDIA_REF_KINDS = \"must be 'media' or 'link'\";\n\nfunction refuse(field: string, rule: string): SphereError {\n return new SphereError(`Invalid NFT ${field}: ${rule}`, 'VALIDATION_ERROR');\n}\n\n// ── to wire ─────────────────────────────────────────────────────────────────\n\nfunction mediaToWire(media: NftMedia, field: string): WireNftMedia {\n if (!(media.bytes instanceof Uint8Array)) throw refuse(`${field}.bytes`, 'must be a Uint8Array');\n return { kind: 'media', media_type: media.media_type, bytes: base64Encode(media.bytes) };\n}\n\nfunction linkToWire(link: NftLink): NftLink {\n return { kind: 'link', media_type: link.media_type, uri: link.uri, sha256: link.sha256 };\n}\n\nfunction mediaRefToWire(ref: NftMediaRef | null, field: string): WireNftMedia | NftLink | null {\n if (ref === null) return null;\n if (ref?.kind === 'media') return mediaToWire(ref, field);\n if (ref?.kind === 'link') return linkToWire(ref);\n throw refuse(`${field}.kind`, MEDIA_REF_KINDS);\n}\n\nfunction metadataToWire(metadata: NftMetadata): WireNftMetadata {\n return {\n kind: 'metadata',\n name: metadata.name,\n description: metadata.description,\n image: mediaRefToWire(metadata.image, 'content.image'),\n animation_url: mediaRefToWire(metadata.animation_url, 'content.animation_url'),\n external_url: metadata.external_url,\n attributes: metadata.attributes.map(({ trait_type, value }) => ({ trait_type, value })),\n collection: metadata.collection,\n collection_id: metadata.collection_id,\n };\n}\n\n/** `NftContent` → its JSON wire form. Inline bytes become base64; every other field is copied unchanged. */\nexport function nftContentToWire(content: NftContent): WireNftContent {\n switch (content?.kind) {\n case 'metadata':\n return metadataToWire(content);\n case 'media':\n return mediaToWire(content, 'content');\n case 'link':\n return linkToWire(content);\n default:\n throw refuse('content.kind', CONTENT_KINDS);\n }\n}\n\n// ── from wire ───────────────────────────────────────────────────────────────\n\nfunction recordAt(value: unknown, field: string, rule = 'must be an object'): WireRecord {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) throw refuse(field, rule);\n return value as WireRecord;\n}\n\nfunction requireExactFields(record: WireRecord, field: string, fields: readonly string[]): void {\n const missing = fields.find((name) => !Object.prototype.hasOwnProperty.call(record, name));\n if (missing !== undefined) throw refuse(`${field}.${missing}`, 'is missing — an absent optional field is null');\n const extra = Object.keys(record).find((name) => !fields.includes(name));\n if (extra !== undefined) throw refuse(`${field}.${extra}`, 'is not a field of this item');\n}\n\nfunction stringAt(record: WireRecord, field: string, name: string): string {\n const value = record[name];\n if (typeof value !== 'string') throw refuse(`${field}.${name}`, 'must be a string');\n return value;\n}\n\nfunction optionalStringAt(record: WireRecord, field: string, name: string): string | null {\n const value = record[name];\n if (value === null) return null;\n if (typeof value !== 'string') throw refuse(`${field}.${name}`, 'must be a string or null');\n return value;\n}\n\nfunction mediaFromWire(record: WireRecord, field: string): NftMedia {\n requireExactFields(record, field, MEDIA_FIELDS);\n const mediaType = stringAt(record, field, 'media_type');\n const bytes = base64DecodeCanonical(stringAt(record, field, 'bytes'));\n if (bytes === null) throw refuse(`${field}.bytes`, 'must be canonical standard base64 with padding (RFC 4648 §4)');\n return { kind: 'media', media_type: mediaType, bytes };\n}\n\nfunction linkFromWire(record: WireRecord, field: string): NftLink {\n requireExactFields(record, field, LINK_FIELDS);\n return {\n kind: 'link',\n media_type: stringAt(record, field, 'media_type'),\n uri: stringAt(record, field, 'uri'),\n sha256: stringAt(record, field, 'sha256'),\n };\n}\n\nfunction mediaRefFromWire(value: unknown, field: string): NftMediaRef | null {\n if (value === null) return null;\n const record = recordAt(value, field, 'must be a media or link object, or null');\n if (record.kind === 'media') return mediaFromWire(record, field);\n if (record.kind === 'link') return linkFromWire(record, field);\n throw refuse(`${field}.kind`, MEDIA_REF_KINDS);\n}\n\nfunction attributeFromWire(value: unknown, field: string): NftAttribute {\n const record = recordAt(value, field);\n requireExactFields(record, field, ATTRIBUTE_FIELDS);\n const traitType = stringAt(record, field, 'trait_type');\n const attributeValue = record.value;\n if (typeof attributeValue !== 'string' && typeof attributeValue !== 'number') {\n throw refuse(`${field}.value`, 'must be a string or a number');\n }\n return { trait_type: traitType, value: attributeValue };\n}\n\nfunction attributesFromWire(value: unknown, field: string): NftAttribute[] {\n if (!Array.isArray(value)) throw refuse(field, 'must be an array — [] when there are none');\n return Array.from(value, (entry: unknown, index) => attributeFromWire(entry, `${field}[${index}]`));\n}\n\nfunction metadataFromWire(record: WireRecord): NftMetadata {\n requireExactFields(record, 'content', METADATA_FIELDS);\n return {\n kind: 'metadata',\n name: stringAt(record, 'content', 'name'),\n description: optionalStringAt(record, 'content', 'description'),\n image: mediaRefFromWire(record.image, 'content.image'),\n animation_url: mediaRefFromWire(record.animation_url, 'content.animation_url'),\n external_url: optionalStringAt(record, 'content', 'external_url'),\n attributes: attributesFromWire(record.attributes, 'content.attributes'),\n collection: optionalStringAt(record, 'content', 'collection'),\n collection_id: optionalStringAt(record, 'content', 'collection_id'),\n };\n}\n\n/**\n * The wire form → `NftContent`. Checks shape and base64 only, throwing a `SphereError`\n * (`VALIDATION_ERROR`) whose message names the offending field.\n */\nexport function nftContentFromWire(wire: unknown): NftContent {\n const record = recordAt(wire, 'content');\n switch (record.kind) {\n case 'metadata':\n return metadataFromWire(record);\n case 'media':\n return mediaFromWire(record, 'content');\n case 'link':\n return linkFromWire(record, 'content');\n default:\n throw refuse('content.kind', CONTENT_KINDS);\n }\n}\n"],"mappings":"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","names":["identity"]}
1
+ {"version":3,"sources":["../../connect/index.ts","../../core/logger.ts","../../core/errors.ts","../../connect/semver.ts","../../constants.ts","../../connect/protocol.ts","../../connect/compatibility.ts","../../connect/version.ts","../../connect/permissions.ts","../../connect/host/payments-compat.ts","../../connect/host/host-state.ts","../../connect/host/WalletSnapshot.ts","../../connect/host/InFlightRegistry.ts","../../connect/host/ConnectHost.ts","../../connect/client/ConnectClient.ts","../../core/base64.ts","../../connect/nft-wire.ts"],"sourcesContent":["/**\n * Sphere Connect — Core (transport-agnostic)\n *\n * Host (wallet side):\n * import { ConnectHost } from '@unicitylabs/sphere-sdk/connect';\n *\n * Client (dApp side):\n * import { ConnectClient } from '@unicitylabs/sphere-sdk/connect';\n */\n\nexport { ConnectHost } from './host/ConnectHost';\nexport { ConnectClient, ConnectError } from './client/ConnectClient';\n\n// Protocol\nexport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n HOST_READY_TYPE,\n HOST_READY_TIMEOUT,\n RPC_METHODS,\n INTENT_ACTIONS,\n ERROR_CODES,\n WALLET_EVENTS,\n AUTO_PUSHED_EVENTS,\n isAutoPushedEvent,\n SPHERE_NETWORKS,\n isSphereConnectMessage,\n createRequestId,\n} from './protocol';\n\nexport type {\n RpcMethod,\n IntentAction,\n ErrorCode,\n WalletEvent,\n SphereRpcRequest,\n SphereRpcResponse,\n SphereIntentRequest,\n SphereIntentResult,\n SphereEventMessage,\n SphereHandshake,\n SphereRpcError,\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\n WalletLockedData,\n WalletLockedPayload,\n WalletUnlockedPayload,\n WalletDisconnectedPayload,\n WalletIdentityChangedPayload,\n NetworkInfo,\n} from './protocol';\n\n// Permissions\nexport {\n PERMISSION_SCOPES,\n ALL_PERMISSIONS,\n DEFAULT_PERMISSIONS,\n METHOD_PERMISSIONS,\n INTENT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n validatePermissions,\n} from './permissions';\n\nexport type { PermissionScope } from './permissions';\n\n// NFT wire form (mint_nft intent)\nexport { nftContentToWire, nftContentFromWire } from './nft-wire';\nexport type {\n MintNftIntentParams,\n MintNftIntentResult,\n WireNftContent,\n WireNftMedia,\n WireNftMetadata,\n} from './nft-wire';\nexport type {\n NftAttribute,\n NftContent,\n NftLink,\n NftMedia,\n NftMediaRef,\n NftMetadata,\n} from '../token-engine/nft-payload';\n\n// Types\nexport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n ConnectClientConfig,\n ConnectResult,\n ConnectEventHandler,\n WalletState,\n LockedRequestContext,\n IntentContext,\n} from './types';\n","/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * SDK Error Types\n *\n * Structured error codes for programmatic error handling in UI.\n * UI can switch on error.code to show appropriate user-facing messages.\n *\n * Errors thrown by provider code (the `./impl/*` bundles) are a different `SphereError` class\n * copy, so `instanceof SphereError` / `isSphereError()` is false for them: read `code` structurally.\n *\n * @example\n * ```ts\n * import { PartialSendConflictError, isPossiblyCommittedSendOutcome } from '@unicitylabs/sphere-sdk';\n *\n * try {\n * await sphere.payments.send({ recipient: '@alice', amount: '1000000', coinId });\n * } catch (err) {\n * if (err instanceof PartialSendConflictError) {\n * // Part of the amount already left the wallet and is final. Only err.remainingAmount is still owed:\n * // if you pay it, do it as a NEW send of exactly that amount, never the original amount.\n * showToast(`Partly sent: ${err.remainingAmount} base units were not sent`);\n * } else if (isPossiblyCommittedSendOutcome(err)) {\n * // The money may already have left the wallet: never call send() again for this payment.\n * showToast('Sent, waiting for confirmation');\n * } else {\n * switch ((err as { code?: unknown } | null)?.code) {\n * case 'SEND_INSUFFICIENT_BALANCE': showToast('Not enough funds'); break;\n * case 'INVALID_RECIPIENT': showToast('Recipient not found'); break;\n * case 'TRANSPORT_ERROR': showToast('Network connection issue'); break;\n * default: showToast(err instanceof Error ? err.message : String(err));\n * }\n * }\n * }\n * ```\n */\n\nexport type SphereErrorCode =\n | 'NOT_INITIALIZED'\n | 'ALREADY_INITIALIZED'\n | 'INVALID_CONFIG'\n | 'INVALID_IDENTITY'\n // Not thrown by the SDK: a send short of funds throws SEND_INSUFFICIENT_BALANCE.\n | 'INSUFFICIENT_BALANCE'\n | 'INVALID_RECIPIENT'\n | 'TRANSFER_FAILED'\n // The source state was consumed by a DIFFERENT transaction (lost race, not a\n // resume) — raised as TransferConflictError (token-engine/errors.ts, Part E.2).\n | 'TRANSFER_CONFLICT'\n // The op's certification is INDETERMINATE (submit accepted / proof fetch\n // inconclusive) — raised as ProofUnconfirmedError; keep the intent OPEN (#631).\n | 'CERTIFICATION_UNCONFIRMED'\n // Split burn checkpoint (sdk-changes E.4, sphere-sdk#501) — all keep-open (token-engine/errors.ts):\n // burn certified but the checkpoint could not be persisted (no mint submitted);\n | 'CHECKPOINT_PERSIST_FAILED'\n // a certified mint leaf has no reproducing stored bytes, or a stored checkpoint fails byte-binding;\n | 'SPLIT_CHECKPOINT_LOST'\n // a byte-bound checkpoint proof no longer verifies against the current trust base (validator rotation).\n | 'CHECKPOINT_TRUSTBASE_MISMATCH'\n | 'STORAGE_ERROR'\n // #665: the on-chain spend committed but the post-commit wallet-api mirror\n // sync (inventory apply / blob upload / save) failed. NOT a lost payment —\n // the intent is kept open and resume converges the mirror (idempotent apply,\n // #664). Surfaced distinctly so a UI can reassure (\"sent — wallet catching\n // up\") instead of showing a hard send failure.\n | 'SEND_SYNC_PENDING'\n // #677: a multi-leg send lost a source to a concurrent transfer AFTER ≥1\n // earlier leg had already certified on-chain AND been journaled/delivered.\n // The delivered value has irreversibly left the wallet, so this is NOT a\n // plain, re-sendable failure: surfacing a bare TransferConflictError makes\n // the caller re-send the FULL amount and pay the delivered leg twice. Raised\n // as PartialSendConflictError; the caller MUST re-plan ONLY the remainder\n // under a NEW transferId, never the whole amount.\n | 'SEND_PARTIALLY_COMPLETED'\n | 'TRANSPORT_ERROR'\n | 'AGGREGATOR_ERROR'\n | 'VALIDATION_ERROR'\n | 'INVALID_AMOUNT'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'DECRYPTION_ERROR'\n | 'MODULE_NOT_AVAILABLE'\n // #793: this Sphere was initialised with `walletApi: 'none'` — the explicit\n // messaging-only composition. Distinct from NOT_INITIALIZED, which is transient\n // (init in flight, mid address-switch, destroyed): this one never resolves.\n | 'PAYMENTS_NOT_COMPOSED'\n | 'SIGNING_ERROR'\n // Token Spend Queue error codes\n | 'SEND_QUEUE_TIMEOUT'\n | 'SEND_INSUFFICIENT_BALANCE'\n | 'SEND_RESERVATION_CANCELLED'\n | 'SEND_QUEUE_FULL'\n | 'MODULE_DESTROYED'\n | 'REENTRANT_GATE'\n // Invoice / Accounting error codes\n | 'INVOICE_NO_TARGETS'\n | 'INVOICE_INVALID_ADDRESS'\n | 'INVOICE_NO_ASSETS'\n | 'INVOICE_INVALID_ASSET'\n | 'INVOICE_INVALID_AMOUNT'\n | 'INVOICE_INVALID_COIN'\n | 'INVOICE_INVALID_NFT'\n | 'INVOICE_PAST_DUE_DATE'\n | 'INVOICE_DUPLICATE_ADDRESS'\n | 'INVOICE_DUPLICATE_COIN'\n | 'INVOICE_DUPLICATE_NFT'\n | 'INVOICE_MINT_FAILED'\n | 'INVOICE_INVALID_PROOF'\n | 'INVOICE_WRONG_TOKEN_TYPE'\n | 'INVOICE_INVALID_DATA'\n | 'INVOICE_ALREADY_EXISTS'\n | 'INVOICE_NOT_FOUND'\n | 'INVOICE_NOT_TARGET'\n | 'INVOICE_ALREADY_CLOSED'\n | 'INVOICE_ALREADY_CANCELLED'\n | 'INVOICE_ORACLE_REQUIRED'\n | 'INVOICE_TERMINATED'\n | 'INVOICE_INVALID_TARGET'\n | 'INVOICE_INVALID_ASSET_INDEX'\n | 'INVOICE_RETURN_EXCEEDS_BALANCE'\n | 'INVOICE_INVALID_DELIVERY_METHOD'\n | 'INVOICE_INVALID_REFUND_ADDRESS'\n | 'INVOICE_INVALID_CONTACT'\n | 'INVOICE_INVALID_ID'\n | 'INVOICE_TOO_MANY_TARGETS'\n | 'INVOICE_TOO_MANY_ASSETS'\n | 'INVOICE_MEMO_TOO_LONG'\n | 'INVOICE_TERMS_TOO_LARGE'\n | 'INVOICE_NOT_TERMINATED'\n | 'INVOICE_NOT_CANCELLED'\n | 'INVOICE_STORAGE_FAILED'\n | 'RATE_LIMITED'\n | 'COMMUNICATIONS_UNAVAILABLE'\n // Swap error codes\n | 'SWAP_INVALID_DEAL'\n | 'SWAP_INVALID_MANIFEST'\n | 'SWAP_NOT_FOUND'\n | 'SWAP_WRONG_STATE'\n | 'SWAP_RESOLVE_FAILED'\n | 'SWAP_DM_SEND_FAILED'\n | 'SWAP_ESCROW_REJECTED'\n | 'SWAP_DEPOSIT_FAILED'\n | 'SWAP_PAYOUT_VERIFICATION_FAILED'\n | 'SWAP_ALREADY_EXISTS'\n | 'SWAP_ALREADY_COMPLETED'\n | 'SWAP_ALREADY_CANCELLED'\n | 'SWAP_TIMEOUT'\n | 'SWAP_LIMIT_EXCEEDED'\n | 'SWAP_ALREADY_INITIALIZED'\n | 'SWAP_MODULE_DESTROYED'\n | 'SWAP_NOT_INITIALIZED';\n\nexport class SphereError extends Error {\n readonly code: SphereErrorCode;\n readonly cause?: unknown;\n /**\n * #441 deferred-paid linkage: the transferId of the possibly-committed send,\n * stamped by `sendOnce` before the error leaves so a payment-request consumer\n * can durably journal request→transfer and resolve 'paid' only when that\n * transfer actually completes (never optimistically). Present ONLY on\n * possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).\n */\n transferId?: string;\n\n constructor(message: string, code: SphereErrorCode, cause?: unknown) {\n super(message);\n this.name = 'SphereError';\n this.code = code;\n this.cause = cause;\n }\n}\n\n/**\n * #677: a send that PARTIALLY completed before losing a source to a concurrent\n * transfer. At least one earlier leg certified on-chain and was journaled for\n * delivery (its {@link committedTokenIds}) — that value has irreversibly left\n * the wallet — but a LATER leg raised `TransferConflictError` (a lost race), and\n * `send()` could NOT COMPLETE the {@link remainingAmount} from the remaining live\n * sources. This is the fallback after the internal remainder re-plan gave up —\n * either insufficient funds OR a transient/transport error during the re-plan\n * (the underlying reason is on `cause`), not necessarily an \"out of funds\" state.\n *\n * Distinct from `TransferConflictError` on purpose: a bare conflict makes the\n * caller re-send the FULL amount, paying the already-delivered leg a second\n * time. Catching THIS type (or `code === 'SEND_PARTIALLY_COMPLETED'`) tells the\n * caller/UI: the delivered legs are final, the conflicted intent stays OPEN\n * (resume converges its certified legs), and only the REMAINDER\n * ({@link remainingAmount}) may be re-planned — under a NEW transferId, never\n * re-sending the whole amount.\n *\n * NOT a subclass of `TransferConflictError` by design: existing conflict\n * handlers that re-send in full must NOT treat this as an ordinary conflict.\n */\nexport class PartialSendConflictError extends SphereError {\n /**\n * The FIRST partial attempt's transferId — its intent stays open for resume,\n * and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across\n * SEVERAL internal attempts before giving up, so {@link committedTokenIds} can\n * span MULTIPLE transferIds — they do NOT all map to this single id.\n */\n readonly transferId: string;\n /**\n * Source token ids whose spend already certified on-chain (and whose finished\n * output blob is journaled for delivery). Their value has already been\n * delivered; NEVER re-send these. May accumulate across several internal\n * remainder-re-plan attempts (so not all are journaled under {@link transferId}).\n */\n readonly committedTokenIds: readonly string[];\n /**\n * The still-undelivered portion (base units, decimal string) — `send()` could\n * not COMPLETE it from the remaining live sources (insufficient funds or a\n * transient error; see `cause`). Re-plan ONLY this amount, under a new\n * transferId; the delivered legs converge via the recipient's §6 claim.\n */\n readonly remainingAmount: string;\n\n constructor(\n message: string,\n transferId: string,\n committedTokenIds: readonly string[],\n remainingAmount: string,\n cause?: unknown,\n ) {\n super(message, 'SEND_PARTIALLY_COMPLETED', cause);\n this.name = 'PartialSendConflictError';\n this.transferId = transferId;\n this.committedTokenIds = [...committedTokenIds];\n this.remainingAmount = remainingAmount;\n }\n}\n\n/**\n * Error codes for a send outcome where money has (or may have) already left the\n * wallet on-chain, so the send is NOT a clean, re-sendable failure. A consumer\n * that persists a \"paid\" flag (e.g. a payment request) must keep the request\n * NON-payable on any of these — reverting to a re-payable state would double-pay\n * (#441). Covers the post-commit mirror-sync-pending / keep-open certification\n * states AND the partial-conflict outcome.\n */\nconst POSSIBLY_COMMITTED_SEND_CODES: ReadonlySet<SphereErrorCode> = new Set([\n 'SEND_SYNC_PENDING',\n 'CERTIFICATION_UNCONFIRMED',\n 'CHECKPOINT_PERSIST_FAILED',\n 'SPLIT_CHECKPOINT_LOST',\n 'CHECKPOINT_TRUSTBASE_MISMATCH',\n 'SEND_PARTIALLY_COMPLETED',\n]);\n\n/**\n * #441: true when a send failed with an outcome where the payment has (or may\n * have) irreversibly left the wallet — a post-commit sync-pending / keep-open\n * certification state, or a `PartialSendConflictError`. Such a send completes (or\n * has already partially completed) via resume/claim and MUST NOT be re-sent, so\n * a persisted \"paid\" state must stay non-payable. A `false` result is a clean\n * pre-commit failure — nothing left the wallet, safe to re-pay.\n */\nexport function isPossiblyCommittedSendOutcome(err: unknown): boolean {\n return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);\n}\n\n/**\n * Type guard to check if an error is a SphereError\n */\nexport function isSphereError(err: unknown): err is SphereError {\n return err instanceof SphereError;\n}\n","// connect/semver.ts\n// Tiny, dependency-free semver helpers for the Connect compatibility gate.\n\n/** MAJOR component of a semver string (e.g. '2.7.3' -> 2). NaN if unparseable. */\nexport function majorOf(v: string): number {\n return parseInt(String(v).split('.')[0], 10);\n}\n\n/**\n * Compare two semver strings, prerelease-aware. Returns -1 | 0 | 1.\n * A release outranks its own prerelease: compareSemver('1.2.3', '1.2.3-rc.1') === 1.\n */\nexport function compareSemver(a: string, b: string): number {\n const parse = (v: string) => {\n const [core, pre] = String(v).split('-', 2) as [string, string | undefined];\n const nums = core.split('.').map((n) => parseInt(n, 10) || 0);\n return { nums, pre };\n };\n const pa = parse(a);\n const pb = parse(b);\n for (let i = 0; i < 3; i++) {\n const d = (pa.nums[i] ?? 0) - (pb.nums[i] ?? 0);\n if (d !== 0) return d < 0 ? -1 : 1;\n }\n if (pa.pre === undefined && pb.pre === undefined) return 0;\n if (pa.pre === undefined) return 1; // release > prerelease\n if (pb.pre === undefined) return -1;\n const sa = pa.pre.split('.');\n const sb = pb.pre.split('.');\n for (let i = 0; i < Math.max(sa.length, sb.length); i++) {\n const x = sa[i];\n const y = sb[i];\n if (x === undefined) return -1;\n if (y === undefined) return 1;\n const nx = Number(x);\n const ny = Number(y);\n if (!Number.isNaN(nx) && !Number.isNaN(ny)) {\n if (nx !== ny) return nx < ny ? -1 : 1;\n } else if (x !== y) {\n return x < y ? -1 : 1;\n }\n }\n return 0;\n}\n","/**\n * SDK2 Constants\n * Default configuration values and storage keys\n */\n\n// =============================================================================\n// Storage Keys\n// =============================================================================\n\n/** Default prefix for all storage keys */\nexport const STORAGE_PREFIX = 'sphere_' as const;\n\n/**\n * Legacy key: Sphere's private decrypt() tries it only as a fallback for wallets written by old\n * SDK versions when no password is given. Nothing is encrypted with it; without a password the\n * mnemonic or master key is stored as plaintext.\n */\nexport const DEFAULT_ENCRYPTION_KEY = 'sphere-default-key' as const;\n\n/**\n * Global storage keys (one per wallet, no address index)\n * Final key format: sphere_{key}\n */\nexport const STORAGE_KEYS_GLOBAL = {\n /** Encrypted BIP39 mnemonic */\n MNEMONIC: 'mnemonic',\n /** Encrypted master private key */\n MASTER_KEY: 'master_key',\n /** BIP32 chain code */\n CHAIN_CODE: 'chain_code',\n /** HD derivation path (full path like m/44'/0'/0'/0/0) */\n DERIVATION_PATH: 'derivation_path',\n /** Base derivation path (like m/44'/0'/0' without chain/index) */\n BASE_PATH: 'base_path',\n /** Derivation mode: bip32, wif_hmac, legacy_hmac */\n DERIVATION_MODE: 'derivation_mode',\n /** Wallet source: mnemonic, file, unknown */\n WALLET_SOURCE: 'wallet_source',\n /** Wallet existence flag */\n WALLET_EXISTS: 'wallet_exists',\n /** Current active address index */\n CURRENT_ADDRESS_INDEX: 'current_address_index',\n /** Nametag cache per address (separate from tracked addresses registry) */\n ADDRESS_NAMETAGS: 'address_nametags',\n /** Active addresses registry (JSON: TrackedAddressesStorage) */\n TRACKED_ADDRESSES: 'tracked_addresses',\n /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */\n LAST_WALLET_EVENT_TS: 'last_wallet_event_ts',\n /** Last processed Nostr DM (gift-wrap) event timestamp (unix seconds), keyed per pubkey */\n LAST_DM_EVENT_TS: 'last_dm_event_ts',\n /** Group chat: last used relay URL (stale data detection) — global, same relay for all addresses */\n GROUP_CHAT_RELAY_URL: 'group_chat_relay_url',\n /** Cached token registry JSON (fetched from remote) */\n TOKEN_REGISTRY_CACHE: 'token_registry_cache',\n /** Timestamp of last token registry cache update (ms since epoch) */\n TOKEN_REGISTRY_CACHE_TS: 'token_registry_cache_ts',\n /** Cached price data JSON (from CoinGecko or other provider) */\n PRICE_CACHE: 'price_cache',\n /** Timestamp of last price cache update (ms since epoch) */\n PRICE_CACHE_TS: 'price_cache_ts',\n} as const;\n\n/**\n * Per-address storage keys (one per derived address)\n * Final key format: sphere_{DIRECT_xxx_yyy}_{key}\n * Example: sphere_DIRECT_abc123_xyz789_pending_transfers\n *\n * Note: Token data is server custody (wallet-api backend), not here; the\n * payments vertical's durable client state self-prefixes `pv2g2:{network}:{pubkey}:`.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Transfer outbox for this address (pre-flip key name; kept as the network-scoping witness) */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Group chat: joined groups for this address */\n GROUP_CHAT_GROUPS: 'group_chat_groups',\n /** Group chat: messages for this address */\n GROUP_CHAT_MESSAGES: 'group_chat_messages',\n /** Group chat: members for this address */\n GROUP_CHAT_MEMBERS: 'group_chat_members',\n /** Group chat: processed event IDs for deduplication */\n GROUP_CHAT_PROCESSED_EVENTS: 'group_chat_processed_events',\n /** Auto-return settings (pre-flip key name; kept as the network-scoping witness) */\n AUTO_RETURN: 'auto_return',\n /** Auto-return dedup ledger (pre-flip key name; kept as the network-scoping witness) */\n AUTO_RETURN_LEDGER: 'auto_return_ledger',\n /** Per-swap key prefix (pre-flip key name; kept as the network-scoping witness) */\n SWAP_RECORD_PREFIX: 'swap:',\n} as const;\n\n/**\n * Per-address keys that are ALSO per-network: token/payment operational state. Mixing these\n * across networks is unsafe. Chat/identity per-address keys (CONVERSATIONS/MESSAGES/\n * GROUP_CHAT_*) are network-AGNOSTIC and deliberately NOT listed. The payments vertical\n * self-prefixes `pv2g2:{network}:{pubkey}:` and never rides this mechanism; the entries left\n * here are the pre-flip key names the network-isolation tests pin the provider behavior\n * with (P11 stage-2 note: the full isNetworkScopedAddressKey cut needs an owner call on\n * those tests first).\n */\nconst NETWORK_SCOPED_ADDRESS_KEYS: readonly string[] = [\n STORAGE_KEYS_ADDRESS.OUTBOX,\n STORAGE_KEYS_ADDRESS.AUTO_RETURN,\n STORAGE_KEYS_ADDRESS.AUTO_RETURN_LEDGER,\n];\n\n/** Composite per-address key prefixes (modules store `{addressId}_{prefix}{id}`) — per-network. */\nconst NETWORK_SCOPED_ADDRESS_PREFIXES: readonly string[] = [\n STORAGE_KEYS_ADDRESS.SWAP_RECORD_PREFIX, // 'swap:'\n 'inv_ledger:', // AccountingModule INV_LEDGER_PREFIX\n];\n\n/**\n * True if a storage key is a per-NETWORK token/payment key (so the storage provider adds the\n * network segment). Handles the bare form ('auto_return_ledger'), the module-built addressId\n * form ('DIRECT_a_b_auto_return_ledger'), and composites ('{addressId}_swap:{id}'). Chat/identity\n * keys return false — they remain per-address, network-agnostic.\n */\nexport function isNetworkScopedAddressKey(key: string): boolean {\n for (const k of NETWORK_SCOPED_ADDRESS_KEYS) {\n if (key === k || key.endsWith(`_${k}`)) return true;\n }\n for (const p of NETWORK_SCOPED_ADDRESS_PREFIXES) {\n if (key.startsWith(p) || key.includes(`_${p}`)) return true;\n }\n return false;\n}\n\n/**\n * Build a per-address storage key using address identifier\n * @param addressId - Short identifier for the address (e.g., first 8 chars of pubkey hash, or direct address hash)\n * @param key - The key from STORAGE_KEYS_ADDRESS\n * @returns Key in format: \"{addressId}_{key}\" e.g., \"a1b2c3d4_tokens\"\n */\nexport function getAddressStorageKey(addressId: string, key: string): string {\n return `${addressId}_${key}`;\n}\n\n/**\n * Create a readable address identifier from directAddress or chainPubkey\n * Format: DIRECT_first6_last6 (sanitized for filesystem/storage)\n * @param directAddress - The L3 direct address (DIRECT:xxx) or chainPubkey\n * @returns Sanitized identifier like \"DIRECT_abc123_xyz789\"\n */\nexport function getAddressId(directAddress: string): string {\n // Remove DIRECT:// or DIRECT: prefix if present\n let hash = directAddress;\n if (hash.startsWith('DIRECT://')) {\n hash = hash.slice(9);\n } else if (hash.startsWith('DIRECT:')) {\n hash = hash.slice(7);\n }\n // Format: DIRECT_first6_last6 (sanitized)\n const first = hash.slice(0, 6).toLowerCase();\n const last = hash.slice(-6).toLowerCase();\n return `DIRECT_${first}_${last}`;\n}\n\n// =============================================================================\n// Nostr Defaults\n// =============================================================================\n\n/**\n * Fallback Nostr relays, used only when no relay list is configured\n * (NostrTransportProvider / MultiAddressTransportMux). `relay.unicity.network`\n * was the v1 relay and no longer resolves, so it is not listed here.\n */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.damus.io',\n 'wss://nos.lol',\n 'wss://relay.nostr.band',\n] as const;\n\n/** Nostr event kinds used by SDK - must match @unicitylabs/nostr-js-sdk */\nexport const NOSTR_EVENT_KINDS = {\n /** NIP-04 encrypted direct message */\n DIRECT_MESSAGE: 4,\n /** Nametag binding (NIP-78 app-specific data) */\n NAMETAG_BINDING: 30078,\n /** Public broadcast */\n BROADCAST: 1,\n} as const;\n\n/**\n * NIP-29 Event Kinds for relay-based group chat\n * https://github.com/nostr-protocol/nips/blob/master/29.md\n */\nexport const NIP29_KINDS = {\n /** Chat message sent to group */\n CHAT_MESSAGE: 9,\n /** Thread root message */\n THREAD_ROOT: 11,\n /** Thread reply message */\n THREAD_REPLY: 12,\n /** User join request */\n JOIN_REQUEST: 9021,\n /** User leave request */\n LEAVE_REQUEST: 9022,\n /** Admin: add/update user */\n PUT_USER: 9000,\n /** Admin: remove user */\n REMOVE_USER: 9001,\n /** Admin: edit group metadata */\n EDIT_METADATA: 9002,\n /** Admin: delete event */\n DELETE_EVENT: 9005,\n /** Admin: create group */\n CREATE_GROUP: 9007,\n /** Admin: delete group */\n DELETE_GROUP: 9008,\n /** Admin: create invite code */\n CREATE_INVITE: 9009,\n /** Relay-signed group metadata */\n GROUP_METADATA: 39000,\n /** Relay-signed group admins */\n GROUP_ADMINS: 39001,\n /** Relay-signed group members */\n GROUP_MEMBERS: 39002,\n /** Relay-signed group roles */\n GROUP_ROLES: 39003,\n} as const;\n\n// =============================================================================\n// Aggregator (Oracle) Defaults\n// =============================================================================\n\n/**\n * Default aggregator request timeout (ms)\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n * There is no default aggregator URL: every live network names its own gateway.\n */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n\n// =============================================================================\n// Wallet Defaults\n// =============================================================================\n\n/** Default BIP32 base path (without chain/index) */\nexport const DEFAULT_BASE_PATH = \"m/44'/0'/0'\" as const;\n\n/** Default BIP32 derivation path (full path with chain/index) */\nexport const DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0` as const;\n\n/** Coin types */\nexport const COIN_TYPES = {\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Default token registry refresh interval (ms) — 1 hour */\nexport const TOKEN_REGISTRY_REFRESH_INTERVAL = 3_600_000;\n\n// =============================================================================\n// Network Defaults\n// =============================================================================\n\n/** Testnet Nostr relays */\nexport const TEST_NOSTR_RELAYS = [\n 'wss://nostr-relay.testnet.unicity.network',\n] as const;\n\n/** Default group chat relays (NIP-29 Zooid relay) */\nexport const DEFAULT_GROUP_RELAYS = [\n 'wss://sphere-relay.unicity.network',\n] as const;\n\n/**\n * Complete configuration for one network. Every field is required, so adding a\n * network (or a field) that is missing any of these is a COMPILE error at the\n * NETWORKS literal below (via `satisfies`) — a half-configured network can never\n * be defined silently.\n */\nexport interface NetworkConfig {\n readonly name: string;\n readonly aggregatorUrl: string;\n readonly nostrRelays: readonly string[];\n readonly groupRelays: readonly string[];\n readonly tokenRegistryUrl: string;\n /** The NFT vessel token type every NFT mints under (#785), 64 lowercase hex: the single\n * `non-fungible` entry of this network's unicity-ids registry. */\n readonly nftTokenType: string;\n /** Canonical numeric network id (= RootTrustBase.networkId; testnet2 = 4).\n * Optional: only set for networks that have a live v2 trust base. */\n readonly networkId?: number;\n}\n\n/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n networkId: 1,\n // v3 state-transition gateway (networkId 1 comes from the trust base). apiKey is env-injected;\n // unlike testnet2's, a mainnet gateway key is a SECRET — never commit one.\n aggregatorUrl: 'https://gateway.mainnet.unicity.network',\n // Mainnet has no relay of its own yet — it shares the testnet relay until one is stood up.\n // Consequence while shared: nametag bindings for both networks live in ONE namespace, and\n // since bindings carry no network the cross-network recipient guard can only signal (#734).\n nostrRelays: TEST_NOSTR_RELAYS,\n groupRelays: DEFAULT_GROUP_RELAYS,\n // Published, but currently only the non-fungible base token type — no fungible\n // coins yet, so those still miss the registry (decimals 0, symbol falls back to\n // six hex chars). Presentation only: the money path treats coinId as opaque bytes.\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.mainnet.json',\n nftTokenType: '9f190eea6c8d7e1e564c35feb4c289add78be4bedb81bb77fe265e926e5493f4',\n },\n // v1 cutover: 'testnet' now POINTS AT TESTNET2 (the v2 gateway network). The\n // old goggregator testnet spoke the removed v1 protocol — a v2 engine cannot\n // run against it. 'testnet2' stays as an alias of the same configuration.\n testnet: {\n name: 'Testnet',\n networkId: 4,\n // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.\n aggregatorUrl: 'https://gateway.testnet2.unicity.network',\n nostrRelays: TEST_NOSTR_RELAYS, // reuse testnet infra (shared relays/ipfs)\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json',\n nftTokenType: '971a26eef0e3aeb22bd3e7d44c47ce963400037e8df42b50d4d44e1589f83826',\n },\n testnet2: {\n name: 'Testnet',\n networkId: 4,\n // v2 state-transition gateway (networkId 4 comes from the trust base). apiKey is env-injected.\n aggregatorUrl: 'https://gateway.testnet2.unicity.network',\n nostrRelays: TEST_NOSTR_RELAYS, // reuse testnet infra (shared relays/ipfs)\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl:\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet2.json',\n nftTokenType: '971a26eef0e3aeb22bd3e7d44c47ce963400037e8df42b50d4d44e1589f83826',\n },\n} as const satisfies Record<string, NetworkConfig>;\n\nexport type NetworkType = keyof typeof NETWORKS;\n\n/**\n * A network descriptor — the canonical way to identify a Unicity network across\n * the SDK and the Connect protocol. `id` is the canonical key (= RootTrustBase\n * networkId, analogous to an EIP-155 chainId); `name` is human-readable metadata.\n * Richer fields (gateway/symbol/explorer/icon) and switch/add-network are deferred.\n */\nexport interface NetworkInfo {\n readonly id: number;\n readonly name?: string;\n}\n\n/**\n * Registry of known networks for dApps and wallets. Single-sourced from NETWORKS\n * so it cannot drift. Use as `network: SPHERE_NETWORKS.testnet2`. Custom networks\n * are the same shape: `network: { id, name }`. Only live v2 networks appear here;\n * the legacy `testnet` alias is intentionally not surfaced.\n */\nexport const SPHERE_NETWORKS = {\n mainnet: { id: NETWORKS.mainnet.networkId as number, name: 'mainnet' },\n testnet2: { id: NETWORKS.testnet2.networkId as number, name: 'testnet2' },\n} as const satisfies Record<string, NetworkInfo>;\n\n// =============================================================================\n// Timeouts & Limits\n// =============================================================================\n\n/** Default timeouts (ms) */\nexport const TIMEOUTS = {\n /** WebSocket connection timeout */\n WEBSOCKET_CONNECT: 10000,\n /** Nostr relay reconnect delay */\n NOSTR_RECONNECT_DELAY: 3000,\n /** Max reconnect attempts */\n MAX_RECONNECT_ATTEMPTS: 5,\n /** Proof polling interval */\n PROOF_POLL_INTERVAL: 1000,\n /** Sync interval */\n SYNC_INTERVAL: 60000,\n} as const;\n\n// =============================================================================\n// Sphere Connect\n// =============================================================================\n\n/** Signal sent by wallet popup to dApp when ConnectHost is ready */\nexport const HOST_READY_TYPE = 'sphere-connect:host-ready' as const;\n\n/** Default timeout (ms) for waiting for the host-ready signal */\nexport const HOST_READY_TIMEOUT = 30_000;\n\n/** Validation limits */\nexport const LIMITS = {\n /** Min nametag length */\n NAMETAG_MIN_LENGTH: 3,\n /** Max nametag length */\n NAMETAG_MAX_LENGTH: 20,\n /** Max memo length */\n MEMO_MAX_LENGTH: 500,\n /** Max message length */\n MESSAGE_MAX_LENGTH: 10000,\n} as const;\n","/**\n * Sphere Connect Protocol\n * JSON-RPC-like message types for wallet ↔ dApp communication.\n */\n\nimport { majorOf } from './semver';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const SPHERE_CONNECT_NAMESPACE = 'sphere-connect';\nexport const SPHERE_CONNECT_VERSION = '2.3'; // Connect protocol version (semver MAJOR.MINOR)\n\n// Default npm-SDK floor a host enforces at the handshake (0.14.1 = the P11 flip:\n// the v1 payments era is gone; pre-flip ConnectClients expect a wallet that no\n// longer exists). '-0' admits every 0.14.1 prerelease (compareSemver: release >\n// prerelease). Override via ConnectHostConfig.minSdkVersion; the claim is\n// compatibility hygiene, not security — a hostile client can lie about it.\nexport const DEFAULT_MIN_CLIENT_SDK_VERSION = '0.14.1-0';\n\nexport { HOST_READY_TYPE, HOST_READY_TIMEOUT, SPHERE_NETWORKS } from '../constants';\n// Import for local use (e.g. SphereHandshake.network) AND re-export for connect consumers.\n// A bare `export type { NetworkInfo } from '../constants'` would re-export without bringing\n// the name into this module's scope, breaking the local references (TS2304).\nimport type { NetworkInfo } from '../constants';\nexport type { NetworkInfo };\n\n// =============================================================================\n// RPC Method Names (query — return data, no UI)\n// =============================================================================\n\nexport const RPC_METHODS = {\n GET_IDENTITY: 'sphere_getIdentity',\n GET_BALANCE: 'sphere_getBalance',\n GET_ASSETS: 'sphere_getAssets',\n GET_FIAT_BALANCE: 'sphere_getFiatBalance',\n GET_TOKENS: 'sphere_getTokens',\n GET_HISTORY: 'sphere_getHistory',\n RESOLVE: 'sphere_resolve',\n SUBSCRIBE: 'sphere_subscribe',\n UNSUBSCRIBE: 'sphere_unsubscribe',\n DISCONNECT: 'sphere_disconnect',\n GET_CONVERSATIONS: 'sphere_getConversations',\n GET_MESSAGES: 'sphere_getMessages',\n GET_DM_UNREAD_COUNT: 'sphere_getDMUnreadCount',\n MARK_AS_READ: 'sphere_markAsRead',\n} as const;\n\nexport type RpcMethod = (typeof RPC_METHODS)[keyof typeof RPC_METHODS];\n\n// =============================================================================\n// Intent Action Names (open wallet UI, require user confirmation)\n// =============================================================================\n\nexport const INTENT_ACTIONS = {\n SEND: 'send',\n DM: 'dm',\n PAYMENT_REQUEST: 'payment_request',\n RECEIVE: 'receive',\n SIGN_MESSAGE: 'sign_message',\n MINT: 'mint',\n // #777: params { to, tokenId, memo? }. Named NFT rather than 'send_token':\n // coins are tokens too, so 'token' does not say which kind moves.\n SEND_NFT: 'send_nft',\n // Connect 2.3: params MintNftIntentParams, result MintNftIntentResult (connect/nft-wire.ts).\n MINT_NFT: 'mint_nft',\n} as const;\n\nexport type IntentAction = (typeof INTENT_ACTIONS)[keyof typeof INTENT_ACTIONS];\n\n// =============================================================================\n// Error Codes\n// =============================================================================\n\nexport const ERROR_CODES = {\n // Standard JSON-RPC\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n\n // Sphere Connect (4xxx)\n NOT_CONNECTED: 4001,\n PERMISSION_DENIED: 4002,\n USER_REJECTED: 4003,\n SESSION_EXPIRED: 4004,\n ORIGIN_BLOCKED: 4005,\n RATE_LIMITED: 4006,\n UNSUPPORTED_PROTOCOL_VERSION: 4007, // Connect MAJOR mismatch (incompatible era)\n INCOMPATIBLE_NETWORK: 4008, // dApp targets a different network than the wallet\n // Wallet locked; THE SESSION IS STILL ALIVE. A QUERY may be retried after wallet:unlocked.\n // An INTENT already delegated to the wallet is NEVER answered with this code — it gets\n // INTENT_OUTCOME_UNKNOWN (4201) instead, because a retry could double-spend.\n WALLET_LOCKED: 4009,\n INSUFFICIENT_BALANCE: 4100,\n INVALID_RECIPIENT: 4101,\n TRANSFER_FAILED: 4102,\n INTENT_CANCELLED: 4200,\n /**\n * The intent was DELEGATED to the wallet and the host lost track of the answer — a host\n * deadline fired, or the wallet locked / logged out mid-flight. **The outcome is UNKNOWN:\n * the money may or may not have moved.**\n *\n * A dApp MUST NOT retry on this code. Reconcile out of band (poll the recipient, the\n * aggregator, or your own backend) and only then decide.\n *\n * This code exists because every other answer would be a lie. `INTENT_CANCELLED` (4200)\n * asserts the user declined and nothing happened; `WALLET_LOCKED` (4009) invites a retry\n * after the unlock. Sending either for an intent the wallet had already submitted is how a\n * paid-but-not-credited order — and then a double spend on retry — happens.\n */\n INTENT_OUTCOME_UNKNOWN: 4201,\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\n\n/** `data` carried by every WALLET_LOCKED (4009) refusal. */\nexport interface WalletLockedData {\n /** Discriminator. Always the literal 'locked' — reserved for future refusal reasons. */\n readonly reason: 'locked';\n /**\n * Whether the wallet's own unlock surface is currently on screen.\n * DECLARED in the fail-fast release and always absent there; POPULATED in the resume\n * release from ConnectHostConfig.unlockSurface(), evaluated at refusal time (visibility\n * changes over time, so a static value would lie). Feeds\n * ConnectClientConfig.onWalletAttention.\n */\n readonly unlockSurface?: 'visible' | 'background';\n}\n\n// =============================================================================\n// Message Types\n// =============================================================================\n\ninterface SphereMessageBase {\n readonly ns: typeof SPHERE_CONNECT_NAMESPACE;\n readonly v: typeof SPHERE_CONNECT_VERSION;\n}\n\n/** Query request: dApp → Wallet */\nexport interface SphereRpcRequest extends SphereMessageBase {\n readonly type: 'request';\n readonly id: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\n/** Query response: Wallet → dApp */\nexport interface SphereRpcResponse extends SphereMessageBase {\n readonly type: 'response';\n readonly id: string;\n readonly result?: unknown;\n readonly error?: SphereRpcError;\n}\n\n/** Intent request: dApp → Wallet (opens wallet UI) */\nexport interface SphereIntentRequest extends SphereMessageBase {\n readonly type: 'intent';\n readonly id: string;\n readonly action: string;\n readonly params: Record<string, unknown>;\n}\n\n/** Intent result: Wallet → dApp (after user action) */\nexport interface SphereIntentResult extends SphereMessageBase {\n readonly type: 'intent_result';\n readonly id: string;\n readonly result?: unknown;\n readonly error?: SphereRpcError;\n}\n\n/** Event push: Wallet → dApp (unsolicited) */\nexport interface SphereEventMessage extends SphereMessageBase {\n readonly type: 'event';\n readonly event: string;\n readonly data: unknown;\n}\n\n/** Handshake: bidirectional */\nexport interface SphereHandshake extends SphereMessageBase {\n readonly type: 'handshake';\n readonly direction: 'request' | 'response';\n readonly permissions: string[];\n readonly dapp?: DAppMetadata;\n readonly sessionId?: string;\n readonly identity?: PublicIdentity;\n /** If true, wallet must NOT open any approval UI. Immediately reject if origin is not already approved. */\n readonly silent?: boolean;\n /** request: dApp target network; response: wallet active network */\n readonly network?: NetworkInfo;\n /** Informational: the dApp's npm SDK version. */\n readonly sdkVersion?: string;\n /** Response: structured rejection reason when the gate refuses the connection. */\n readonly error?: SphereRpcError;\n /** Response: non-fatal deprecation notice (does not block the connection). */\n readonly warning?: SphereRpcError;\n /** Response only: the wallet is LOCKED and the session is ALIVE. The dApp is connected\n * and must not re-handshake; it will receive `wallet:unlocked` on the same session.\n * Additive and safe for old clients: handleHandshakeResponse reads only sessionId,\n * permissions, identity, network, warning and error and ignores unknown fields. */\n readonly locked?: boolean;\n}\n\nexport interface SphereRpcError {\n readonly code: number;\n readonly message: string;\n readonly data?: unknown;\n}\n\nexport type SphereConnectMessage =\n | SphereRpcRequest\n | SphereRpcResponse\n | SphereIntentRequest\n | SphereIntentResult\n | SphereEventMessage\n | SphereHandshake;\n\n// =============================================================================\n// Shared Types\n// =============================================================================\n\nexport interface DAppMetadata {\n readonly name: string;\n readonly description?: string;\n readonly icon?: string;\n readonly url: string;\n}\n\nexport interface PublicIdentity {\n readonly chainPubkey: string;\n readonly directAddress?: string;\n readonly nametag?: string;\n}\n\n// =============================================================================\n// Wallet-initiated Events (pushed automatically by host, no subscription needed)\n// =============================================================================\n\n/**\n * Events that ConnectHost pushes proactively to connected dApps.\n * dApps can listen with client.on(WALLET_EVENTS.LOCKED, handler) etc.\n * No sphere_subscribe call needed — host sends these unconditionally.\n */\nexport const WALLET_EVENTS = {\n /** Wallet is LOCKED — the session is STILL ALIVE. Requests are answered\n * WALLET_LOCKED (4009) until `wallet:unlocked`. The dApp must NOT disconnect,\n * must NOT clear its sessionId, and must NOT re-handshake.\n * Payload: {@link WalletLockedPayload}. Pushed by ConnectHost.setLocked() and\n * immediately after a handshake response carrying `locked: true`. */\n LOCKED: 'wallet:locked',\n /** Wallet was unlocked — the SAME session continues: no re-handshake, no re-approval,\n * no re-subscribe (the host re-arms the dApp's subscriptions before pushing this).\n * Payload: {@link WalletUnlockedPayload} — carries the CURRENT identity, which may\n * differ from the one the dApp connected with. Pushed by ConnectHost.updateSphere()\n * on the locked -> live edge only. */\n UNLOCKED: 'wallet:unlocked',\n /** The session is GONE (logout, wallet deleted, dApp sphere_disconnect, expiry seen at\n * unlock, a different seed behind the lock screen, host destroy).\n * The dApp must clear its session and re-handshake to continue. Unlocking does not cure it.\n * Payload: {@link WalletDisconnectedPayload}. Pushed by ConnectHost.revokeSession(). */\n DISCONNECTED: 'wallet:disconnected',\n /** Active wallet address changed. dApp should update displayed identity.\n * Pushed automatically by ConnectHost — no sphere_subscribe needed. */\n IDENTITY_CHANGED: 'identity:changed',\n} as const;\n\nexport type WalletEvent = (typeof WALLET_EVENTS)[keyof typeof WALLET_EVENTS];\n\n/** Payload of {@link WALLET_EVENTS.LOCKED}. Intentionally empty — matches today's push,\n * so an old dApp sees no shape change. */\nexport type WalletLockedPayload = Record<string, never>;\n\n/** Payload of {@link WALLET_EVENTS.DISCONNECTED}. Intentionally empty. */\nexport type WalletDisconnectedPayload = Record<string, never>;\n\n/** Payload of {@link WALLET_EVENTS.UNLOCKED}. */\nexport interface WalletUnlockedPayload {\n /** The wallet's public identity at unlock time. Absent when the rebound Sphere has no\n * identity yet (JSON transports drop undefined keys).\n * Unlock is NOT implicitly the same wallet: the lock screen's \"Forgot password ->\n * restore from recovery phrase\" installs a DIFFERENT seed while origin approvals are\n * keyed by origin alone. The host's own lock-edge guard is authoritative (it revokes\n * instead of pushing this event on a mismatch); this field is what lets a dApp render\n * honestly and what the client-side retry queue checks before draining. */\n readonly identity?: PublicIdentity;\n}\n\n/** Payload of {@link WALLET_EVENTS.IDENTITY_CHANGED} — unchanged: the host forwards\n * Sphere's own event data verbatim and pushes a PublicIdentity from updateSphere().\n * Typed as the union for honesty. */\nexport type WalletIdentityChangedPayload = PublicIdentity | unknown;\n\n/** Events the host pushes unconditionally. They must NEVER be routed through\n * `sphere_subscribe`: Sphere.on() accepts any string and would silently never emit,\n * so the subscribe would succeed and deliver nothing forever. */\nexport const AUTO_PUSHED_EVENTS: readonly WalletEvent[] = [\n WALLET_EVENTS.LOCKED,\n WALLET_EVENTS.UNLOCKED,\n WALLET_EVENTS.DISCONNECTED,\n WALLET_EVENTS.IDENTITY_CHANGED,\n];\n\n/** True for an event the host pushes unconditionally (see {@link AUTO_PUSHED_EVENTS}). */\nexport function isAutoPushedEvent(event: string): event is WalletEvent {\n return (AUTO_PUSHED_EVENTS as readonly string[]).includes(event);\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if a message belongs to the Sphere Connect protocol.\n * Handshakes are accepted at ANY version (the version decision happens in the\n * handshake handler so an incompatible peer gets a clean typed error instead of a\n * silently-dropped message → timeout). All other (session) traffic must share the\n * same MAJOR (so MINOR versions interoperate, e.g. 2.0 ↔ 2.1). */\nexport function isSphereConnectMessage(msg: unknown): msg is SphereConnectMessage {\n if (!msg || typeof msg !== 'object') return false;\n const m = msg as Record<string, unknown>;\n if (m.ns !== SPHERE_CONNECT_NAMESPACE) return false;\n if (m.type === 'handshake') return true;\n if (typeof m.v !== 'string') return false;\n return majorOf(m.v) === majorOf(SPHERE_CONNECT_VERSION);\n}\n\n/** Create a unique request ID */\nexport function createRequestId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID\n return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n","// connect/compatibility.ts — pure, dependency-free compatibility decision for the Connect gate.\nimport { ERROR_CODES } from './protocol';\nimport type { SphereRpcError, NetworkInfo } from './protocol';\nimport { majorOf, compareSemver } from './semver';\n\nexport type IncompatibleReason = 'protocol_incompatible' | 'network_incompatible';\n\nexport interface CompatInput {\n clientProtocol: string; // inbound msg.v\n walletProtocol: string; // SPHERE_CONNECT_VERSION\n clientNetwork: NetworkInfo | undefined; // inbound msg.network\n walletNetworkId: number; // wallet active network\n minMinor?: number; // optional MINOR floor within the MAJOR\n clientSdkVersion?: string; // optional secondary npm floor\n minSdkVersion?: string;\n}\n\nexport type CompatResult = { ok: true } | { ok: false; error: SphereRpcError };\n\nfunction fail(\n code: number,\n reason: IncompatibleReason,\n message: string,\n extra: Record<string, unknown>,\n): CompatResult {\n return { ok: false, error: { code, message, data: { reason, ...extra } } };\n}\n\n/**\n * Decide whether a connecting peer is compatible. Runs four ordered checks\n * (protocol MAJOR → optional MINOR floor → optional SDK floor → network) and\n * returns {ok:true} or {ok:false, error}. A malformed clientProtocol (NaN MAJOR)\n * is treated as protocol-incompatible.\n *\n * Every refusal message NAMES the versions involved. The same numbers are also in\n * `error.data` for anything that wants to branch on them, but `data` alone is not\n * enough: the whole fleet renders `error.message` and nothing else, so a floor that\n * only said \"below the required minimum\" told a developer to upgrade without ever\n * saying to what.\n */\nexport function checkCompatibility(input: CompatInput): CompatResult {\n const { clientProtocol, walletProtocol, minMinor, clientSdkVersion, minSdkVersion } = input;\n\n // 1. Protocol MAJOR must match.\n if (majorOf(clientProtocol) !== majorOf(walletProtocol)) {\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `Incompatible Connect protocol version: app speaks ${clientProtocol}, wallet speaks ${walletProtocol}`,\n { walletProtocol, clientProtocol });\n }\n\n const walletMajor = majorOf(walletProtocol);\n\n // 2. Optional MINOR floor within the MAJOR.\n if (minMinor !== undefined && compareSemver(clientProtocol, `${walletMajor}.${minMinor}`) < 0) {\n const requiredProtocol = `${walletMajor}.${minMinor}`;\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `Connect protocol ${clientProtocol} is below the required minimum ${requiredProtocol}`,\n { walletProtocol, clientProtocol, requiredProtocol });\n }\n\n // 3. Optional secondary npm-SDK floor (rarely used).\n if (minSdkVersion !== undefined && (!clientSdkVersion || compareSemver(clientSdkVersion, minSdkVersion) < 0)) {\n // A client that reported nothing is a distinct failure from one that reported an old\n // version — say which it was, or the developer checks a version that was never sent.\n const actual = clientSdkVersion ?? 'unknown (not reported)';\n return fail(ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION, 'protocol_incompatible',\n `SDK version ${actual} is below the required minimum ${minSdkVersion}`,\n { walletProtocol, clientProtocol, requiredSdk: minSdkVersion, actualSdk: clientSdkVersion ?? null });\n }\n\n // 4. Network must match (a missing network is treated as a mismatch).\n if (!input.clientNetwork || input.clientNetwork.id !== input.walletNetworkId) {\n return fail(ERROR_CODES.INCOMPATIBLE_NETWORK, 'network_incompatible',\n 'dApp targets a different network than the wallet',\n { walletNetwork: { id: input.walletNetworkId }, clientNetwork: input.clientNetwork ?? null });\n }\n\n return { ok: true };\n}\n\n","// AUTO-GENERATED by scripts/gen-version.mjs — do not edit.\nexport const SDK_VERSION = '0.18.0-dev.2';\n","/**\n * Sphere Connect Permission System\n * Defines scopes, maps methods/intents to required permissions.\n */\n\nimport { RPC_METHODS, INTENT_ACTIONS } from './protocol';\n\n// =============================================================================\n// Permission Scopes\n// =============================================================================\n\nexport const PERMISSION_SCOPES = {\n IDENTITY_READ: 'identity:read',\n BALANCE_READ: 'balance:read',\n TOKENS_READ: 'tokens:read',\n HISTORY_READ: 'history:read',\n EVENTS_SUBSCRIBE: 'events:subscribe',\n RESOLVE_PEER: 'resolve:peer',\n TRANSFER_REQUEST: 'transfer:request',\n DM_REQUEST: 'dm:request',\n DM_READ: 'dm:read',\n DM_MANAGE: 'dm:manage',\n PAYMENT_REQUEST: 'payment:request',\n SIGN_REQUEST: 'sign:request',\n MINT_REQUEST: 'mint:request',\n /** #777: moving a coinless token (an NFT). Distinct from transfer:request. */\n NFT_TRANSFER: 'nft:transfer',\n /** Minting an NFT signs dApp-chosen content as the user; mint:request and nft:transfer do not imply it. */\n NFT_MINT: 'nft:mint',\n} as const;\n\nexport type PermissionScope = (typeof PERMISSION_SCOPES)[keyof typeof PERMISSION_SCOPES];\n\n/** All available permission scopes */\nexport const ALL_PERMISSIONS: readonly PermissionScope[] = Object.values(PERMISSION_SCOPES);\n\n/** Permissions always granted on connect */\nexport const DEFAULT_PERMISSIONS: readonly PermissionScope[] = [\n PERMISSION_SCOPES.IDENTITY_READ,\n];\n\n// =============================================================================\n// Method → Permission Mapping\n// =============================================================================\n\nexport const METHOD_PERMISSIONS: Record<string, PermissionScope> = {\n [RPC_METHODS.GET_IDENTITY]: PERMISSION_SCOPES.IDENTITY_READ,\n [RPC_METHODS.GET_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_ASSETS]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_FIAT_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,\n [RPC_METHODS.GET_TOKENS]: PERMISSION_SCOPES.TOKENS_READ,\n [RPC_METHODS.GET_HISTORY]: PERMISSION_SCOPES.HISTORY_READ,\n [RPC_METHODS.RESOLVE]: PERMISSION_SCOPES.RESOLVE_PEER,\n [RPC_METHODS.SUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE,\n [RPC_METHODS.UNSUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE,\n [RPC_METHODS.GET_CONVERSATIONS]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.GET_MESSAGES]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.GET_DM_UNREAD_COUNT]: PERMISSION_SCOPES.DM_READ,\n [RPC_METHODS.MARK_AS_READ]: PERMISSION_SCOPES.DM_MANAGE,\n};\n\n// =============================================================================\n// Intent → Permission Mapping\n// =============================================================================\n\nexport const INTENT_PERMISSIONS: Record<string, PermissionScope> = {\n [INTENT_ACTIONS.SEND]: PERMISSION_SCOPES.TRANSFER_REQUEST,\n [INTENT_ACTIONS.DM]: PERMISSION_SCOPES.DM_REQUEST,\n [INTENT_ACTIONS.PAYMENT_REQUEST]: PERMISSION_SCOPES.PAYMENT_REQUEST,\n [INTENT_ACTIONS.RECEIVE]: PERMISSION_SCOPES.IDENTITY_READ,\n [INTENT_ACTIONS.SIGN_MESSAGE]: PERMISSION_SCOPES.SIGN_REQUEST,\n [INTENT_ACTIONS.MINT]: PERMISSION_SCOPES.MINT_REQUEST,\n [INTENT_ACTIONS.SEND_NFT]: PERMISSION_SCOPES.NFT_TRANSFER,\n [INTENT_ACTIONS.MINT_NFT]: PERMISSION_SCOPES.NFT_MINT,\n};\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if granted permissions allow calling a method */\nexport function hasMethodPermission(granted: ReadonlySet<string>, method: string): boolean {\n const required = METHOD_PERMISSIONS[method];\n if (!required) return false;\n return granted.has(required);\n}\n\n/** Check if granted permissions allow an intent action */\nexport function hasIntentPermission(granted: ReadonlySet<string>, action: string): boolean {\n const required = INTENT_PERMISSIONS[action];\n if (!required) return false;\n return granted.has(required);\n}\n\n/** Validate that all requested permissions are known scopes */\nexport function validatePermissions(permissions: string[]): permissions is PermissionScope[] {\n const validScopes = new Set<string>(ALL_PERMISSIONS);\n return permissions.every((p) => validScopes.has(p));\n}\n","// docs/PAYMENTS-V2-DESIGN.md §4 \"Compatibility\": old wire queries are served from\n// `sphere.payments`, and every old dApp-subscribable event name is re-emitted from the\n// facade's bus — dApps change NOTHING. Old names stay plain string literals: post-flip\n// this wire surface is their only home.\n\nimport type { TransferResult } from '../../types';\nimport type { PaymentRequestView, PaymentsV2 } from '../../modules/payments-v2/api';\nimport type { SphereInstance } from './SphereInstance';\n\ntype Forward = (data: unknown) => void;\ntype Attach = (sphere: SphereInstance, forward: Forward) => () => void;\n\ntype PaymentRequestUpdated = {\n id: string;\n status: 'pending' | 'settling' | 'paid' | 'rejected' | 'expired';\n};\ntype TransferAttention = { transferId: string; code: string; detail?: string };\ntype ConnectionStatus = { status: 'connected' | 'degraded' | 'offline' };\n\n// Old `sphere_getFiatBalance` semantics, held exactly: sum of priced assets, null when NO\n// asset carries a price — the \"no price data\" signal a dApp may branch on.\nexport function sumFiatUsd(\n assets: ReadonlyArray<{ fiatValueUsd: number | null }>,\n): number | null {\n let total = 0;\n let priced = false;\n for (const asset of assets) {\n if (asset.fiatValueUsd != null) {\n total += asset.fiatValueUsd;\n priced = true;\n }\n }\n return priced ? total : null;\n}\n\nconst SETTLED_STATUSES: ReadonlySet<TransferResult['status']> = new Set([\n 'confirmed',\n 'delivered',\n 'completed',\n]);\n\nconst REALTIME_STATUS: Record<ConnectionStatus['status'], string> = {\n connected: 'connected',\n degraded: 'reconnecting',\n offline: 'closed',\n};\n\n// The ONE view → legacy `IncomingPaymentRequest` mapping (incoming AND the per-status\n// rebuilds): symbol is registry-resolved when known, else '' — never absent on the wire.\nfunction toLegacyRequest(view: PaymentRequestView, status: string): Record<string, unknown> {\n return { ...view, symbol: view.symbol ?? '', status };\n}\n\n// Rebuilds the old per-status `IncomingPaymentRequest` payload: full view from\n// `requests.list()` (processed requests stay listed until dismissProcessed()); if already\n// gone, id + status still carry the signal and unknowable fields are empty.\n/** The facade, or null while none runs — `sphere.payments` throws between verticals.\n * Callers here are EVENT callbacks, whose throw Sphere's dispatcher swallows. */\nfunction paymentsOrNull(sphere: SphereInstance): PaymentsV2 | null {\n try {\n return sphere.payments;\n } catch {\n return null;\n }\n}\n\nfunction legacyRequestPayload(\n sphere: SphereInstance,\n update: PaymentRequestUpdated,\n): Record<string, unknown> {\n const view: PaymentRequestView | undefined = paymentsOrNull(sphere)\n ?.requests.list()\n .find((request) => request.id === update.id);\n if (!view) {\n return {\n id: update.id,\n requestId: update.id,\n senderPubkey: '',\n amount: '',\n coinId: '',\n symbol: '',\n timestamp: Date.now(),\n status: update.status,\n };\n }\n return toLegacyRequest(view, update.status);\n}\n\nfunction requestStatusAttacher(status: 'paid' | 'rejected' | 'expired'): Attach {\n return (sphere, forward) =>\n sphere.on('payment_request:updated', (update: PaymentRequestUpdated) => {\n if (update.status === status) forward(legacyRequestPayload(sphere, update));\n });\n}\n\n// The v2 attention `code` IS the old event name (machine/journal.ts ATTENTION_* constants).\nfunction attentionAttacher(code: string, toLegacy: (a: TransferAttention) => unknown): Attach {\n return (sphere, forward) =>\n sphere.on('transfer:attention', (attention: TransferAttention) => {\n if (attention.code === code) forward(toLegacy(attention));\n });\n}\n\nfunction remoteUpdateAttacher(sphere: SphereInstance, forward: Forward): () => void {\n let sequence = 0;\n return sphere.on('inventory:updated', () => {\n sequence += 1;\n forward({ providerId: 'wallet-api', name: 'wallet-api', sequence, cid: '', added: 0, removed: 0 });\n });\n}\n\n// A Map, not a plain object: the key is dApp-controlled, and an object lookup would reach\n// the prototype chain ('constructor' would come back as a callable \"attacher\").\nconst COMPAT_ATTACHERS: ReadonlyMap<string, Attach> = new Map<string, Attach>([\n // Old completion split, held: deliveryPending ? delivery_pending : confirmed, plus the\n // failed arm (manifest judgment call #1). Payloads are the TransferResult, unchanged.\n ['transfer:confirmed', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (SETTLED_STATUSES.has(result.status) && result.deliveryPending !== true) forward(result);\n })],\n ['transfer:delivery_pending', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (result.status !== 'failed' && result.deliveryPending === true) forward(result);\n })],\n ['transfer:failed', (sphere, forward) =>\n sphere.on('transfer:updated', (result: TransferResult) => {\n if (result.status === 'failed') forward(result);\n })],\n // Same name on both wires, different payload: the raw v2 view has optional `symbol`;\n // legacy subscribers get the IncomingPaymentRequest shape via the shared mapping.\n ['payment_request:incoming', (sphere, forward) =>\n sphere.on('payment_request:incoming', (view: PaymentRequestView) => {\n forward(toLegacyRequest(view, view.status));\n })],\n ['payment_request:paid', requestStatusAttacher('paid')],\n ['payment_request:rejected', requestStatusAttacher('rejected')],\n ['payment_request:expired', requestStatusAttacher('expired')],\n // detail carries the old inner code (SPLIT_CHECKPOINT_LOST / CHECKPOINT_TRUSTBASE_MISMATCH).\n ['split:checkpoint-stuck', attentionAttacher('split:checkpoint-stuck', (attention) => ({\n transferId: attention.transferId,\n code: attention.detail ?? '',\n error: attention.detail ?? '',\n }))],\n ['delivery:undeliverable', attentionAttacher('delivery:undeliverable', (attention) => ({\n transferId: attention.transferId,\n recipientPubkey: '',\n attempts: 0,\n error: attention.detail ?? '',\n }))],\n ['delivery:deferred', attentionAttacher('delivery:deferred', (attention) => ({\n transferId: attention.transferId,\n recipientPubkey: '',\n reason: attention.detail ?? attention.code,\n deferredUntil: 0,\n }))],\n ['realtime:status', (sphere, forward) =>\n sphere.on('connection:status', (connection: ConnectionStatus) => {\n forward({ status: REALTIME_STATUS[connection.status] ?? 'closed' });\n })],\n // The server IS storage on the v2 vertical — a degraded connection is degraded storage.\n ['storage:degraded', (sphere, forward) =>\n sphere.on('connection:status', (connection: ConnectionStatus) => {\n if (connection.status !== 'degraded') return;\n forward({ providerId: 'wallet-api', error: 'wallet-api connection degraded' });\n })],\n ['sync:completed', (sphere, forward) =>\n sphere.on('inventory:updated', () => {\n forward({ source: 'payments', count: paymentsOrNull(sphere)?.tokens().length ?? 0 });\n })],\n ['sync:remote-update', remoteUpdateAttacher],\n]);\n\n// Attach ONE old-name subscription fed from its v2 source event; `forward` sends the wire\n// frame under the OLD name. Null when `eventName` is not an adapted name (the caller falls\n// through to a direct `sphere.on`).\nexport function attachCompatEvent(\n sphere: SphereInstance,\n eventName: string,\n forward: Forward,\n): (() => void) | null {\n const attach = COMPAT_ATTACHERS.get(eventName);\n return attach ? attach(sphere, forward) : null;\n}\n","/**\n * Wallet-binding FSM + the single total gate decision.\n *\n * PURE: no I/O, no timers, no Sphere reference, no `await` — a transition\n * table + assertTransition.\n *\n * Nothing here is re-exported from connect/index.ts — it is host-internal.\n */\n\nimport { SphereError } from '../../core/errors';\nimport { ERROR_CODES, RPC_METHODS } from '../protocol';\nimport type { WalletLockedData } from '../protocol';\nimport type { WalletState } from '../types';\n\n// ===========================================================================\n// Wire text — RECOMMENDATIONS, not contracts\n// ===========================================================================\n\n/** Recommended refusal text for WALLET_LOCKED. Discriminate on code 4009 and on\n * data.reason, NEVER on this string. Deliberately does not match the reference dApp's\n * teardown regex /not.connected|timeout|transport|closed|session/i. */\nexport const WALLET_LOCKED_MESSAGE = 'Wallet is locked';\n\n/** Fixed text for a non-SphereError throw. A raw JS message must never cross the trust\n * boundary into a third-party dApp. */\nexport const INTERNAL_ERROR_MESSAGE = 'Internal wallet error';\n/**\n * Recommended text for INTENT_OUTCOME_UNKNOWN (4201). Like every message on this wire it is\n * NOT a contract — consumers discriminate on the code — but the wording matters here because a\n * developer reading it must not conclude the intent was cancelled.\n */\nexport const INTENT_UNKNOWN_MESSAGE =\n 'Intent outcome unknown — do not retry; reconcile before acting';\n\n/** Today's literal on the host's not-connected refusals — kept byte-identical.\n * `unavailable` answers this too: unlocking cannot cure it, so promising an unlock\n * would be a lie. */\nexport const NOT_CONNECTED_MESSAGE = 'Not connected';\n\n// ===========================================================================\n// Transitions\n// ===========================================================================\n\n/** Legal edges. NO self-transitions by design: every verb must early-return when it is\n * already in the target state, so assertWalletTransition() enforces idempotency at the\n * table level rather than by reviewer diligence. */\nexport const VALID_WALLET_TRANSITIONS: Record<WalletState, readonly WalletState[]> = {\n live: ['locked', 'unavailable'],\n locked: ['live', 'unavailable'],\n unavailable: ['live', 'locked'],\n};\n\nexport function isValidWalletTransition(from: WalletState, to: WalletState): boolean {\n return VALID_WALLET_TRANSITIONS[from].includes(to);\n}\n\n/** Throws SphereError('Invalid wallet state transition: from -> to', 'VALIDATION_ERROR')\n * — an EXISTING SphereErrorCode; core/errors.ts is not touched. */\nexport function assertWalletTransition(from: WalletState, to: WalletState): void {\n if (!isValidWalletTransition(from, to)) {\n throw new SphereError(`Invalid wallet state transition: ${from} -> ${to}`, 'VALIDATION_ERROR');\n }\n}\n\n// ===========================================================================\n// Locked allow-list\n// ===========================================================================\n\n/**\n * The four methods answerable while locked. Everything else gets 4009.\n *\n * - sphere_getIdentity -> 'serve-from-snapshot'. Those exact bytes were already handed\n * to this origin in the handshake response and DEFAULT_PERMISSIONS\n * grants identity:read unconditionally, so refusing re-states a\n * revealed fact for zero security while making every dApp that\n * reads identity once on mount render \"disconnected\" for the whole\n * lock.\n * - sphere_subscribe -> 'serve'. Refusing it is a BUG, not a policy: ConnectClient.on()\n * fires sphere_subscribe fire-and-forget and NEVER retries (the\n * error goes to logger.debug), so the event stream would die\n * forever after one lock.\n * - sphere_unsubscribe -> 'serve'. Needs no Sphere at all.\n * - sphere_disconnect -> 'serve'. Without it a session can never be dropped while\n * locked; the client swallows the error and calls cleanup()\n * anyway, so the dApp shows \"disconnected\" while onDisconnect —\n * the only hook that revokes the persisted origin approval —\n * never fires.\n *\n * WHAT IS BLOCKED — the honest count, because an enumeration of only the money reads reads as\n * if messaging still works. RPC_METHODS has FOURTEEN entries; four are above. The other TEN,\n * and EVERY intent, are refused:\n *\n * sphere_getBalance, sphere_getAssets, sphere_getFiatBalance, sphere_getTokens,\n * sphere_getHistory — money state a locked wallet cannot honour. Never cached either: a\n * dApp holding a stale balance is a dApp about to offer an\n * unpayable spend.\n * sphere_resolve — nametag resolution needs the live transport.\n * sphere_getConversations, sphere_getMessages, sphere_getDMUnreadCount, sphere_markAsRead\n * — DMs are decrypted with keys that left memory. Messaging does NOT\n * keep working while locked; a dApp must stop polling and wait.\n *\n * AND THE CASE WITH NO 4009 AT ALL. Everything above assumes a host that HOLDS a session. A\n * wallet that COLD-STARTS locked (a page reload, a fresh popup — the password is memory-only,\n * so this is the common path) has no session and an EMPTY snapshot, so `handleHandshake`\n * refuses at step 0 with an errorless empty response: the dApp's connect() rejects with a bare\n * \"Connection rejected by wallet\" carrying NO code. There is nothing to match 4009 against.\n * That silence is deliberate — the refusal must reveal nothing about the wallet to an origin\n * holding no approval — so a dApp cannot distinguish it from a user pressing Reject and must\n * treat it as \"not ready yet\": keep waiting for HOST_READY, which the wallet emits when a\n * human unlocks it.\n */\nexport const LOCKED_ALLOWLIST: ReadonlySet<string> = new Set<string>([\n RPC_METHODS.GET_IDENTITY,\n RPC_METHODS.SUBSCRIBE,\n RPC_METHODS.UNSUBSCRIBE,\n RPC_METHODS.DISCONNECT,\n]);\n\n// ===========================================================================\n// Gate\n// ===========================================================================\n\nexport type RequestKind = 'query' | 'intent' | 'handshake';\n\nexport type GateDecision =\n | { readonly kind: 'serve' }\n /** Answer from WalletSnapshot. If the required snapshot field is absent the caller\n * REFUSES (4009 for a query, the empty refusal for a handshake) — it never invents a\n * fact about a wallet we have not seen, and never sends undefined-as-success. */\n | { readonly kind: 'serve-from-snapshot' }\n | {\n readonly kind: 'refuse';\n readonly error: {\n readonly code: number;\n readonly message: string;\n readonly data?: WalletLockedData;\n };\n };\n\nconst REFUSE_NOT_CONNECTED: GateDecision = {\n kind: 'refuse',\n error: { code: ERROR_CODES.NOT_CONNECTED, message: NOT_CONNECTED_MESSAGE },\n};\n\nconst REFUSE_LOCKED: GateDecision = {\n kind: 'refuse',\n error: {\n code: ERROR_CODES.WALLET_LOCKED,\n message: WALLET_LOCKED_MESSAGE,\n data: { reason: 'locked' },\n },\n};\n\n/**\n * The single total gate decision. Positional, four required arguments, no I/O.\n * `name` is an RPC method, an intent action, or the literal 'handshake'.\n *\n * Called at step 4 of the ordering contract, AFTER the session check, the expiry check and\n * the rate limit, and BEFORE the permission check — because hasMethodPermission() is false\n * for anything unmapped, so a locked unknown method would otherwise answer\n * PERMISSION_DENIED 4002, an un-retryable code that tells the dApp its permissions are\n * wrong.\n *\n * The resume release merges `unlockSurface` into `error.data` AFTER this returns — the gate\n * stays pure.\n */\nexport function gate(\n walletState: WalletState,\n hasActiveSession: boolean,\n requestKind: RequestKind,\n name: string,\n): GateDecision {\n // 'unavailable' is a dead end: revoke + wallet:disconnected already happened, and\n // unlocking does not cure it. Never 4009 here.\n if (walletState === 'unavailable') return REFUSE_NOT_CONNECTED;\n\n if (requestKind === 'handshake') {\n // While locked EVERY handshake is answered from the snapshot and forced silent, so an\n // origin without an approval still gets today's empty refusal with no UI.\n return walletState === 'locked' ? { kind: 'serve-from-snapshot' } : { kind: 'serve' };\n }\n\n // A query or an intent without a session is 4001 in every wallet state: the lock is\n // observable ONLY to an origin that already holds an approval.\n if (!hasActiveSession) return REFUSE_NOT_CONNECTED;\n\n if (walletState === 'live') return { kind: 'serve' };\n\n // locked, with an active session.\n if (requestKind === 'query' && LOCKED_ALLOWLIST.has(name)) {\n return name === RPC_METHODS.GET_IDENTITY\n ? { kind: 'serve-from-snapshot' }\n : { kind: 'serve' };\n }\n return REFUSE_LOCKED;\n}\n","/**\n * Immutable public facts about the wallet binding.\n *\n * Holds NO reference to Sphere, so \"nothing is read from a destroyed Sphere while locked\"\n * is a property of the types rather than of code review.\n *\n * NOT named SessionSnapshot and carries NO sessionId: it must exist BEFORE any session\n * does — sendHandshakeResponse reads networkId and is the transport for EVERY handshake\n * response, including the refusals.\n *\n * The lock-edge identity binding IS `snapshot.identity?.chainPubkey`; there is no separate\n * chainPubkey field and no separate boundIdentity field.\n */\n\nimport type { PublicIdentity } from '../protocol';\nimport type { SphereInstance } from './SphereInstance';\n\nexport interface WalletSnapshot {\n readonly networkId?: number;\n readonly identity?: PublicIdentity;\n /** Epoch ms of capture. 0 means \"never captured\" — see EMPTY_WALLET_SNAPSHOT. */\n readonly capturedAt: number;\n}\n\n/** capturedAt 0, no networkId, no identity. Used for a host built locked with no prior\n * binding (cold start with an encrypted wallet), after setUnavailable(), and after\n * destroy(). A handshake against an empty snapshot gets today's empty refusal —\n * including a resume. \"Never invent a fact about a wallet we have not seen.\" */\nexport const EMPTY_WALLET_SNAPSHOT: WalletSnapshot = Object.freeze({ capturedAt: 0 });\n\n/**\n * Build from a bound Sphere. `null` returns EMPTY_WALLET_SNAPSHOT.\n *\n * If sphere.identity is null the snapshot has NO identity, and sphere_getIdentity while\n * locked answers 4009 rather than undefined-as-success — a dApp reads an `undefined`\n * success result as \"the wallet has no identity\".\n */\nexport function buildWalletSnapshot(sphere: SphereInstance | null): WalletSnapshot {\n if (!sphere) return EMPTY_WALLET_SNAPSHOT;\n const id = sphere.identity;\n return Object.freeze({\n ...(typeof sphere.networkId === 'number' ? { networkId: sphere.networkId } : {}),\n ...(id\n ? {\n identity: Object.freeze({\n chainPubkey: id.chainPubkey,\n directAddress: id.directAddress,\n nametag: id.nametag,\n }),\n }\n : {}),\n capturedAt: Date.now(),\n });\n}\n","/**\n * Every accepted request id, with its own bounded host-side timer.\n *\n * The single convergence point for every exit: router resolved, router threw, onIntent\n * settled or threw, host deadline, setLocked, revokeSession, setUnavailable, destroy.\n * Because settle() returns null the second time, \"exactly one frame per id\" is a property\n * of this class rather than of every call site.\n *\n * SYNCHRONOUS by design — no `await` anywhere inside. Frame-agnostic: it knows deadlines\n * and kinds, never how to send.\n */\n\nimport { logger } from '../../core/logger';\n\nexport type InFlightKind = 'query' | 'intent';\n\nexport interface InFlightEntry {\n readonly id: string;\n readonly kind: InFlightKind;\n /** Epoch ms. */\n readonly deadline: number;\n /** Aborted by settle() / settleAll() / expiry. Feeds IntentContext.signal. */\n readonly controller: AbortController;\n}\n\nexport interface InFlightRegistryOptions {\n /** Called when an entry's own timer fires. The entry is ALREADY removed and aborted, so\n * the sink just sends the frame (INTERNAL_ERROR -32603 for a query, INTENT_OUTCOME_UNKNOWN\n * 4201 for an intent — never INTENT_CANCELLED, which would assert that nothing happened). */\n readonly onExpire: (entry: InFlightEntry) => void;\n}\n\ninterface Slot {\n readonly entry: InFlightEntry;\n readonly timer: ReturnType<typeof setTimeout>;\n}\n\nexport class InFlightRegistry {\n private readonly slots = new Map<string, Slot>();\n private readonly onExpire: (entry: InFlightEntry) => void;\n\n constructor(options: InFlightRegistryOptions) {\n this.onExpire = options.onExpire;\n }\n\n get size(): number {\n return this.slots.size;\n }\n\n has(id: string): boolean {\n return this.slots.has(id);\n }\n\n /** Register BEFORE the first await and arm the timer AT INSERTION TIME.\n * A duplicate id is a protocol violation: warn and reuse the existing entry, so a\n * hostile or buggy client cannot arm unbounded timers by replaying one id. */\n add(id: string, kind: InFlightKind, deadlineMs: number): InFlightEntry {\n const existing = this.slots.get(id);\n if (existing) {\n logger.warn('InFlightRegistry', `Duplicate request id, reusing entry: ${id}`);\n return existing.entry;\n }\n\n const entry: InFlightEntry = {\n id,\n kind,\n deadline: Date.now() + deadlineMs,\n controller: new AbortController(),\n };\n const timer = setTimeout(() => {\n this.slots.delete(id);\n entry.controller.abort();\n this.onExpire(entry);\n }, deadlineMs);\n\n this.slots.set(id, { entry, timer });\n return entry;\n }\n\n /** Remove + abort BEFORE the caller sends. Returns the entry, or null when it was\n * already settled — in which case the caller MUST send nothing. */\n settle(id: string): InFlightEntry | null {\n const slot = this.slots.get(id);\n if (!slot) {\n logger.warn('InFlightRegistry', `Already settled, dropping second answer: ${id}`);\n return null;\n }\n this.slots.delete(id);\n clearTimeout(slot.timer);\n slot.entry.controller.abort();\n return slot.entry;\n }\n\n /** Settle every entry, in insertion order, aborting each. The caller sends one frame per\n * returned entry. Used by setLocked (4009), revokeSession (4001), destroy (4001). */\n settleAll(): InFlightEntry[] {\n const out: InFlightEntry[] = [];\n for (const slot of this.slots.values()) {\n clearTimeout(slot.timer);\n slot.entry.controller.abort();\n out.push(slot.entry);\n }\n this.slots.clear();\n return out;\n }\n\n /** Clear all timers WITHOUT invoking onExpire. Host teardown only. */\n destroy(): void {\n for (const slot of this.slots.values()) clearTimeout(slot.timer);\n this.slots.clear();\n }\n}\n","/**\n * ConnectHost — Wallet side of Sphere Connect.\n *\n * Wraps a Sphere instance and exposes its API through a ConnectTransport.\n * Handles permission checking, rate limiting, session management,\n * and delegates intents to the wallet app via callbacks.\n */\n\nimport { logger } from '../../core/logger';\nimport { SphereError } from '../../core/errors';\nimport type { SphereEventType } from '../../types';\nimport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n WalletState,\n LockedRequestContext,\n IntentContext,\n} from '../types';\nimport type {\n SphereConnectMessage,\n SphereRpcRequest,\n SphereIntentRequest,\n SphereHandshake,\n PublicIdentity,\n SphereRpcError,\n NetworkInfo,\n WalletLockedData,\n WalletLockedPayload,\n WalletUnlockedPayload,\n WalletDisconnectedPayload,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n INTENT_ACTIONS,\n ERROR_CODES,\n WALLET_EVENTS,\n createRequestId,\n isAutoPushedEvent,\n DEFAULT_MIN_CLIENT_SDK_VERSION,\n} from '../protocol';\nimport { checkCompatibility } from '../compatibility';\nimport { SDK_VERSION } from '../version';\nimport {\n DEFAULT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n} from '../permissions';\nimport type { PermissionScope } from '../permissions';\nimport type { SphereInstance, ConnectDirectMessage } from './SphereInstance';\nimport { attachCompatEvent, sumFiatUsd } from './payments-compat';\nimport {\n assertWalletTransition,\n gate,\n WALLET_LOCKED_MESSAGE,\n INTERNAL_ERROR_MESSAGE,\n INTENT_UNKNOWN_MESSAGE,\n NOT_CONNECTED_MESSAGE,\n} from './host-state';\nimport type { WalletSnapshot } from './WalletSnapshot';\nimport { EMPTY_WALLET_SNAPSHOT, buildWalletSnapshot } from './WalletSnapshot';\nimport { InFlightRegistry } from './InFlightRegistry';\nimport type { InFlightEntry } from './InFlightRegistry';\n\nconst DEFAULT_SESSION_TTL_MS = 86400000; // 24 hours\nconst DEFAULT_MAX_RPS = 20;\n/**\n * Codes a wallet must never use to answer an intent the host already handed it. Both describe\n * the CHANNEL, not the operation: by the time `onIntent` has been called the wallet owns the\n * decision, so \"wallet locked\" or \"not connected\" says nothing true about the spend while\n * inviting a retry — 4009's documented advice is literally \"retry after wallet:unlocked\". They\n * are downgraded to INTENT_OUTCOME_UNKNOWN.\n *\n * Deliberately NOT here: USER_REJECTED and INTENT_CANCELLED. A user who declines the prompt\n * BEFORE anything is submitted is a real, retryable rejection; downgrading it would make every\n * declined payment un-retryable, which is both false and worse UX than the bug. Ensuring the\n * wallet cannot report a cancel AFTER submitting is the WALLET's job — it must not offer a\n * cancel control once the transfer is on the wire. The general contract for third-party wallets,\n * an explicit `ctx.commit()` marking the point of no return after which these two are\n * downgraded as well, is a follow-up rather than something to fake here.\n */\nconst CHANNEL_ONLY_CODES: ReadonlySet<number> = new Set<number>([\n ERROR_CODES.WALLET_LOCKED,\n ERROR_CODES.NOT_CONNECTED,\n]);\n\n/** Intents that always reach `onIntent`: minting an NFT signs dApp-chosen content as the user, so no auto-approval may stand in for the prompt. */\nconst ALWAYS_ASK_INTENTS: ReadonlySet<string> = new Set<string>([INTENT_ACTIONS.MINT_NFT]);\n\nconst DEFAULT_REQUEST_DEADLINE_MS = 25000;\n// LONGER than ConnectClient's own DEFAULT_INTENT_TIMEOUT (120 s). The host must never be the\n// first to give up on a delegated intent: whoever answers first defines the outcome for the\n// dApp, and the host cannot know whether the wallet has already submitted the transfer. With\n// the client timing out first, the dApp learns \"outcome unknown\" from its own clock instead of\n// being told, authoritatively and falsely, that nothing happened.\nconst DEFAULT_INTENT_DEADLINE_MS = 180000;\nconst DEFAULT_HANDSHAKE_DEADLINE_MS = 120000;\n\n/** Resolve `promise`, or `fallback()` after `ms`. Used ONLY for onConnectionRequest, which\n * has no id and therefore cannot live in InFlightRegistry. A rejection still propagates,\n * so handleHandshake's own error handling is unchanged. */\nfunction withDeadline<T>(promise: Promise<T>, ms: number, fallback: () => T): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => resolve(fallback()), ms);\n promise.then(\n (value) => { clearTimeout(timer); resolve(value); },\n (error) => { clearTimeout(timer); reject(error); },\n );\n });\n}\n\nexport class ConnectHost {\n /** Null whenever _walletState is 'locked' or 'unavailable' (invariant B). */\n private sphere: SphereInstance | null;\n\n /** The wallet-binding axis. Underscored because `walletState` is the public getter.\n * ORTHOGONAL to `session` — a locked wallet keeps its session, a live wallet may have\n * none. Written only by the WALLET (setLocked / setUnavailable / updateSphere / destroy);\n * `session` is written by the dApp handshake, sphere_disconnect and expiry. */\n private _walletState: WalletState;\n\n /** Immutable public facts about the current binding. Refreshed on every bind\n * (constructor, updateSphere); FROZEN by setLocked(); EMPTY after setUnavailable() and\n * destroy(). Never read from Sphere while locked — that is a property of the types\n * here, not of code review. */\n private snapshot: WalletSnapshot;\n\n /** Subscription KEYS captured by setLocked() BEFORE the unsub closures are detached.\n * Sphere.destroy() kills those closures, so the keys are the only recoverable\n * information. Excludes 'identity:changed' (autoSubscribeIdentityChanged re-arms it).\n * A Set, not an array: handleSubscribe may be called twice for the same key while\n * locked. */\n private suspendedSubscriptions: Set<string> = new Set();\n\n /** Every accepted id, with its own host-side timer. The single convergence point for\n * lock / revoke / unavailable / destroy / deadline. */\n private readonly inFlight: InFlightRegistry;\n\n private readonly transport: ConnectTransport;\n private readonly config: ConnectHostConfig;\n\n private session: ConnectSession | null = null;\n private grantedPermissions: Set<string> = new Set();\n\n // Event subscription management\n private eventSubscriptions: Map<string, () => void> = new Map(); // eventName → unsub\n\n // Intent auto-approve: action → handler that bypasses wallet UI\n private autoApprovedIntents = new Map<\n string,\n (action: string, params: Record<string, unknown>, session: ConnectSession) => Promise<{ result?: unknown; error?: { code: number; message: string; data?: unknown } }>\n >();\n\n // Rate limiting\n private rateLimitCounter = 0;\n private rateLimitResetAt = 0;\n\n private unsubscribeTransport: (() => void) | null = null;\n\n constructor(config: ConnectHostConfig) {\n this.transport = config.transport;\n this.config = config;\n\n this._walletState = config.initialWalletState ?? 'live';\n this.sphere = (config.sphere ?? null) as SphereInstance | null;\n if (this._walletState === 'live' && !this.sphere) {\n // Fail LOUD but soft: a throw here breaks the wallet's React mount.\n logger.warn(\n 'ConnectHost',\n 'Constructed live with sphere === null; coercing to unavailable. ' +\n 'Pass initialWalletState: \"locked\" when the wallet is locked at construction time.',\n );\n this._walletState = 'unavailable';\n }\n if (this._walletState !== 'live') this.sphere = null; // invariant B, unconditionally\n this.snapshot = buildWalletSnapshot(this.sphere); // EMPTY_WALLET_SNAPSHOT when null\n\n this.inFlight = new InFlightRegistry({ onExpire: (e) => this.settleExpired(e) });\n\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\n }\n\n /** The wallet-binding axis. Orthogonal to {@link getSession}. Read-only —\n * transitions go through setLocked() / setUnavailable() / updateSphere(). */\n get walletState(): WalletState {\n return this._walletState;\n }\n\n /** Both axes in one read, for UI that must render \"connected AND locked\".\n * Required by the wallet's ConnectPage, which today renders a green pulsing\n * \"Connected to {dapp}\" with no regard for lock state. */\n getState(): { readonly walletState: WalletState; readonly session: ConnectSession | null } {\n return { walletState: this._walletState, session: this.session };\n }\n\n /** Get current active session */\n getSession(): ConnectSession | null {\n return this.session;\n }\n\n /** Register an auto-approve handler for an intent action (session-scoped). Throws for an intent that always asks the user. */\n setIntentAutoApprove(\n action: string,\n handler: (\n action: string,\n params: Record<string, unknown>,\n session: ConnectSession,\n ) => Promise<{ result?: unknown; error?: { code: number; message: string; data?: unknown } }>,\n ): void {\n if (ALWAYS_ASK_INTENTS.has(action)) {\n throw new SphereError(`Intent ${action} always asks the user and cannot be auto-approved`, 'INVALID_CONFIG');\n }\n this.autoApprovedIntents.set(action, handler);\n }\n\n /** Remove auto-approve for an intent action. */\n clearIntentAutoApprove(action: string): void {\n this.autoApprovedIntents.delete(action);\n }\n\n /**\n * Bind a (new) Sphere instance. This is BOTH the re-arm path after setLocked() /\n * setUnavailable() AND the existing address-switch path in a live wallet.\n *\n * From 'live' (address switch): today's behaviour, unchanged — re-arm identity:changed,\n * push identity:changed. NO identity comparison: an address switch is legal.\n *\n * On the 'locked' -> 'live' edge, in this order:\n * 1. compare snapshot.identity?.chainPubkey with the new Sphere's chainPubkey.\n * MISMATCH => revokeSession() (which pushes wallet:disconnected) and RETURN.\n * Never wallet:unlocked. This is the \"Forgot password -> restore recovery phrase\n * installed a different seed behind an origin-keyed approval\" guard.\n * 2. session.expiresAt passed => revokeSession() and RETURN. A wallet:unlocked into a\n * dead session would make the dApp's next request answer SESSION_EXPIRED 4004.\n * 3. rebind, refresh the snapshot, go live, re-arm identity:changed, replay every\n * suspended sphere_subscribe key, and ONLY THEN push wallet:unlocked with the\n * CURRENT identity. Re-arm BEFORE push, so a dApp reacting synchronously cannot\n * race its own event streams.\n *\n * From 'unavailable' -> 'live': rebind + refresh the snapshot, no identity check\n * (nothing was bound to compare against) and no event (the session is already null).\n */\n updateSphere(newSphere: unknown): void {\n const wasLocked = this._walletState === 'locked';\n const next = (newSphere ?? null) as SphereInstance | null;\n\n // `null` is explicitly anticipated by the coalesce above, and committing 'live' without a\n // Sphere is unrepresentable: every query would dereference null and answer -32603 forever,\n // never 4009, while the client keeps reporting walletLocked with nothing able to clear it.\n // Route it to the verb that actually means \"there is no Sphere\".\n if (!next) {\n if (this._walletState === 'locked') {\n // Stay locked. The dApp is waiting for a real unlock; a spurious 'unavailable' would\n // revoke a session that a genuine unlock could still have served.\n logger.warn('ConnectHost', 'updateSphere(null) while locked — staying locked');\n return;\n }\n logger.warn('ConnectHost', 'updateSphere(null) — treating as a non-lock loss of Sphere');\n this.setUnavailable();\n return;\n }\n\n if (this._walletState === 'live') {\n // THIS rebind never re-runs checkCompatibility: the lock-edge guard below sits\n // behind `wasLocked`. Unchecked, a host switching network without locking keeps\n // the session and serves a chain the dApp never agreed to. Identity is NOT\n // compared here — changing it is what an address switch IS.\n if (this.session?.active && (this.snapshot.networkId ?? null) !== (next.networkId ?? null)) {\n logger.warn(\n 'ConnectHost',\n `Network changed under a live session — revoking instead of rebinding (origin=${this.config.origin ?? 'unverified'})`,\n );\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession();\n return;\n }\n // Address switch on a live host — today's behaviour, verbatim.\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n const existing = this.eventSubscriptions.get(WALLET_EVENTS.IDENTITY_CHANGED);\n if (existing) {\n existing();\n this.eventSubscriptions.delete(WALLET_EVENTS.IDENTITY_CHANGED);\n }\n if (this.session?.active) {\n this.autoSubscribeIdentityChanged();\n // Push the new identity immediately so dApp doesn't have to wait for the next event\n const identity = this.getPublicIdentity();\n if (identity) {\n this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, identity);\n }\n }\n return;\n }\n\n // 1. Lock-edge identity guard — ONLY on locked -> live. From 'unavailable' there is\n // nothing to compare against.\n if (wasLocked && this.session?.active) {\n // FAIL CLOSED. `before && after &&` used to no-op the whole guard whenever either side\n // was absent — and an absent `before` is exactly what a wallet manufactures by calling\n // sphere.destroy() before setLocked(), an ordering the docs ask for but nothing enforces.\n // An unknown identity on either side is a mismatch: it is not evidence of sameness.\n const before = this.snapshot.identity?.chainPubkey ?? null;\n const after = next.identity?.chainPubkey ?? null;\n // A NETWORK change is equally unrecoverable and was not checked at all. On main a lock\n // revoked unconditionally, so a re-handshake ran checkCompatibility and answered 4008;\n // preserving the session across a lock skipped that gate, leaving the dApp served\n // balances from a chain it never agreed to while still reporting the old network.\n const netBefore = this.snapshot.networkId ?? null;\n const netAfter = next.networkId ?? null;\n if (before !== after || netBefore !== netAfter) {\n logger.warn(\n 'ConnectHost',\n `Wallet behind the lock screen is not the one this session was approved for — revoking instead of unlocking (origin=${this.config.origin ?? 'unverified'})`,\n );\n assertWalletTransition(this._walletState, 'live');\n this._walletState = 'live';\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession(); // pushes wallet:disconnected, settles in flight with 4001\n return;\n }\n\n // 2. Expired while locked. The TTL is 24 h by default and expiresAt does not refresh.\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n logger.warn(\n 'ConnectHost',\n `Session expired while locked — re-handshake required (origin=${this.config.origin ?? 'unverified'})`,\n );\n assertWalletTransition(this._walletState, 'live');\n this._walletState = 'live';\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this.revokeSession();\n return;\n }\n }\n\n // 3. Rebind and go live. _walletState is set BEFORE the replay, because\n // handleSubscribe() branches on it and would otherwise put every key straight back\n // into suspendedSubscriptions and arm nothing.\n assertWalletTransition(this._walletState, 'live');\n this.sphere = next;\n this.snapshot = buildWalletSnapshot(next);\n this._walletState = 'live';\n\n if (!this.session?.active) {\n // Nothing to re-arm and nothing to announce.\n this.suspendedSubscriptions.clear();\n return;\n }\n\n this.autoSubscribeIdentityChanged();\n\n const suspended = [...this.suspendedSubscriptions];\n this.suspendedSubscriptions.clear();\n for (const eventName of suspended) {\n try {\n this.handleSubscribe(eventName);\n } catch (err) {\n logger.warn('ConnectHost', `Re-subscribe failed after unlock: ${eventName}`, err);\n }\n }\n\n logger.debug(\n 'ConnectHost',\n `Wallet unlocked — re-armed ${suspended.length} subscription(s) (origin=${this.config.origin ?? 'unverified'})`,\n );\n\n this.pushClientEvent(WALLET_EVENTS.UNLOCKED, {\n identity: this.getPublicIdentity(),\n } satisfies WalletUnlockedPayload);\n\n // AND identity:changed, which main pushed on every unlock and which the Connect 2.0 docs\n // named as the ONLY unlock signal. checkCompatibility gates on the MAJOR alone, so every\n // already-shipped 2.0 dApp connects to a 2.1 wallet happily — and then, without this, sits\n // showing \"wallet locked\" for the rest of the page's life, because wallet:unlocked is a\n // name it has never heard of. Idempotent for a 2.1 client: it has already written the same\n // identity from the payload above, and its IDENTITY_CHANGED branch just rewrites it.\n const identity = this.getPublicIdentity();\n if (identity) this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, identity);\n }\n\n /**\n * The wallet locked (manual lock, idle auto-lock, cross-tab broadcast, cold start).\n * The session is PRESERVED — a lock is a state, not a teardown. Every request outside\n * the locked allow-list is answered WALLET_LOCKED (4009) until updateSphere().\n *\n * Idempotent: a second call is a no-op and pushes nothing. Required, because\n * SphereProvider.lock(), ConnectPage's `sphere → null` effect and broadcastLock()'s\n * same-tab loopback can all fire it for one user action.\n *\n * ORDERING CONTRACT: call this BEFORE sphere.destroy(). The host drops its Sphere\n * reference here; destroying first leaves in-flight requests reading a dead instance\n * (-32603, or `undefined` returned AS SUCCESS from sphere_getIdentity).\n */\n setLocked(): void {\n if (this._walletState === 'locked') return;\n assertWalletTransition(this._walletState, 'locked');\n\n // Freeze BEFORE dropping. snapshot.identity?.chainPubkey IS the lock-edge identity\n // binding compared in updateSphere() — there is no separate boundIdentity field.\n this.snapshot = buildWalletSnapshot(this.sphere);\n this._walletState = 'locked';\n logger.debug(\n 'ConnectHost',\n `Wallet locked — session preserved (origin=${this.config.origin ?? 'unverified'}, session=${this.session?.id ?? 'none'})`,\n );\n\n if (this.session?.active) {\n this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n }\n\n // Snapshot-then-detach. cleanupEventSubscriptions() ends in .clear() and the Map holds\n // eventName → closure; Sphere.destroy() kills those closures, so the KEYS are the only\n // recoverable information. Exclude identity:changed — autoSubscribeIdentityChanged()\n // re-arms that one itself.\n for (const key of this.eventSubscriptions.keys()) {\n if (key === WALLET_EVENTS.IDENTITY_CHANGED) continue;\n this.suspendedSubscriptions.add(key);\n }\n this.cleanupEventSubscriptions();\n\n // Today only revokeSession() clears these. Under session-preserving semantics an\n // auto-approved intent would otherwise survive a lock and execute unattended after\n // the unlock.\n this.autoApprovedIntents.clear();\n\n this.settleInFlight(ERROR_CODES.WALLET_LOCKED, WALLET_LOCKED_MESSAGE, { reason: 'locked' });\n this.sphere = null;\n }\n\n /**\n * The Sphere instance is gone for a NON-LOCK reason (a generic init failure leaves\n * `sphere === null, isLocked === false` in the wallet).\n * This is a DEAD END: unlocking does not cure it, so it revokes the session and pushes\n * wallet:disconnected rather than promising an unlock that cannot help.\n * Subsequent requests answer NOT_CONNECTED (4001); handshakes get the empty refusal\n * WITHOUT dereferencing a null Sphere.\n *\n * Idempotent. Pushes no 'wallet:unavailable' — there is no such event.\n */\n setUnavailable(): void {\n if (this._walletState === 'unavailable') return;\n assertWalletTransition(this._walletState, 'unavailable');\n\n this._walletState = 'unavailable';\n logger.warn(\n 'ConnectHost',\n `Sphere unavailable (non-lock) — session revoked (origin=${this.config.origin ?? 'unverified'})`,\n );\n\n this.snapshot = EMPTY_WALLET_SNAPSHOT; // nothing may be served from it\n this.suspendedSubscriptions.clear();\n this.revokeSession(); // pushes wallet:disconnected, settles 4001\n this.sphere = null;\n }\n\n /**\n * Destroy the SESSION (logout, wallet deleted, dApp sphere_disconnect, popup\n * beforeunload, expiry, identity mismatch at unlock). Pushes wallet:disconnected BEFORE\n * tearing down, so the dApp stops believing it is connected instead of finding out at\n * its next 4001.\n *\n * This is the TEARDOWN verb. For a lock use setLocked() — a lock never destroys the\n * session. revokeSession() does NOT touch walletState: the two axes are orthogonal.\n */\n revokeSession(): void {\n if (this.session) {\n logger.debug(\n 'ConnectHost',\n `Session revoked (origin=${this.config.origin ?? 'unverified'}, session=${this.session.id})`,\n );\n if (this.session.active) {\n this.pushClientEvent(WALLET_EVENTS.DISCONNECTED, {} as WalletDisconnectedPayload);\n }\n this.session.active = false;\n this.cleanupEventSubscriptions();\n this.autoApprovedIntents.clear();\n this.session = null;\n this.grantedPermissions.clear();\n }\n this.suspendedSubscriptions.clear();\n this.settleInFlight(ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n }\n\n /** Destroy the host, clean up all resources. Idempotent. */\n destroy(): void {\n this.revokeSession(); // pushes wallet:disconnected, settles 4001\n this.inFlight.destroy(); // clear timers WITHOUT invoking onExpire\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\n }\n this.sphere = null;\n this.snapshot = EMPTY_WALLET_SNAPSHOT;\n if (this._walletState !== 'unavailable') {\n assertWalletTransition(this._walletState, 'unavailable');\n this._walletState = 'unavailable';\n }\n }\n\n // ===========================================================================\n // Message Handling\n // ===========================================================================\n\n private async handleMessage(msg: SphereConnectMessage): Promise<void> {\n try {\n if (msg.type === 'handshake' && msg.direction === 'request') {\n await this.handleHandshake(msg);\n return;\n }\n\n if (msg.type === 'request') {\n await this.handleRpcRequest(msg);\n return;\n }\n\n if (msg.type === 'intent') {\n await this.handleIntentRequest(msg);\n return;\n }\n } catch (error) {\n logger.warn('ConnectHost', 'Error handling message:', error);\n // A throw used to send NOTHING, so the dApp hung for its full client timeout and\n // ended with a bare Error('Query timeout: …') / Error('Intent timeout: …') /\n // Error('Connection timeout') carrying no .code.\n this.sendUnhandledError(msg, error);\n }\n }\n\n // ===========================================================================\n // Handshake\n // ===========================================================================\n\n private async handleHandshake(msg: SphereHandshake): Promise<void> {\n const dapp = msg.dapp;\n // A handshake without dapp metadata is malformed — deny silently (no session).\n // The compatibility gate and onConnectionRejected are intentionally not consulted\n // here: there is no app to gate or to surface a rejection reason for.\n if (!dapp) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // STEP 0 — a host with no live binding AND no snapshot knows nothing about a wallet.\n // BEFORE checkCompatibility on purpose: snapshot.networkId is undefined on a\n // cold-start-locked host, so the network check (a missing network is treated as a\n // mismatch) would answer INCOMPATIBLE_NETWORK 4008 to EVERY origin — leaking\n // \"a wallet lives here, network -1\" with no approval, and lying about the cause.\n if (this._walletState !== 'live' && !this.snapshot.identity) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Rate limit. The handshake path was entirely unmetered: an unapproved origin could\n // loop handshakes and open the approval UI without bound. The refusal is today's EMPTY\n // refusal — it carries no error and reveals nothing about the wallet's state.\n if (!this.checkRateLimit()) {\n logger.warn('ConnectHost', 'Handshake rate-limited', { dapp: dapp.name });\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Captured for the pre-await decisions; RE-READ after the approval prompt below, because a\n // human-time await gives the wallet room to lock, unlock or log out underneath us.\n const stateAtPrompt = this._walletState;\n let locked = stateAtPrompt === 'locked';\n\n // Lock gate for the handshake kind. 'unavailable' refuses here WITHOUT dereferencing a\n // null Sphere — which is the whole reason the state exists separately.\n const handshakeDecision = gate(this._walletState, !!this.session?.active, 'handshake', 'handshake');\n if (handshakeDecision.kind === 'refuse') {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Compatibility gate — runs BEFORE resume and BEFORE onConnectionRequest, so an\n // incompatible/old client cannot slip through on a stale sessionId.\n const result = checkCompatibility({\n clientProtocol: msg.v,\n walletProtocol: SPHERE_CONNECT_VERSION,\n clientNetwork: msg.network,\n walletNetworkId: this.snapshot.networkId ?? -1,\n minMinor: this.config.minMinorVersion,\n clientSdkVersion: msg.sdkVersion,\n minSdkVersion: this.config.minSdkVersion ?? DEFAULT_MIN_CLIENT_SDK_VERSION,\n });\n if (!result.ok) {\n logger.warn('ConnectHost', 'Rejected handshake', {\n dapp: dapp.name,\n reason: (result.error.data as { reason?: string } | undefined)?.reason,\n clientProtocol: msg.v,\n walletProtocol: SPHERE_CONNECT_VERSION,\n clientNetwork: msg.network ?? null,\n walletNetwork: this.snapshot.networkId ?? null,\n });\n this.config.onConnectionRejected?.(dapp, result.error, !!msg.silent);\n this.sendHandshakeResponse([], undefined, undefined, result.error, msg.v);\n return;\n }\n\n const clientInfo = { protocolVersion: msg.v, network: msg.network, sdkVersion: msg.sdkVersion };\n\n // Session resumption: if the client presents a valid existing sessionId,\n // skip the approval popup and restore the session without user interaction.\n // A resume DURING A LOCK succeeds and carries locked: true — a reloaded dApp is the\n // most common entry into this feature, and refusing it would leave that dApp with no\n // channel at all (it is not subscribed, so it would never receive wallet:unlocked).\n if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {\n const identity = locked ? this.snapshotIdentity() : this.getPublicIdentity();\n this.sendHandshakeResponse(\n [...this.grantedPermissions],\n this.session.id,\n identity,\n undefined,\n undefined,\n undefined,\n locked ? true : undefined,\n );\n if (locked) {\n this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n this.notifyLockedRequest('handshake', 'handshake');\n }\n return;\n }\n\n const requestedPermissions = msg.permissions as PermissionScope[];\n\n // FORCED SILENT while locked: the wallet's existing `if (silent) return { approved:\n // false }` branch refuses an unapproved origin with NO UI, and a previously approved\n // origin is approved from persisted state. No dApp request may ever raise a credential\n // surface, so a locked handshake must never be able to open one.\n const silent = msg.silent === true || locked;\n\n const { approved, grantedPermissions } = await withDeadline(\n Promise.resolve(\n this.config.onConnectionRequest(dapp, requestedPermissions, silent, clientInfo),\n ),\n this.config.handshakeDeadlineMs ?? DEFAULT_HANDSHAKE_DEADLINE_MS,\n () => {\n logger.warn('ConnectHost', 'Connection approval prompt timed out', { dapp: dapp.name });\n return { approved: false, grantedPermissions: [] as PermissionScope[] };\n },\n );\n\n // `locked` was read BEFORE a human-time await. Re-read the axis now, because the wallet can\n // have moved under us while the modal was open — an idle auto-lock, a cross-tab lock, a\n // logout, or an unlock.\n //\n // Getting this wrong in either direction is bad. Landing in 'locked' or 'unavailable' used\n // to mint an ACTIVE session on a non-live host while telling the dApp it was rejected: the\n // wallet then believes an origin is connected that has forgotten it exists, and under\n // 'unavailable' the gate refuses sphere_disconnect too, so that session is unclearable for\n // its whole TTL and onDisconnect never revokes the persisted origin approval. Landing in\n // 'live' (an unlock during the prompt) used to leave the dApp permanently walletLocked,\n // because the unlock edge had no session to notify at the time it fired.\n const stateAfterPrompt = this._walletState;\n if (stateAfterPrompt !== 'live' && stateAfterPrompt !== stateAtPrompt) {\n logger.warn(\n 'ConnectHost',\n `Wallet left 'live' while the approval prompt was open — refusing the handshake instead of minting a session (state=${stateAfterPrompt}, origin=${this.config.origin ?? 'unverified'})`,\n );\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n // Recompute so the response's `locked` flag and the trailing push describe NOW, not then.\n locked = stateAfterPrompt !== 'live';\n\n if (!approved) {\n // Deliberately NO notifyLockedRequest here. This origin was DENIED — it holds no\n // approval, so there is nothing for the user to unlock *for*, and counting it would put\n // an unvetted name in the wallet's own chrome and leak the lock to it. The notify stays\n // on the resume path and the approved path, where the origin demonstrably holds one.\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n // Create session\n const sessionId = createRequestId();\n const allPermissions = [...new Set([...DEFAULT_PERMISSIONS, ...grantedPermissions])];\n const ttl = this.config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;\n\n this.session = {\n id: sessionId,\n dapp,\n permissions: allPermissions,\n createdAt: Date.now(),\n expiresAt: ttl > 0 ? Date.now() + ttl : 0,\n active: true,\n };\n this.grantedPermissions = new Set(allPermissions);\n\n // Auto-push identity:changed to dApp whenever the wallet switches address.\n // MetaMask pattern: no sphere_subscribe needed — host pushes it unconditionally.\n // SKIPPED while locked: there is no Sphere to attach to. updateSphere() arms it on the\n // locked -> live edge, so no extra bookkeeping is needed.\n if (!locked) this.autoSubscribeIdentityChanged();\n\n // Build public identity — from the snapshot while locked.\n const identity = locked ? this.snapshotIdentity() : this.getPublicIdentity();\n\n this.sendHandshakeResponse(\n allPermissions,\n sessionId,\n identity,\n undefined,\n undefined,\n undefined,\n locked ? true : undefined,\n );\n if (locked) this.pushClientEvent(WALLET_EVENTS.LOCKED, {} as WalletLockedPayload);\n }\n\n // `warning` is a forward-compatible deprecation-notice slot (see SphereHandshake.warning);\n // no call site emits one yet — reserved for the deprecation-window policy.\n private sendHandshakeResponse(\n permissions: string[],\n sessionId: string | undefined,\n identity: PublicIdentity | undefined,\n error?: SphereRpcError,\n echoV?: string,\n warning?: SphereRpcError,\n locked?: boolean,\n ): void {\n const network: NetworkInfo | undefined =\n typeof this.snapshot.networkId === 'number' ? { id: this.snapshot.networkId } : undefined;\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: (error && echoV ? echoV : SPHERE_CONNECT_VERSION) as typeof SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'response',\n permissions,\n sessionId,\n identity,\n network,\n sdkVersion: SDK_VERSION,\n error,\n warning,\n ...(locked ? { locked: true } : {}),\n });\n }\n\n // ===========================================================================\n // RPC Requests (query)\n // ===========================================================================\n\n private async handleRpcRequest(msg: SphereRpcRequest): Promise<void> {\n // 1. Session check\n if (!this.session?.active) {\n this.sendError(msg.id, ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n return;\n }\n\n // 2. Session expiry — BEFORE the lock gate. A dead session must never be advertised\n // as \"retry after unlock\", and the check needs no Sphere.\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n // Answer THIS request before revoking: revokeSession() pushes wallet:disconnected, and\n // a client that cleans up on that event would otherwise reject this id locally before\n // our own frame arrives.\n this.sendError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n this.revokeSession();\n return;\n }\n\n // 3. Rate limit — BEFORE the lock gate, because this is what bounds onLockedRequest\n // volume. A dApp polling in a loop while locked is throttled by the existing\n // limiter, which is why there is no second anti-spam mechanism.\n if (!this.checkRateLimit()) {\n this.sendError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // 4. Disconnect, BEFORE the lock gate. It has to work in EVERY wallet state, and the gate\n // does not allow that: 'unavailable' refuses every query with NOT_CONNECTED, so a\n // session created just before Sphere went away could never be dropped — it sat active\n // for its whole TTL (24 h by default) and onDisconnect, the only hook that revokes the\n // persisted origin approval, never fired. Also before the permission check, because\n // sphere_disconnect has no mapped permission. It stays AFTER the rate limiter so a\n // disconnect flood is still throttled.\n if (msg.method === RPC_METHODS.DISCONNECT) {\n const disconnectedSession = this.session;\n // Answer BEFORE revoking, for the same reason as the expiry branch above.\n this.sendResult(msg.id, { disconnected: true });\n this.revokeSession();\n if (disconnectedSession && this.config.onDisconnect) {\n // Fire-and-forget: don't block the response\n Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch((err) => logger.warn('Connect', 'onDisconnect handler error', err));\n }\n return;\n }\n\n // 5. Lock gate — BEFORE the permission check, because hasMethodPermission() is false\n // for anything unmapped, so a locked unknown method would otherwise answer\n // PERMISSION_DENIED 4002: an un-retryable code that tells the dApp its permissions\n // are wrong.\n const decision = gate(this._walletState, true, 'query', msg.method);\n if (decision.kind === 'refuse') {\n this.sendError(msg.id, decision.error.code, decision.error.message, decision.error.data);\n if (decision.error.code === ERROR_CODES.WALLET_LOCKED) {\n logger.debug('ConnectHost', `Refused query ${msg.method} — WALLET_LOCKED 4009 (origin=${this.config.origin ?? 'unverified'})`);\n this.notifyLockedRequest('query', msg.method);\n }\n return;\n }\n\n\n // 5. Permission check\n if (!hasMethodPermission(this.grantedPermissions, msg.method)) {\n this.sendError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for ${msg.method}`);\n return;\n }\n\n // 6. Snapshot answer. Only sphere_getIdentity reaches this branch (host-state.ts).\n // An absent snapshot identity REFUSES: `undefined` sent as a SUCCESS result reads\n // to a dApp as \"the wallet has no identity\".\n if (decision.kind === 'serve-from-snapshot') {\n const identity = this.snapshotIdentity();\n if (!identity) {\n this.sendError(msg.id, ERROR_CODES.WALLET_LOCKED, WALLET_LOCKED_MESSAGE, { reason: 'locked' });\n this.notifyLockedRequest('query', msg.method);\n return;\n }\n this.sendResult(msg.id, identity);\n return;\n }\n\n // 7. Register BEFORE the first await, with the timer armed at insertion time, then\n // execute. Every send goes through settle(), which returns null when a lock, a\n // revoke, a destroy or the deadline already answered this id — the whole\n // \"exactly one frame per id\" mechanism.\n this.inFlight.add(msg.id, 'query', this.config.requestDeadlineMs ?? DEFAULT_REQUEST_DEADLINE_MS);\n try {\n const result = await this.executeMethod(msg.method, msg.params ?? {});\n if (!this.inFlight.settle(msg.id)) return;\n this.sendResult(msg.id, result);\n } catch (error) {\n if (!this.inFlight.settle(msg.id)) return;\n // Our OWN SphereError messages are DX ('Missing required parameter: identifier',\n // 'Communications module not available') and stay. Anything else is internal JS text\n // that must not cross the trust boundary into a third-party dApp.\n // instanceof is unreliable across bundle copies — check the name too.\n const isSphereError =\n error instanceof SphereError || (error as { name?: string })?.name === 'SphereError';\n if (isSphereError) {\n const e = error as SphereError;\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, e.message, { reason: e.code });\n return;\n }\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n }\n }\n\n // ===========================================================================\n // Intent Requests\n // ===========================================================================\n\n private async handleIntentRequest(msg: SphereIntentRequest): Promise<void> {\n // 1. Session check\n if (!this.session?.active) {\n this.sendIntentError(msg.id, ERROR_CODES.NOT_CONNECTED, NOT_CONNECTED_MESSAGE);\n return;\n }\n\n // 2. Session expiry — BEFORE the lock gate (same reason as the query path).\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n // Answer first — see the query path.\n this.sendIntentError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n this.revokeSession();\n return;\n }\n\n // 3. Rate limit. checkRateLimit() was called only from handleRpcRequest, so the intent\n // path — the money path, the one that opens wallet modals — was entirely unmetered.\n if (!this.checkRateLimit()) {\n this.sendIntentError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // 4. Lock gate. NO allow-list: every intent needs a live wallet.\n const decision = gate(this._walletState, true, 'intent', msg.action);\n if (decision.kind === 'refuse') {\n this.sendIntentError(msg.id, decision.error.code, decision.error.message, decision.error.data);\n if (decision.error.code === ERROR_CODES.WALLET_LOCKED) {\n logger.debug('ConnectHost', `Refused intent ${msg.action} — WALLET_LOCKED 4009 (origin=${this.config.origin ?? 'unverified'})`);\n this.notifyLockedRequest('intent', msg.action);\n }\n return;\n }\n\n // 5. Permission check\n if (!hasIntentPermission(this.grantedPermissions, msg.action)) {\n this.sendIntentError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for intent: ${msg.action}`);\n return;\n }\n\n // 6. Register BEFORE the first await, then delegate. The entry owns the deadline and\n // the AbortController, so lock / revoke / unavailable / destroy / deadline all\n // settle this id exactly once.\n const session = this.session;\n const entry = this.inFlight.add(\n msg.id,\n 'intent',\n this.config.intentDeadlineMs ?? DEFAULT_INTENT_DEADLINE_MS,\n );\n const ctx: IntentContext = {\n origin: this.config.origin,\n expiresAt: entry.deadline,\n signal: entry.controller.signal,\n };\n\n try {\n const autoHandler = ALWAYS_ASK_INTENTS.has(msg.action) ? undefined : this.autoApprovedIntents.get(msg.action);\n const response = autoHandler\n ? await autoHandler(msg.action, msg.params, session)\n : await this.config.onIntent(msg.action, msg.params, session, ctx);\n\n if (!this.inFlight.settle(msg.id)) return;\n if (response.error) {\n // Not relayed blindly: two of these codes describe the CHANNEL and say nothing true\n // about a spend the wallet has already taken ownership of. Everything else — including\n // USER_REJECTED, a legitimate pre-submission decline — passes through untouched. See\n // CHANNEL_ONLY_CODES.\n const asserts = CHANNEL_ONLY_CODES.has(response.error.code);\n if (asserts) {\n logger.warn(\n 'ConnectHost',\n `Wallet answered intent ${msg.action} with ${response.error.code}, which describes the channel rather than the spend — downgrading to INTENT_OUTCOME_UNKNOWN (origin=${this.config.origin ?? 'unverified'})`,\n );\n }\n this.sendIntentError(\n msg.id,\n asserts ? ERROR_CODES.INTENT_OUTCOME_UNKNOWN : response.error.code,\n asserts ? INTENT_UNKNOWN_MESSAGE : response.error.message,\n asserts ? undefined : response.error.data,\n );\n } else {\n this.sendIntentResult(msg.id, response.result);\n }\n } catch (error) {\n // A throw from onIntent or from an auto-approve handler used to reach\n // handleMessage's catch, which sent NOTHING — the dApp then hung for its full\n // intentTimeout and ended with a bare Error('Intent timeout: …') carrying no .code.\n logger.warn('ConnectHost', `Intent handler threw: ${msg.action}`, error);\n if (!this.inFlight.settle(msg.id)) return;\n this.sendIntentError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n }\n }\n\n // ===========================================================================\n // Method Router\n // ===========================================================================\n\n private async executeMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n // Subscription bookkeeping needs NO Sphere and is allow-listed while locked\n // (host-state.ts), so it must be answered BEFORE the dereference below — otherwise a\n // locked sphere_unsubscribe dies on requireSphere() with -32603 despite the gate\n // having let it through.\n switch (method) {\n case RPC_METHODS.SUBSCRIBE:\n return this.handleSubscribe(params.event as string);\n case RPC_METHODS.UNSUBSCRIBE:\n return this.handleUnsubscribe(params.event as string);\n }\n\n // ONE dereference for the whole router. The gate has already proven _walletState is\n // 'live' before we get here, so this is defence in depth — and it is what makes the\n // nullable field compile without a single `!`.\n const sphere = this.requireSphere();\n // §4 wire-compat (payments-compat.ts): the old query results are built from\n // facade reads. `sphere.payments` is read per branch, never up here — the\n // getter throws while no vertical runs and GET_IDENTITY must still answer.\n switch (method) {\n case RPC_METHODS.GET_IDENTITY:\n return this.getPublicIdentity();\n\n case RPC_METHODS.GET_BALANCE:\n case RPC_METHODS.GET_ASSETS:\n return sphere.payments.assets(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_FIAT_BALANCE:\n return { fiatBalance: sumFiatUsd(await sphere.payments.assets()) };\n\n case RPC_METHODS.GET_TOKENS:\n return this.stripTokenSdkData(\n sphere.payments.tokens(params.coinId ? { coinId: params.coinId as string } : undefined),\n );\n\n case RPC_METHODS.GET_HISTORY: {\n // Flat entry array on the wire; entries already carry the consumed shape\n // (`timestamp` mapped from the server's `ts`, plus symbol/tokenIds).\n const limit = typeof params.limit === 'number' && Number.isFinite(params.limit)\n ? params.limit\n : undefined;\n if (limit !== undefined) return (await sphere.payments.history({ limit })).entries;\n // INVARIANT: the legacy wire has no cursor — completeness is the contract.\n // Parameterless sphere_getHistory returned the ENTIRE ledger, so follow\n // the facade's cursors until the record is exhausted.\n let page = await sphere.payments.history(undefined);\n const entries = [...page.entries];\n while (page.more && page.cursor !== null) {\n page = await sphere.payments.history({ before: page.cursor });\n entries.push(...page.entries);\n }\n return entries;\n }\n\n case RPC_METHODS.RESOLVE:\n if (!params.identifier) {\n throw new SphereError('Missing required parameter: identifier', 'VALIDATION_ERROR');\n }\n return sphere.resolve(params.identifier as string);\n\n case RPC_METHODS.SUBSCRIBE:\n return this.handleSubscribe(params.event as string);\n\n case RPC_METHODS.UNSUBSCRIBE:\n return this.handleUnsubscribe(params.event as string);\n\n case RPC_METHODS.GET_CONVERSATIONS: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n const convos = comms.getConversations();\n const result: Array<{\n peerPubkey: string;\n peerNametag?: string;\n lastMessage: ConnectDirectMessage;\n unreadCount: number;\n messageCount: number;\n }> = [];\n // Collect conversations and track which ones need nametag resolution\n const needsResolve: Array<{ index: number; peerPubkey: string }> = [];\n for (const [peer, messages] of convos) {\n if (messages.length === 0) continue;\n const last = messages[messages.length - 1];\n // Find peer nametag from any message in the conversation\n const peerNametag =\n messages.find(m => m.senderPubkey === peer && m.senderNametag)?.senderNametag\n ?? messages.find(m => m.recipientPubkey === peer && m.recipientNametag)?.recipientNametag;\n const idx = result.length;\n result.push({\n peerPubkey: peer,\n peerNametag,\n lastMessage: last,\n unreadCount: comms.getUnreadCount(peer),\n messageCount: messages.length,\n });\n if (!peerNametag) {\n needsResolve.push({ index: idx, peerPubkey: peer });\n }\n }\n // Resolve missing nametags via transport (parallel, best-effort)\n if (needsResolve.length > 0) {\n const resolved = await Promise.all(\n needsResolve.map(({ peerPubkey }) =>\n comms.resolvePeerNametag(peerPubkey).catch((err) => { logger.debug('Connect', 'Peer Unicity ID resolution failed', err); return undefined; }),\n ),\n );\n for (let i = 0; i < needsResolve.length; i++) {\n if (resolved[i]) {\n result[needsResolve[i].index].peerNametag = resolved[i];\n }\n }\n }\n result.sort((a, b) => b.lastMessage.timestamp - a.lastMessage.timestamp);\n return result;\n }\n\n case RPC_METHODS.GET_MESSAGES: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n if (!params.peerPubkey) throw new SphereError('Missing required parameter: peerPubkey', 'VALIDATION_ERROR');\n return comms.getConversationPage(\n params.peerPubkey as string,\n {\n limit: params.limit as number | undefined,\n before: params.before as number | undefined,\n },\n );\n }\n\n case RPC_METHODS.GET_DM_UNREAD_COUNT: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n return {\n unreadCount: comms.getUnreadCount(\n params.peerPubkey as string | undefined,\n ),\n };\n }\n\n case RPC_METHODS.MARK_AS_READ: {\n const comms = sphere.communications;\n if (!comms) throw new SphereError('Communications module not available', 'MODULE_NOT_AVAILABLE');\n if (!params.messageIds || !Array.isArray(params.messageIds)) {\n throw new SphereError('Missing required parameter: messageIds (string[])', 'VALIDATION_ERROR');\n }\n await comms.markAsRead(params.messageIds as string[]);\n return { marked: true, count: (params.messageIds as string[]).length };\n }\n\n default:\n throw new SphereError(`Unknown method: ${method}`, 'VALIDATION_ERROR');\n }\n }\n\n // ===========================================================================\n // Event Subscriptions\n // ===========================================================================\n\n private autoSubscribeIdentityChanged(): void {\n if (this.eventSubscriptions.has(WALLET_EVENTS.IDENTITY_CHANGED)) return;\n const unsub = this.requireSphere().on('identity:changed' as SphereEventType, (data: unknown) => {\n this.pushClientEvent(WALLET_EVENTS.IDENTITY_CHANGED, data);\n });\n this.eventSubscriptions.set(WALLET_EVENTS.IDENTITY_CHANGED, unsub);\n }\n\n private handleSubscribe(eventName: string): { subscribed: boolean; event: string } {\n if (!eventName) throw new SphereError('Missing required parameter: event', 'VALIDATION_ERROR');\n\n // The four auto-pushed events must NOT be attached to Sphere.on(): it accepts any string\n // and never emits for them, so the subscription would deliver nothing forever. But the\n // answer is SUCCESS, not an error — the host pushes them unconditionally, so \"you are\n // subscribed\" is true, just satisfied by a different mechanism.\n //\n // Throwing here surfaced as INTERNAL_ERROR -32603 and silently broke every dApp built\n // before 2.1: `client.on('wallet:locked', …)` fires sphere_subscribe fire-and-forget, and\n // on 2.0 that call succeeded. A MINOR bump must not turn a working call into a crash. The\n // 2.1 client skips the call altogether, so this path exists only for older dApps.\n if (isAutoPushedEvent(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n // While locked there is no Sphere to attach to: record the KEY and answer success.\n // Refusing would be a bug, not a policy — ConnectClient.on() fires sphere_subscribe\n // fire-and-forget and never retries, so the stream would die forever after one lock.\n if (this._walletState === 'locked') {\n this.suspendedSubscriptions.add(eventName);\n return { subscribed: true, event: eventName };\n }\n\n if (this.eventSubscriptions.has(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n const sphere = this.requireSphere();\n\n // §4 wire-compat (payments-compat.ts): on a v2-facade host the old event names have no\n // bus emitter — re-emit them from the v2 events so nothing a dApp subscribes to silently\n // stops firing. Keyed under the OLD name, so unsubscribe and the lock snapshot/replay\n // bookkeeping work unchanged.\n {\n const compatUnsub = attachCompatEvent(sphere, eventName, (data) =>\n this.pushClientEvent(eventName, data),\n );\n if (compatUnsub) {\n this.eventSubscriptions.set(eventName, compatUnsub);\n return { subscribed: true, event: eventName };\n }\n }\n\n const unsub = sphere.on(eventName as SphereEventType, (data: unknown) => {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'event',\n event: eventName,\n data,\n });\n });\n\n this.eventSubscriptions.set(eventName, unsub);\n return { subscribed: true, event: eventName };\n }\n\n private handleUnsubscribe(eventName: string): { unsubscribed: boolean; event: string } {\n if (!eventName) throw new SphereError('Missing required parameter: event', 'VALIDATION_ERROR');\n\n const unsub = this.eventSubscriptions.get(eventName);\n if (unsub) {\n unsub();\n this.eventSubscriptions.delete(eventName);\n }\n // Also drop it from the lock snapshot: sphere_unsubscribe is on the locked allow-list,\n // so without this the next updateSphere() re-arm would resurrect the stream.\n this.suspendedSubscriptions.delete(eventName);\n return { unsubscribed: true, event: eventName };\n }\n\n private cleanupEventSubscriptions(): void {\n for (const [, unsub] of this.eventSubscriptions) {\n unsub();\n }\n this.eventSubscriptions.clear();\n }\n\n // ===========================================================================\n // Helpers\n // ===========================================================================\n\n /** Push an event to the dApp without requiring a sphere_subscribe call. */\n private pushClientEvent(event: string, data: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'event',\n event,\n data,\n });\n }\n\n /** The bound Sphere, or a typed refusal. The ONLY way the router may reach Sphere.\n * Unreachable in practice — the gate guarantees 'live' before the router is entered —\n * so this is defence in depth, not the primary mechanism. */\n private requireSphere(): SphereInstance {\n if (!this.sphere) {\n throw new SphereError(\n this._walletState === 'locked' ? WALLET_LOCKED_MESSAGE : 'Wallet unavailable',\n 'NOT_INITIALIZED',\n );\n }\n return this.sphere;\n }\n\n /** SNAPSHOT read. `undefined` means \"we never saw an identity\": callers MUST refuse,\n * never answer undefined-as-success — a dApp reads that as \"the wallet has no\n * identity\". Two explicit methods instead of one dual-mode method, so nobody can serve\n * a snapshot believing it is live. */\n private snapshotIdentity(): PublicIdentity | undefined {\n return this.snapshot.identity;\n }\n\n /** InFlightRegistry.onExpire sink. Filled in a later task; declared here so the\n * constructor can wire it. */\n private settleExpired(entry: InFlightEntry): void {\n logger.warn(\n 'ConnectHost',\n `Host deadline reached, answering on our own: ${entry.kind} ${entry.id} (origin=${this.config.origin ?? 'unverified'})`,\n );\n if (entry.kind === 'query') {\n this.sendError(entry.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n } else {\n // NOT INTENT_CANCELLED. The wallet was handed this intent and may already have submitted\n // the transfer — the deadline says only that we stopped waiting for the answer, never\n // that nothing happened. Asserting a cancel here is how a dApp re-offers a payment that\n // already went through.\n this.sendIntentError(entry.id, ERROR_CODES.INTENT_OUTCOME_UNKNOWN, INTENT_UNKNOWN_MESSAGE);\n }\n }\n\n /** Answer every request already in flight with one coded frame each. A request in flight\n * when the Sphere goes away otherwise answers -32603 with a raw JS message, returns\n * `undefined` AS SUCCESS (sphere_getIdentity), or — for a delegated intent — never\n * answers at all until the client's own 120 s timeout. */\n private settleInFlight(code: number, message: string, data?: WalletLockedData): void {\n for (const entry of this.inFlight.settleAll()) {\n if (entry.kind === 'query') {\n this.sendError(entry.id, code, message, data);\n continue;\n }\n // An INTENT is never answered with the caller's code. 4009 invites a retry after the\n // unlock and 4001 reads as \"never happened\", but this intent was already delegated to\n // the wallet: the user may have confirmed it and the transfer may be on the wire. Only\n // \"outcome unknown\" is true, and it explicitly forbids a retry.\n this.sendIntentError(entry.id, ERROR_CODES.INTENT_OUTCOME_UNKNOWN, INTENT_UNKNOWN_MESSAGE);\n }\n }\n\n /**\n * Notify-only. The host has ALREADY answered and never waits for the wallet.\n *\n * The wallet's only permitted reaction is a PASSIVE badge in its PERMANENT chrome; a\n * dApp request may trigger a CONSENT prompt but never a credential prompt. Volume is\n * bounded by checkRateLimit(), which guards all three entry points — there is no\n * coalescing, no cooldown and no cap by design.\n *\n * A throwing handler must not break the host.\n */\n private notifyLockedRequest(kind: LockedRequestContext['kind'], name: string): void {\n try {\n this.config.onLockedRequest?.({ origin: this.config.origin, kind, name });\n } catch (err) {\n logger.warn('ConnectHost', 'onLockedRequest handler error', err);\n }\n }\n\n /** Last-resort answer for a handler that threw before its own catch could run.\n * Id-bearing frames get a coded error (InFlightRegistry guarantees exactly one answer\n * per id); a handshake gets today's empty refusal, because a failed handshake must\n * reveal nothing. */\n private sendUnhandledError(msg: SphereConnectMessage, error: unknown): void {\n if (msg.type === 'request') {\n if (!this.inFlight.settle(msg.id) && this.inFlight.has(msg.id)) return;\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n return;\n }\n if (msg.type === 'intent') {\n this.inFlight.settle(msg.id);\n this.sendIntentError(msg.id, ERROR_CODES.INTERNAL_ERROR, INTERNAL_ERROR_MESSAGE);\n return;\n }\n if (msg.type === 'handshake' && msg.direction === 'request') {\n logger.warn('ConnectHost', 'Handshake handler threw; sending the empty refusal', error);\n this.sendHandshakeResponse([], undefined, undefined);\n }\n }\n\n private getPublicIdentity(): PublicIdentity | undefined {\n const id = this.requireSphere().identity;\n if (!id) return undefined;\n return {\n chainPubkey: id.chainPubkey,\n directAddress: id.directAddress,\n nametag: id.nametag,\n };\n }\n\n private stripTokenSdkData(tokens: unknown[]): unknown[] {\n return tokens.map((t) => {\n const token = t as Record<string, unknown>;\n // Return all fields except internal sdkData\n const { sdkData: _sdkData, ...publicFields } = token;\n return publicFields;\n });\n }\n\n private sendResult(id: string, result: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n result,\n });\n }\n\n private sendError(id: string, code: number, message: string, data?: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n error: { code, message, ...(data !== undefined ? { data } : {}) },\n });\n }\n\n private sendIntentResult(id: string, result: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent_result',\n id,\n result,\n });\n }\n\n private sendIntentError(id: string, code: number, message: string, data?: unknown): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent_result',\n id,\n error: { code, message, ...(data !== undefined ? { data } : {}) },\n });\n }\n\n private checkRateLimit(): boolean {\n const maxRps = this.config.maxRequestsPerSecond ?? DEFAULT_MAX_RPS;\n const now = Date.now();\n if (now > this.rateLimitResetAt) {\n this.rateLimitCounter = 0;\n this.rateLimitResetAt = now + 1000;\n }\n this.rateLimitCounter++;\n return this.rateLimitCounter <= maxRps;\n }\n}\n","/**\n * ConnectClient — dApp side of Sphere Connect.\n *\n * Lightweight client that communicates with a wallet's ConnectHost\n * through a ConnectTransport. Provides query and intent methods\n * that mirror the Sphere SDK API.\n *\n * Zero EXTERNAL dependencies: nothing from npm reaches a dApp through this file. It does use a\n * few SDK-internal modules (core/logger, and constants by way of connect/protocol's\n * SPHERE_NETWORKS), which are bundled into the Connect entry points.\n */\n\nimport { logger } from '../../core/logger';\nimport type { ConnectTransport, ConnectClientConfig, ConnectResult, ConnectEventHandler } from '../types';\nimport type {\n SphereConnectMessage,\n SphereHandshake,\n DAppMetadata,\n PublicIdentity,\n NetworkInfo,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n ERROR_CODES,\n WALLET_EVENTS,\n createRequestId,\n isAutoPushedEvent,\n} from '../protocol';\nimport { SDK_VERSION } from '../version';\nimport { ALL_PERMISSIONS } from '../permissions';\nimport type { PermissionScope } from '../permissions';\n\n/** Error thrown by ConnectClient carrying the host's structured rejection.\n * Discriminate on the numeric `.code` (bundle-safe), not `instanceof`. */\nexport class ConnectError extends Error {\n constructor(message: string, public readonly code: number, public readonly data?: unknown) {\n super(message);\n this.name = 'ConnectError';\n }\n}\n\nconst DEFAULT_TIMEOUT = 30000;\nconst DEFAULT_INTENT_TIMEOUT = 120000;\n\ninterface PendingRequest {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n /**\n * A QUERY may be failed with anything truthful — it is idempotent and safe to retry. An\n * INTENT may not: once it reached the wallet, the money may already be moving, so no local\n * teardown is allowed to tell the dApp it did not happen.\n */\n kind: 'query' | 'intent';\n}\n\nexport class ConnectClient {\n private readonly transport: ConnectTransport;\n private readonly dapp: DAppMetadata;\n private readonly requestedPermissions: PermissionScope[];\n private readonly timeout: number;\n private readonly intentTimeout: number;\n\n private readonly resumeSessionId: string | null;\n private readonly silent: boolean;\n private readonly network: NetworkInfo | undefined;\n\n private sessionId: string | null = null;\n private grantedPermissions: PermissionScope[] = [];\n private identity: PublicIdentity | null = null;\n private walletNet: NetworkInfo | null = null;\n private walletProto: string | null = null;\n private locked = false;\n private connected = false;\n\n private pendingRequests: Map<string, PendingRequest> = new Map();\n private eventHandlers: Map<string, Set<ConnectEventHandler>> = new Map();\n private unsubscribeTransport: (() => void) | null = null;\n\n // Handshake resolver (one-shot)\n private handshakeResolver: {\n resolve: (value: ConnectResult) => void;\n reject: (error: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n } | null = null;\n\n constructor(config: ConnectClientConfig) {\n this.transport = config.transport;\n this.dapp = config.dapp;\n this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;\n this.resumeSessionId = config.resumeSessionId ?? null;\n this.silent = config.silent ?? false;\n this.network = config.network;\n }\n\n // ===========================================================================\n // Connection\n // ===========================================================================\n\n /** Connect to the wallet. Returns session info and public identity. */\n async connect(): Promise<ConnectResult> {\n // Start listening\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\n\n return new Promise<ConnectResult>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.handshakeResolver = null;\n reject(new Error('Connection timeout'));\n }, this.timeout);\n\n this.handshakeResolver = { resolve, reject, timer };\n\n // Send handshake request\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'request',\n permissions: this.requestedPermissions,\n dapp: this.dapp,\n sdkVersion: SDK_VERSION,\n ...(this.network ? { network: this.network } : {}),\n ...(this.resumeSessionId ? { sessionId: this.resumeSessionId } : {}),\n ...(this.silent ? { silent: true } : {}),\n });\n });\n }\n\n /** Disconnect from the wallet */\n async disconnect(): Promise<void> {\n if (this.connected) {\n try {\n await this.query(RPC_METHODS.DISCONNECT);\n } catch {\n // Ignore errors during disconnect\n }\n }\n this.cleanup();\n }\n\n /** Whether currently connected */\n get isConnected(): boolean {\n return this.connected;\n }\n\n /** Granted permission scopes */\n get permissions(): readonly PermissionScope[] {\n return this.grantedPermissions;\n }\n\n /** Current session ID */\n get session(): string | null {\n return this.sessionId;\n }\n\n /** Public identity received during handshake */\n get walletIdentity(): PublicIdentity | null {\n return this.identity;\n }\n\n /** Wallet's active network, received during handshake. */\n get walletNetwork(): NetworkInfo | null {\n return this.walletNet;\n }\n\n /**\n * The wallet's Connect protocol version, captured from the handshake response `v`.\n * Null before the first handshake response and after a disconnect.\n *\n * Feature-detect with it: compare against '2.1' to decide whether the wallet can be\n * trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys\n * the session on lock and never emits either, so a dApp waiting for them against one\n * waits forever.\n *\n * CAVEAT: on an ERROR response the host echoes the dApp's own `v` back\n * (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the\n * dApp's version rather than the wallet's. Only trust it after a successful handshake.\n */\n get walletProtocol(): string | null {\n return this.walletProto;\n }\n\n /**\n * Whether the wallet was locked at the last handshake or lifecycle event.\n * A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.\n * Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME\n * session — do not disconnect, do not clear the session, do not re-handshake.\n */\n get walletLocked(): boolean {\n return this.locked;\n }\n\n // ===========================================================================\n // Query (read data)\n // ===========================================================================\n\n /** Send a query request and return the result */\n async query<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T> {\n if (!this.connected) throw new ConnectError('Not connected', ERROR_CODES.NOT_CONNECTED);\n\n const id = createRequestId();\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Query timeout: ${method}`));\n }, this.timeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\n kind: 'query',\n });\n\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'request',\n id,\n method,\n params,\n });\n });\n }\n\n // ===========================================================================\n // Intent (trigger wallet UI)\n // ===========================================================================\n\n /** Send an intent request. The wallet will open its UI for user confirmation. */\n async intent<T = unknown>(action: string, params: Record<string, unknown>): Promise<T> {\n if (!this.connected) throw new ConnectError('Not connected', ERROR_CODES.NOT_CONNECTED);\n\n const id = createRequestId();\n\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pendingRequests.delete(id);\n // TYPED, and specifically NOT a cancellation. We stopped waiting; the wallet may have\n // submitted the transfer. A dApp that retries here can pay twice, so the code says\n // \"outcome unknown\" and the docs forbid a retry (see ERROR_CODES.INTENT_OUTCOME_UNKNOWN).\n reject(\n new ConnectError(\n `Intent outcome unknown — do not retry; reconcile before acting: ${action}`,\n ERROR_CODES.INTENT_OUTCOME_UNKNOWN,\n ),\n );\n }, this.intentTimeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\n kind: 'intent',\n });\n\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'intent',\n id,\n action,\n params,\n });\n });\n }\n\n // ===========================================================================\n // Events\n // ===========================================================================\n\n /** Subscribe to a wallet event. Returns unsubscribe function. */\n on(event: string, handler: ConnectEventHandler): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n // Tell host to forward this event. Auto-pushed wallet events are NEVER routed through\n // sphere_subscribe: the host pushes them unconditionally and answers that RPC with success\n // without attaching anything (Sphere.on() would accept the name and never emit).\n if (this.connected && !isAutoPushedEvent(event)) {\n this.query(RPC_METHODS.SUBSCRIBE, { event }).catch((err) => logger.debug('Connect', 'Event subscription failed', err));\n }\n }\n this.eventHandlers.get(event)!.add(handler);\n\n return () => {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) {\n this.eventHandlers.delete(event);\n if (this.connected && !isAutoPushedEvent(event)) {\n this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch((err) => logger.debug('Connect', 'Event unsubscription failed', err));\n }\n }\n }\n };\n }\n\n // ===========================================================================\n // Message Handling\n // ===========================================================================\n\n private handleMessage(msg: SphereConnectMessage): void {\n // Handshake response\n if (msg.type === 'handshake' && msg.direction === 'response') {\n this.handleHandshakeResponse(msg);\n return;\n }\n\n // RPC response (query)\n if (msg.type === 'response') {\n this.handlePendingResponse(msg.id, msg.result, msg.error);\n return;\n }\n\n // Intent result\n if (msg.type === 'intent_result') {\n this.handlePendingResponse(msg.id, msg.result, msg.error);\n return;\n }\n\n // Event\n if (msg.type === 'event') {\n // 0. Only an ESTABLISHED session may move this client's authoritative state.\n // These frames now decide `identity`, `locked` and `connected` — and the docs tell a\n // dApp to gate a money replay on comparing that identity. A PostMessage client accepts\n // frames from anything that can reach its window, so an injected iframe could\n // otherwise rewrite `walletIdentity` to an address of its choosing before any\n // handshake had happened. A pre-connect wallet event is meaningless in any case:\n // there is no session for it to describe.\n if (!this.connected || !this.sessionId) {\n logger.warn('Connect', `Ignoring wallet event before a session exists: ${msg.event}`);\n return;\n }\n\n // 1. Unconditional NON-DESTRUCTIVE state update, BEFORE any handler lookup, so a\n // dApp that registered no handler still ends up with correct client state. The\n // client privileged no wallet event before 2.1, so an unhandled\n // wallet:disconnected was dropped entirely.\n if (msg.event === WALLET_EVENTS.LOCKED) {\n this.locked = true;\n } else if (msg.event === WALLET_EVENTS.UNLOCKED) {\n this.locked = false;\n const identity = (msg.data as { identity?: PublicIdentity } | undefined)?.identity;\n if (identity) this.identity = identity;\n } else if (msg.event === WALLET_EVENTS.DISCONNECTED) {\n // So a handler reading isConnected sees the truth.\n this.connected = false;\n } else if (msg.event === WALLET_EVENTS.IDENTITY_CHANGED) {\n const data = msg.data as PublicIdentity | undefined;\n if (data && typeof data.chainPubkey === 'string') this.identity = data;\n }\n\n // 2. Dispatch to registered handlers.\n this.dispatchEvent(msg.event, msg.data);\n\n // 3. Destructive step LAST — cleanup() clears eventHandlers, so running it after\n // dispatch is what keeps the dApp's own wallet:disconnected callback alive.\n if (msg.event === WALLET_EVENTS.DISCONNECTED) {\n this.cleanup();\n }\n }\n }\n\n private dispatchEvent(event: string, data: unknown): void {\n const handlers = this.eventHandlers.get(event);\n if (!handlers) return;\n for (const handler of handlers) {\n try {\n handler(data);\n } catch (err) {\n logger.debug('Connect', 'Event handler error', err);\n }\n }\n }\n\n private handleHandshakeResponse(msg: SphereConnectMessage & { type: 'handshake' }): void {\n if (!this.handshakeResolver) return;\n\n clearTimeout(this.handshakeResolver.timer);\n\n const m = msg as SphereHandshake;\n\n // Captured even on a refusal, but see the getter's caveat: the host echoes the dApp's\n // own `v` on an error response.\n this.walletProto = (msg as { v?: string }).v ?? null;\n\n if (m.error) {\n this.handshakeResolver.reject(new ConnectError(m.error.message, m.error.code, m.error.data));\n this.handshakeResolver = null;\n return;\n }\n\n if (msg.sessionId && msg.identity) {\n this.sessionId = msg.sessionId;\n this.grantedPermissions = msg.permissions as PermissionScope[];\n this.identity = msg.identity;\n this.walletNet = m.network ?? null;\n this.locked = m.locked === true;\n this.connected = true;\n if (m.warning) logger.warn('Connect', 'Wallet deprecation notice', m.warning.message);\n this.handshakeResolver.resolve({\n sessionId: msg.sessionId,\n permissions: this.grantedPermissions,\n identity: msg.identity,\n // A resume DURING a lock succeeds: the dApp is connected on the same session and\n // must not re-handshake. It will get wallet:unlocked when the user unlocks.\n ...(this.locked ? { locked: true } : {}),\n });\n } else {\n this.handshakeResolver.reject(new Error('Connection rejected by wallet'));\n }\n\n this.handshakeResolver = null;\n }\n\n private handlePendingResponse(\n id: string,\n result: unknown,\n error?: { code: number; message: string; data?: unknown },\n ): void {\n const pending = this.pendingRequests.get(id);\n if (!pending) return;\n\n clearTimeout(pending.timer);\n this.pendingRequests.delete(id);\n\n if (error) {\n pending.reject(new ConnectError(error.message, error.code, error.data));\n } else {\n pending.resolve(result);\n }\n }\n\n // ===========================================================================\n // Cleanup\n // ===========================================================================\n\n private cleanup(): void {\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\n }\n\n // A handshake in flight has to be settled too. wallet:disconnected arriving mid-connect()\n // left it parked for the full client timeout and then rejected untyped, so a dApp could\n // not tell \"the wallet dropped us\" from \"the wallet is slow\".\n if (this.handshakeResolver) {\n clearTimeout(this.handshakeResolver.timer);\n this.handshakeResolver.reject(new ConnectError('Disconnected', ERROR_CODES.NOT_CONNECTED));\n this.handshakeResolver = null;\n }\n\n // Reject all pending requests with a TYPED error. new Error('Disconnected') carried no\n // .code, so a dApp could not tell it from a transport failure.\n //\n // A pending INTENT is rejected with INTENT_OUTCOME_UNKNOWN, never NOT_CONNECTED: the\n // wallet already had it and may have submitted the transfer, so \"not connected\" would read\n // as \"your spend never ran\" and invite a retry that pays twice.\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(\n pending.kind === 'intent'\n ? new ConnectError(\n 'Intent outcome unknown — do not retry; reconcile before acting',\n ERROR_CODES.INTENT_OUTCOME_UNKNOWN,\n )\n : new ConnectError('Disconnected', ERROR_CODES.NOT_CONNECTED),\n );\n }\n this.pendingRequests.clear();\n this.eventHandlers.clear();\n\n this.connected = false;\n this.sessionId = null;\n this.grantedPermissions = [];\n this.identity = null;\n this.walletNet = null;\n this.walletProto = null;\n this.locked = false;\n }\n}\n","/** Standard base64 (RFC 4648 §4, with padding), hand-rolled: no Buffer or atob, identical in Node and browsers. */\n\nconst ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst INVALID = 0xff;\nconst SEXTETS = new Uint8Array(128).fill(INVALID);\nfor (let i = 0; i < ALPHABET.length; i++) SEXTETS[ALPHABET.charCodeAt(i)] = i;\n\nexport function base64Encode(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;\n const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;\n const triple = (bytes[i] << 16) | (b1 << 8) | b2;\n out += ALPHABET[triple >> 18] + ALPHABET[(triple >> 12) & 0x3f];\n out += i + 1 < bytes.length ? ALPHABET[(triple >> 6) & 0x3f] : '=';\n out += i + 2 < bytes.length ? ALPHABET[triple & 0x3f] : '=';\n }\n return out;\n}\n\n/** Decodes only the canonical spelling — padded, standard alphabet, zero padding bits. `null` for anything else. */\nexport function base64DecodeCanonical(text: string): Uint8Array | null {\n if (text.length % 4 !== 0) return null;\n const padding = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0;\n const out = new Uint8Array((text.length / 4) * 3 - padding);\n let buffer = 0;\n let bits = 0;\n let offset = 0;\n for (let i = 0; i < text.length - padding; i++) {\n const code = text.charCodeAt(i);\n const sextet = code < SEXTETS.length ? SEXTETS[code] : INVALID;\n if (sextet === INVALID) return null;\n buffer = ((buffer << 6) | sextet) & 0xfff;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[offset++] = buffer >> bits;\n }\n }\n return (buffer & ((1 << bits) - 1)) === 0 ? out : null;\n}\n","/**\n * JSON wire form of NFT content for the `mint_nft` Connect intent: `NftContent` with every inline\n * `NftMedia.bytes` as base64, because Connect messages are JSON. Shape only — the value rules\n * (text, media types, URIs, the payload cap) stay in `encodeNftContent`.\n */\nimport { base64DecodeCanonical, base64Encode } from '../core/base64';\nimport { SphereError } from '../core/errors';\nimport type {\n NftAttribute,\n NftContent,\n NftLink,\n NftMedia,\n NftMediaRef,\n NftMetadata,\n} from '../token-engine/nft-payload';\n\n/** An inline file; `bytes` is standard base64 (RFC 4648 §4, with padding). */\nexport interface WireNftMedia {\n readonly kind: 'media';\n readonly media_type: string;\n readonly bytes: string;\n}\n\nexport interface WireNftMetadata {\n readonly kind: 'metadata';\n readonly name: string;\n readonly description: string | null;\n readonly image: WireNftMedia | NftLink | null;\n readonly animation_url: WireNftMedia | NftLink | null;\n readonly external_url: string | null;\n readonly attributes: readonly NftAttribute[];\n readonly collection: string | null;\n /** Hex, passed through unchanged; `encodeNftContent` judges its value. */\n readonly collection_id: string | null;\n}\n\nexport type WireNftContent = WireNftMetadata | WireNftMedia | NftLink;\n\n/** A type alias, not an interface, so a typed value passes as `ConnectClient.intent` params. */\nexport type MintNftIntentParams = {\n readonly content: WireNftContent;\n /** Default `true`: the wallet wraps the payload in NftSigned with its chain key as creator. */\n readonly sign?: boolean;\n};\n\nexport interface MintNftIntentResult {\n /** 64 lowercase hex. */\n readonly tokenId: string;\n}\n\ntype WireRecord = Record<string, unknown>;\n\nconst METADATA_FIELDS: readonly string[] = [\n 'kind', 'name', 'description', 'image', 'animation_url', 'external_url', 'attributes', 'collection', 'collection_id',\n];\nconst MEDIA_FIELDS: readonly string[] = ['kind', 'media_type', 'bytes'];\nconst LINK_FIELDS: readonly string[] = ['kind', 'media_type', 'uri', 'sha256'];\nconst ATTRIBUTE_FIELDS: readonly string[] = ['trait_type', 'value'];\nconst CONTENT_KINDS = \"must be 'metadata', 'media' or 'link'\";\nconst MEDIA_REF_KINDS = \"must be 'media' or 'link'\";\n\nfunction refuse(field: string, rule: string): SphereError {\n return new SphereError(`Invalid NFT ${field}: ${rule}`, 'VALIDATION_ERROR');\n}\n\n// ── to wire ─────────────────────────────────────────────────────────────────\n\nfunction mediaToWire(media: NftMedia, field: string): WireNftMedia {\n if (!(media.bytes instanceof Uint8Array)) throw refuse(`${field}.bytes`, 'must be a Uint8Array');\n return { kind: 'media', media_type: media.media_type, bytes: base64Encode(media.bytes) };\n}\n\nfunction linkToWire(link: NftLink): NftLink {\n return { kind: 'link', media_type: link.media_type, uri: link.uri, sha256: link.sha256 };\n}\n\nfunction mediaRefToWire(ref: NftMediaRef | null, field: string): WireNftMedia | NftLink | null {\n if (ref === null) return null;\n if (ref?.kind === 'media') return mediaToWire(ref, field);\n if (ref?.kind === 'link') return linkToWire(ref);\n throw refuse(`${field}.kind`, MEDIA_REF_KINDS);\n}\n\nfunction metadataToWire(metadata: NftMetadata): WireNftMetadata {\n return {\n kind: 'metadata',\n name: metadata.name,\n description: metadata.description,\n image: mediaRefToWire(metadata.image, 'content.image'),\n animation_url: mediaRefToWire(metadata.animation_url, 'content.animation_url'),\n external_url: metadata.external_url,\n attributes: metadata.attributes.map(({ trait_type, value }) => ({ trait_type, value })),\n collection: metadata.collection,\n collection_id: metadata.collection_id,\n };\n}\n\n/** `NftContent` → its JSON wire form. Inline bytes become base64; every other field is copied unchanged. */\nexport function nftContentToWire(content: NftContent): WireNftContent {\n switch (content?.kind) {\n case 'metadata':\n return metadataToWire(content);\n case 'media':\n return mediaToWire(content, 'content');\n case 'link':\n return linkToWire(content);\n default:\n throw refuse('content.kind', CONTENT_KINDS);\n }\n}\n\n// ── from wire ───────────────────────────────────────────────────────────────\n\nfunction recordAt(value: unknown, field: string, rule = 'must be an object'): WireRecord {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) throw refuse(field, rule);\n return value as WireRecord;\n}\n\nfunction requireExactFields(record: WireRecord, field: string, fields: readonly string[]): void {\n const missing = fields.find((name) => !Object.prototype.hasOwnProperty.call(record, name));\n if (missing !== undefined) throw refuse(`${field}.${missing}`, 'is missing — an absent optional field is null');\n const extra = Object.keys(record).find((name) => !fields.includes(name));\n if (extra !== undefined) throw refuse(`${field}.${extra}`, 'is not a field of this item');\n}\n\nfunction stringAt(record: WireRecord, field: string, name: string): string {\n const value = record[name];\n if (typeof value !== 'string') throw refuse(`${field}.${name}`, 'must be a string');\n return value;\n}\n\nfunction optionalStringAt(record: WireRecord, field: string, name: string): string | null {\n const value = record[name];\n if (value === null) return null;\n if (typeof value !== 'string') throw refuse(`${field}.${name}`, 'must be a string or null');\n return value;\n}\n\nfunction mediaFromWire(record: WireRecord, field: string): NftMedia {\n requireExactFields(record, field, MEDIA_FIELDS);\n const mediaType = stringAt(record, field, 'media_type');\n const bytes = base64DecodeCanonical(stringAt(record, field, 'bytes'));\n if (bytes === null) throw refuse(`${field}.bytes`, 'must be canonical standard base64 with padding (RFC 4648 §4)');\n return { kind: 'media', media_type: mediaType, bytes };\n}\n\nfunction linkFromWire(record: WireRecord, field: string): NftLink {\n requireExactFields(record, field, LINK_FIELDS);\n return {\n kind: 'link',\n media_type: stringAt(record, field, 'media_type'),\n uri: stringAt(record, field, 'uri'),\n sha256: stringAt(record, field, 'sha256'),\n };\n}\n\nfunction mediaRefFromWire(value: unknown, field: string): NftMediaRef | null {\n if (value === null) return null;\n const record = recordAt(value, field, 'must be a media or link object, or null');\n if (record.kind === 'media') return mediaFromWire(record, field);\n if (record.kind === 'link') return linkFromWire(record, field);\n throw refuse(`${field}.kind`, MEDIA_REF_KINDS);\n}\n\nfunction attributeFromWire(value: unknown, field: string): NftAttribute {\n const record = recordAt(value, field);\n requireExactFields(record, field, ATTRIBUTE_FIELDS);\n const traitType = stringAt(record, field, 'trait_type');\n const attributeValue = record.value;\n if (typeof attributeValue !== 'string' && typeof attributeValue !== 'number') {\n throw refuse(`${field}.value`, 'must be a string or a number');\n }\n return { trait_type: traitType, value: attributeValue };\n}\n\nfunction attributesFromWire(value: unknown, field: string): NftAttribute[] {\n if (!Array.isArray(value)) throw refuse(field, 'must be an array — [] when there are none');\n return Array.from(value, (entry: unknown, index) => attributeFromWire(entry, `${field}[${index}]`));\n}\n\nfunction metadataFromWire(record: WireRecord): NftMetadata {\n requireExactFields(record, 'content', METADATA_FIELDS);\n return {\n kind: 'metadata',\n name: stringAt(record, 'content', 'name'),\n description: optionalStringAt(record, 'content', 'description'),\n image: mediaRefFromWire(record.image, 'content.image'),\n animation_url: mediaRefFromWire(record.animation_url, 'content.animation_url'),\n external_url: optionalStringAt(record, 'content', 'external_url'),\n attributes: attributesFromWire(record.attributes, 'content.attributes'),\n collection: optionalStringAt(record, 'content', 'collection'),\n collection_id: optionalStringAt(record, 'content', 'collection_id'),\n };\n}\n\n/**\n * The wire form → `NftContent`. Checks shape and base64 only, throwing a `SphereError`\n * (`VALIDATION_ERROR`) whose message names the offending field.\n */\nexport function nftContentFromWire(wire: unknown): NftContent {\n const record = recordAt(wire, 'content');\n switch (record.kind) {\n case 'metadata':\n return metadataFromWire(record);\n case 'media':\n return mediaFromWire(record, 'content');\n case 'link':\n return linkFromWire(record, 'content');\n default:\n throw refuse('content.kind', CONTENT_KINDS);\n }\n}\n"],"mappings":"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","names":["identity"]}