@unicitylabs/sphere-sdk 0.12.0-dev.1 → 0.13.0
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.
- package/dist/connect/index.cjs +802 -110
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +305 -16
- package/dist/connect/index.d.ts +305 -16
- package/dist/connect/index.js +802 -110
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +20 -1
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +20 -1
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +208 -41
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +33 -1
- package/dist/impl/browser/connect/index.d.ts +33 -1
- package/dist/impl/browser/connect/index.js +208 -41
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +101 -1
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +6 -1
- package/dist/impl/nodejs/connect/index.d.ts +6 -1
- package/dist/impl/nodejs/connect/index.js +101 -1
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/index.cjs +20 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/connect/index.d.cts
CHANGED
|
@@ -30,7 +30,7 @@ declare const HOST_READY_TIMEOUT = 30000;
|
|
|
30
30
|
* JSON-RPC-like message types for wallet ↔ dApp communication.
|
|
31
31
|
*/
|
|
32
32
|
declare const SPHERE_CONNECT_NAMESPACE = "sphere-connect";
|
|
33
|
-
declare const SPHERE_CONNECT_VERSION = "2.
|
|
33
|
+
declare const SPHERE_CONNECT_VERSION = "2.1";
|
|
34
34
|
|
|
35
35
|
declare const RPC_METHODS: {
|
|
36
36
|
readonly GET_IDENTITY: "sphere_getIdentity";
|
|
@@ -83,12 +83,40 @@ declare const ERROR_CODES: {
|
|
|
83
83
|
readonly RATE_LIMITED: 4006;
|
|
84
84
|
readonly UNSUPPORTED_PROTOCOL_VERSION: 4007;
|
|
85
85
|
readonly INCOMPATIBLE_NETWORK: 4008;
|
|
86
|
+
readonly WALLET_LOCKED: 4009;
|
|
86
87
|
readonly INSUFFICIENT_BALANCE: 4100;
|
|
87
88
|
readonly INVALID_RECIPIENT: 4101;
|
|
88
89
|
readonly TRANSFER_FAILED: 4102;
|
|
89
90
|
readonly INTENT_CANCELLED: 4200;
|
|
91
|
+
/**
|
|
92
|
+
* The intent was DELEGATED to the wallet and the host lost track of the answer — a host
|
|
93
|
+
* deadline fired, or the wallet locked / logged out mid-flight. **The outcome is UNKNOWN:
|
|
94
|
+
* the money may or may not have moved.**
|
|
95
|
+
*
|
|
96
|
+
* A dApp MUST NOT retry on this code. Reconcile out of band (poll the recipient, the
|
|
97
|
+
* aggregator, or your own backend) and only then decide.
|
|
98
|
+
*
|
|
99
|
+
* This code exists because every other answer would be a lie. `INTENT_CANCELLED` (4200)
|
|
100
|
+
* asserts the user declined and nothing happened; `WALLET_LOCKED` (4009) invites a retry
|
|
101
|
+
* after the unlock. Sending either for an intent the wallet had already submitted is how a
|
|
102
|
+
* paid-but-not-credited order — and then a double spend on retry — happens.
|
|
103
|
+
*/
|
|
104
|
+
readonly INTENT_OUTCOME_UNKNOWN: 4201;
|
|
90
105
|
};
|
|
91
106
|
type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];
|
|
107
|
+
/** `data` carried by every WALLET_LOCKED (4009) refusal. */
|
|
108
|
+
interface WalletLockedData {
|
|
109
|
+
/** Discriminator. Always the literal 'locked' — reserved for future refusal reasons. */
|
|
110
|
+
readonly reason: 'locked';
|
|
111
|
+
/**
|
|
112
|
+
* Whether the wallet's own unlock surface is currently on screen.
|
|
113
|
+
* DECLARED in the fail-fast release and always absent there; POPULATED in the resume
|
|
114
|
+
* release from ConnectHostConfig.unlockSurface(), evaluated at refusal time (visibility
|
|
115
|
+
* changes over time, so a static value would lie). Feeds
|
|
116
|
+
* ConnectClientConfig.onWalletAttention.
|
|
117
|
+
*/
|
|
118
|
+
readonly unlockSurface?: 'visible' | 'background';
|
|
119
|
+
}
|
|
92
120
|
interface SphereMessageBase {
|
|
93
121
|
readonly ns: typeof SPHERE_CONNECT_NAMESPACE;
|
|
94
122
|
readonly v: typeof SPHERE_CONNECT_VERSION;
|
|
@@ -145,6 +173,11 @@ interface SphereHandshake extends SphereMessageBase {
|
|
|
145
173
|
readonly error?: SphereRpcError;
|
|
146
174
|
/** Response: non-fatal deprecation notice (does not block the connection). */
|
|
147
175
|
readonly warning?: SphereRpcError;
|
|
176
|
+
/** Response only: the wallet is LOCKED and the session is ALIVE. The dApp is connected
|
|
177
|
+
* and must not re-handshake; it will receive `wallet:unlocked` on the same session.
|
|
178
|
+
* Additive and safe for old clients: handleHandshakeResponse reads only sessionId,
|
|
179
|
+
* permissions, identity, network, warning and error and ignores unknown fields. */
|
|
180
|
+
readonly locked?: boolean;
|
|
148
181
|
}
|
|
149
182
|
interface SphereRpcError {
|
|
150
183
|
readonly code: number;
|
|
@@ -169,14 +202,54 @@ interface PublicIdentity {
|
|
|
169
202
|
* No sphere_subscribe call needed — host sends these unconditionally.
|
|
170
203
|
*/
|
|
171
204
|
declare const WALLET_EVENTS: {
|
|
172
|
-
/** Wallet
|
|
173
|
-
*
|
|
205
|
+
/** Wallet is LOCKED — the session is STILL ALIVE. Requests are answered
|
|
206
|
+
* WALLET_LOCKED (4009) until `wallet:unlocked`. The dApp must NOT disconnect,
|
|
207
|
+
* must NOT clear its sessionId, and must NOT re-handshake.
|
|
208
|
+
* Payload: {@link WalletLockedPayload}. Pushed by ConnectHost.setLocked() and
|
|
209
|
+
* immediately after a handshake response carrying `locked: true`. */
|
|
174
210
|
readonly LOCKED: "wallet:locked";
|
|
211
|
+
/** Wallet was unlocked — the SAME session continues: no re-handshake, no re-approval,
|
|
212
|
+
* no re-subscribe (the host re-arms the dApp's subscriptions before pushing this).
|
|
213
|
+
* Payload: {@link WalletUnlockedPayload} — carries the CURRENT identity, which may
|
|
214
|
+
* differ from the one the dApp connected with. Pushed by ConnectHost.updateSphere()
|
|
215
|
+
* on the locked -> live edge only. */
|
|
216
|
+
readonly UNLOCKED: "wallet:unlocked";
|
|
217
|
+
/** The session is GONE (logout, wallet deleted, dApp sphere_disconnect, expiry seen at
|
|
218
|
+
* unlock, a different seed behind the lock screen, host destroy).
|
|
219
|
+
* The dApp must clear its session and re-handshake to continue. Unlocking does not cure it.
|
|
220
|
+
* Payload: {@link WalletDisconnectedPayload}. Pushed by ConnectHost.revokeSession(). */
|
|
221
|
+
readonly DISCONNECTED: "wallet:disconnected";
|
|
175
222
|
/** Active wallet address changed. dApp should update displayed identity.
|
|
176
223
|
* Pushed automatically by ConnectHost — no sphere_subscribe needed. */
|
|
177
224
|
readonly IDENTITY_CHANGED: "identity:changed";
|
|
178
225
|
};
|
|
179
226
|
type WalletEvent = (typeof WALLET_EVENTS)[keyof typeof WALLET_EVENTS];
|
|
227
|
+
/** Payload of {@link WALLET_EVENTS.LOCKED}. Intentionally empty — matches today's push,
|
|
228
|
+
* so an old dApp sees no shape change. */
|
|
229
|
+
type WalletLockedPayload = Record<string, never>;
|
|
230
|
+
/** Payload of {@link WALLET_EVENTS.DISCONNECTED}. Intentionally empty. */
|
|
231
|
+
type WalletDisconnectedPayload = Record<string, never>;
|
|
232
|
+
/** Payload of {@link WALLET_EVENTS.UNLOCKED}. */
|
|
233
|
+
interface WalletUnlockedPayload {
|
|
234
|
+
/** The wallet's public identity at unlock time. Absent when the rebound Sphere has no
|
|
235
|
+
* identity yet (JSON transports drop undefined keys).
|
|
236
|
+
* Unlock is NOT implicitly the same wallet: the lock screen's "Forgot password ->
|
|
237
|
+
* restore from recovery phrase" installs a DIFFERENT seed while origin approvals are
|
|
238
|
+
* keyed by origin alone. The host's own lock-edge guard is authoritative (it revokes
|
|
239
|
+
* instead of pushing this event on a mismatch); this field is what lets a dApp render
|
|
240
|
+
* honestly and what the client-side retry queue checks before draining. */
|
|
241
|
+
readonly identity?: PublicIdentity;
|
|
242
|
+
}
|
|
243
|
+
/** Payload of {@link WALLET_EVENTS.IDENTITY_CHANGED} — unchanged: the host forwards
|
|
244
|
+
* Sphere's own event data verbatim and pushes a PublicIdentity from updateSphere().
|
|
245
|
+
* Typed as the union for honesty. */
|
|
246
|
+
type WalletIdentityChangedPayload = PublicIdentity | unknown;
|
|
247
|
+
/** Events the host pushes unconditionally. They must NEVER be routed through
|
|
248
|
+
* `sphere_subscribe`: Sphere.on() accepts any string and would silently never emit,
|
|
249
|
+
* so the subscribe would succeed and deliver nothing forever. */
|
|
250
|
+
declare const AUTO_PUSHED_EVENTS: readonly WalletEvent[];
|
|
251
|
+
/** True for an event the host pushes unconditionally (see {@link AUTO_PUSHED_EVENTS}). */
|
|
252
|
+
declare function isAutoPushedEvent(event: string): event is WalletEvent;
|
|
180
253
|
/** Check if a message belongs to the Sphere Connect protocol.
|
|
181
254
|
* Handshakes are accepted at ANY version (the version decision happens in the
|
|
182
255
|
* handshake handler so an incompatible peer gets a clean typed error instead of a
|
|
@@ -242,11 +315,56 @@ interface ConnectSession {
|
|
|
242
315
|
readonly expiresAt: number;
|
|
243
316
|
active: boolean;
|
|
244
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* State of the host's binding to a Sphere instance. ORTHOGONAL to `session`:
|
|
320
|
+
* a locked wallet keeps its session, and a live wallet may have none.
|
|
321
|
+
*
|
|
322
|
+
* - 'live' — Sphere bound and usable.
|
|
323
|
+
* - 'locked' — the wallet is locked: the Sphere reference is DROPPED and the
|
|
324
|
+
* session is PRESERVED. Requests outside the allow-list answer
|
|
325
|
+
* WALLET_LOCKED (4009). Curable by updateSphere().
|
|
326
|
+
* - 'unavailable' — Sphere is gone for a NON-lock reason (a generic init failure leaves
|
|
327
|
+
* `sphere === null, isLocked === false`). Entering it revokes the
|
|
328
|
+
* session and pushes wallet:disconnected. NOT curable by unlocking.
|
|
329
|
+
*/
|
|
330
|
+
type WalletState = 'live' | 'locked' | 'unavailable';
|
|
331
|
+
/** Context handed to {@link ConnectHostConfig.onLockedRequest}. Notify-only. */
|
|
332
|
+
interface LockedRequestContext {
|
|
333
|
+
/** ConnectHostConfig.origin, when the wallet supplied one. NEVER the dApp-claimed
|
|
334
|
+
* `session.dapp.url`. Absent = "a connected app"; make no claim you cannot verify. */
|
|
335
|
+
readonly origin?: string;
|
|
336
|
+
readonly kind: 'query' | 'intent' | 'handshake';
|
|
337
|
+
/** An RPC_METHODS value for 'query', an INTENT_ACTIONS value for 'intent',
|
|
338
|
+
* the literal 'handshake' for 'handshake'. */
|
|
339
|
+
readonly name: string;
|
|
340
|
+
}
|
|
341
|
+
/** 4th argument of {@link ConnectHostConfig.onIntent} (Connect 2.1). */
|
|
342
|
+
interface IntentContext {
|
|
343
|
+
readonly origin?: string;
|
|
344
|
+
/** Epoch ms after which the host answers on its own and aborts `signal`. */
|
|
345
|
+
readonly expiresAt: number;
|
|
346
|
+
/** Aborted when the host settles the intent for ANY reason (deadline, lock, revoke,
|
|
347
|
+
* unavailable, destroy). The wallet MUST dismiss its modal on abort. */
|
|
348
|
+
readonly signal: AbortSignal;
|
|
349
|
+
}
|
|
245
350
|
interface ConnectHostConfig {
|
|
246
|
-
/** Sphere SDK instance to bridge
|
|
351
|
+
/** Sphere SDK instance to bridge. MAY be null (or omitted-as-null) when
|
|
352
|
+
* `initialWalletState` is 'locked' — the host never serves anything from a destroyed
|
|
353
|
+
* Sphere. A null sphere with initialWalletState 'live' is coerced to 'unavailable' with
|
|
354
|
+
* a logger.warn rather than throwing, because a throw here breaks the wallet's React
|
|
355
|
+
* mount. */
|
|
247
356
|
sphere: unknown;
|
|
248
357
|
/** Transport layer for communication */
|
|
249
358
|
transport: ConnectTransport;
|
|
359
|
+
/**
|
|
360
|
+
* The origin this host serves, when the wallet knows it (e.g. 'https://app.example.com').
|
|
361
|
+
* OPTIONAL and it stays optional: with the credential prompt gone, NO security decision
|
|
362
|
+
* depends on it — it only labels a passive badge and log lines. Requiring it would break
|
|
363
|
+
* the nodejs/ mock wallet host and any WebSocket host, neither of which has a browser
|
|
364
|
+
* origin. When absent, the wallet's badge says "a connected app" and claims no origin.
|
|
365
|
+
* Never confuse it with `session.dapp.url`, which is dApp-CLAIMED metadata.
|
|
366
|
+
*/
|
|
367
|
+
origin?: string;
|
|
250
368
|
/** Called when dApp requests connection. Wallet shows approval UI.
|
|
251
369
|
* When `silent` is true, the wallet must NOT open any UI — return rejected immediately if origin is unknown. */
|
|
252
370
|
onConnectionRequest: (dapp: DAppMetadata, requestedPermissions: PermissionScope[], silent?: boolean, clientInfo?: {
|
|
@@ -257,8 +375,11 @@ interface ConnectHostConfig {
|
|
|
257
375
|
approved: boolean;
|
|
258
376
|
grantedPermissions: PermissionScope[];
|
|
259
377
|
}>;
|
|
260
|
-
/** Called when dApp sends an intent. Wallet opens corresponding UI.
|
|
261
|
-
|
|
378
|
+
/** Called when dApp sends an intent. Wallet opens corresponding UI.
|
|
379
|
+
* `ctx` (added in Connect 2.1) carries the host-side deadline and an AbortSignal:
|
|
380
|
+
* the wallet MUST dismiss its modal on abort, otherwise the host's own deadline
|
|
381
|
+
* manufactures the double-submit it was added to prevent. */
|
|
382
|
+
onIntent: (action: string, params: Record<string, unknown>, session: ConnectSession, ctx?: IntentContext) => Promise<{
|
|
262
383
|
result?: unknown;
|
|
263
384
|
error?: {
|
|
264
385
|
code: number;
|
|
@@ -271,14 +392,47 @@ interface ConnectHostConfig {
|
|
|
271
392
|
* in its UI. Does NOT affect the decision (the host already decided). `silent` is true for
|
|
272
393
|
* auto-connect attempts — the wallet should not show UI for those. */
|
|
273
394
|
onConnectionRejected?: (dapp: DAppMetadata | undefined, error: SphereRpcError, silent?: boolean) => void;
|
|
395
|
+
/**
|
|
396
|
+
* Notify-only: the host has just answered WALLET_LOCKED (4009) — or refused a handshake
|
|
397
|
+
* while locked. The host has ALREADY answered and never waits for this callback;
|
|
398
|
+
* throwing from it must not break the host (the host wraps the call in try/catch).
|
|
399
|
+
*
|
|
400
|
+
* THIS MUST NOT RAISE A CREDENTIAL SURFACE. A dApp request may trigger a CONSENT
|
|
401
|
+
* prompt; it may never trigger a password field. The wallet's only permitted reaction
|
|
402
|
+
* is a PASSIVE badge in its PERMANENT chrome ("N requests waiting — Unlock"); the
|
|
403
|
+
* password field appears only after a human clicks it. Volume is already bounded by
|
|
404
|
+
* checkRateLimit(), which now guards all three entry points — there is no second
|
|
405
|
+
* anti-spam mechanism, no coalescing, no cooldown and no cap by design.
|
|
406
|
+
*
|
|
407
|
+
* Also the natural telemetry seam.
|
|
408
|
+
*/
|
|
409
|
+
onLockedRequest?: (ctx: LockedRequestContext) => void;
|
|
274
410
|
/** Session time-to-live in ms. Default: 86400000 (24h). 0 = no expiry. */
|
|
275
411
|
sessionTtlMs?: number;
|
|
412
|
+
/**
|
|
413
|
+
* The wallet-binding state the host starts in. Default: 'live'.
|
|
414
|
+
* Pass 'locked' when constructing a host while the wallet is already locked (cold start
|
|
415
|
+
* with an encrypted wallet: initialize() takes the classifyInitFailure === 'locked'
|
|
416
|
+
* branch and returns without setting Sphere) — otherwise the host starts 'live' with
|
|
417
|
+
* `sphere: null` and dereferences null on the first request.
|
|
418
|
+
* 'unavailable' is not constructible: use setUnavailable() after construction.
|
|
419
|
+
*/
|
|
420
|
+
initialWalletState?: 'live' | 'locked';
|
|
276
421
|
/** Optional secondary npm-SDK floor (rarely needed — the Connect protocol version is the era gate). */
|
|
277
422
|
minSdkVersion?: string;
|
|
278
423
|
/** Optional MINOR floor within the current Connect MAJOR. */
|
|
279
424
|
minMinorVersion?: number;
|
|
280
425
|
/** Max requests per second per session. Default: 20. */
|
|
281
426
|
maxRequestsPerSecond?: number;
|
|
427
|
+
/** Host-side deadline for a query, in ms. Default: 25000. The host answers within it
|
|
428
|
+
* no matter what the router does. */
|
|
429
|
+
requestDeadlineMs?: number;
|
|
430
|
+
/** Host-side deadline for an intent, in ms. Default: 90000. Fires INTENT_CANCELLED
|
|
431
|
+
* (4200) AND aborts ctx.signal — it must cancel, not merely answer. */
|
|
432
|
+
intentDeadlineMs?: number;
|
|
433
|
+
/** Host-side deadline for onConnectionRequest, in ms. Default: 120000. A handshake
|
|
434
|
+
* carries no id, so expiry sends the empty refusal. */
|
|
435
|
+
handshakeDeadlineMs?: number;
|
|
282
436
|
}
|
|
283
437
|
interface ConnectClientConfig {
|
|
284
438
|
/** Transport layer for communication */
|
|
@@ -305,6 +459,9 @@ interface ConnectResult {
|
|
|
305
459
|
readonly sessionId: string;
|
|
306
460
|
readonly permissions: PermissionScope[];
|
|
307
461
|
readonly identity: PublicIdentity;
|
|
462
|
+
/** True when the wallet is locked but the session is alive — a resume during a lock,
|
|
463
|
+
* the most common entry into this feature. Requests answer 4009 until wallet:unlocked. */
|
|
464
|
+
readonly locked?: boolean;
|
|
308
465
|
}
|
|
309
466
|
type ConnectEventHandler = (data: unknown) => void;
|
|
310
467
|
|
|
@@ -317,7 +474,27 @@ type ConnectEventHandler = (data: unknown) => void;
|
|
|
317
474
|
*/
|
|
318
475
|
|
|
319
476
|
declare class ConnectHost {
|
|
477
|
+
/** Null whenever _walletState is 'locked' or 'unavailable' (invariant B). */
|
|
320
478
|
private sphere;
|
|
479
|
+
/** The wallet-binding axis. Underscored because `walletState` is the public getter.
|
|
480
|
+
* ORTHOGONAL to `session` — a locked wallet keeps its session, a live wallet may have
|
|
481
|
+
* none. Written only by the WALLET (setLocked / setUnavailable / updateSphere / destroy);
|
|
482
|
+
* `session` is written by the dApp handshake, sphere_disconnect and expiry. */
|
|
483
|
+
private _walletState;
|
|
484
|
+
/** Immutable public facts about the current binding. Refreshed on every bind
|
|
485
|
+
* (constructor, updateSphere); FROZEN by setLocked(); EMPTY after setUnavailable() and
|
|
486
|
+
* destroy(). Never read from Sphere while locked — that is a property of the types
|
|
487
|
+
* here, not of code review. */
|
|
488
|
+
private snapshot;
|
|
489
|
+
/** Subscription KEYS captured by setLocked() BEFORE the unsub closures are detached.
|
|
490
|
+
* Sphere.destroy() kills those closures, so the keys are the only recoverable
|
|
491
|
+
* information. Excludes 'identity:changed' (autoSubscribeIdentityChanged re-arms it).
|
|
492
|
+
* A Set, not an array: handleSubscribe may be called twice for the same key while
|
|
493
|
+
* locked. */
|
|
494
|
+
private suspendedSubscriptions;
|
|
495
|
+
/** Every accepted id, with its own host-side timer. The single convergence point for
|
|
496
|
+
* lock / revoke / unavailable / destroy / deadline. */
|
|
497
|
+
private readonly inFlight;
|
|
321
498
|
private readonly transport;
|
|
322
499
|
private readonly config;
|
|
323
500
|
private session;
|
|
@@ -328,6 +505,16 @@ declare class ConnectHost {
|
|
|
328
505
|
private rateLimitResetAt;
|
|
329
506
|
private unsubscribeTransport;
|
|
330
507
|
constructor(config: ConnectHostConfig);
|
|
508
|
+
/** The wallet-binding axis. Orthogonal to {@link getSession}. Read-only —
|
|
509
|
+
* transitions go through setLocked() / setUnavailable() / updateSphere(). */
|
|
510
|
+
get walletState(): WalletState;
|
|
511
|
+
/** Both axes in one read, for UI that must render "connected AND locked".
|
|
512
|
+
* Required by the wallet's ConnectPage, which today renders a green pulsing
|
|
513
|
+
* "Connected to {dapp}" with no regard for lock state. */
|
|
514
|
+
getState(): {
|
|
515
|
+
readonly walletState: WalletState;
|
|
516
|
+
readonly session: ConnectSession | null;
|
|
517
|
+
};
|
|
331
518
|
/** Get current active session */
|
|
332
519
|
getSession(): ConnectSession | null;
|
|
333
520
|
/** Register an auto-approve handler for an intent action (session-scoped). */
|
|
@@ -341,19 +528,64 @@ declare class ConnectHost {
|
|
|
341
528
|
/** Remove auto-approve for an intent action. */
|
|
342
529
|
clearIntentAutoApprove(action: string): void;
|
|
343
530
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
531
|
+
* Bind a (new) Sphere instance. This is BOTH the re-arm path after setLocked() /
|
|
532
|
+
* setUnavailable() AND the existing address-switch path in a live wallet.
|
|
533
|
+
*
|
|
534
|
+
* From 'live' (address switch): today's behaviour, unchanged — re-arm identity:changed,
|
|
535
|
+
* push identity:changed. NO identity comparison: an address switch is legal.
|
|
536
|
+
*
|
|
537
|
+
* On the 'locked' -> 'live' edge, in this order:
|
|
538
|
+
* 1. compare snapshot.identity?.chainPubkey with the new Sphere's chainPubkey.
|
|
539
|
+
* MISMATCH => revokeSession() (which pushes wallet:disconnected) and RETURN.
|
|
540
|
+
* Never wallet:unlocked. This is the "Forgot password -> restore recovery phrase
|
|
541
|
+
* installed a different seed behind an origin-keyed approval" guard.
|
|
542
|
+
* 2. session.expiresAt passed => revokeSession() and RETURN. A wallet:unlocked into a
|
|
543
|
+
* dead session would make the dApp's next request answer SESSION_EXPIRED 4004.
|
|
544
|
+
* 3. rebind, refresh the snapshot, go live, re-arm identity:changed, replay every
|
|
545
|
+
* suspended sphere_subscribe key, and ONLY THEN push wallet:unlocked with the
|
|
546
|
+
* CURRENT identity. Re-arm BEFORE push, so a dApp reacting synchronously cannot
|
|
547
|
+
* race its own event streams.
|
|
548
|
+
*
|
|
549
|
+
* From 'unavailable' -> 'live': rebind + refresh the snapshot, no identity check
|
|
550
|
+
* (nothing was bound to compare against) and no event (the session is already null).
|
|
346
551
|
*/
|
|
347
552
|
updateSphere(newSphere: unknown): void;
|
|
348
|
-
/** Revoke the current session */
|
|
349
|
-
revokeSession(): void;
|
|
350
553
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
554
|
+
* The wallet locked (manual lock, idle auto-lock, cross-tab broadcast, cold start).
|
|
555
|
+
* The session is PRESERVED — a lock is a state, not a teardown. Every request outside
|
|
556
|
+
* the locked allow-list is answered WALLET_LOCKED (4009) until updateSphere().
|
|
557
|
+
*
|
|
558
|
+
* Idempotent: a second call is a no-op and pushes nothing. Required, because
|
|
559
|
+
* SphereProvider.lock(), ConnectPage's `sphere → null` effect and broadcastLock()'s
|
|
560
|
+
* same-tab loopback can all fire it for one user action.
|
|
561
|
+
*
|
|
562
|
+
* ORDERING CONTRACT: call this BEFORE sphere.destroy(). The host drops its Sphere
|
|
563
|
+
* reference here; destroying first leaves in-flight requests reading a dead instance
|
|
564
|
+
* (-32603, or `undefined` returned AS SUCCESS from sphere_getIdentity).
|
|
565
|
+
*/
|
|
566
|
+
setLocked(): void;
|
|
567
|
+
/**
|
|
568
|
+
* The Sphere instance is gone for a NON-LOCK reason (a generic init failure leaves
|
|
569
|
+
* `sphere === null, isLocked === false` in the wallet).
|
|
570
|
+
* This is a DEAD END: unlocking does not cure it, so it revokes the session and pushes
|
|
571
|
+
* wallet:disconnected rather than promising an unlock that cannot help.
|
|
572
|
+
* Subsequent requests answer NOT_CONNECTED (4001); handshakes get the empty refusal
|
|
573
|
+
* WITHOUT dereferencing a null Sphere.
|
|
574
|
+
*
|
|
575
|
+
* Idempotent. Pushes no 'wallet:unavailable' — there is no such event.
|
|
354
576
|
*/
|
|
355
|
-
|
|
356
|
-
/**
|
|
577
|
+
setUnavailable(): void;
|
|
578
|
+
/**
|
|
579
|
+
* Destroy the SESSION (logout, wallet deleted, dApp sphere_disconnect, popup
|
|
580
|
+
* beforeunload, expiry, identity mismatch at unlock). Pushes wallet:disconnected BEFORE
|
|
581
|
+
* tearing down, so the dApp stops believing it is connected instead of finding out at
|
|
582
|
+
* its next 4001.
|
|
583
|
+
*
|
|
584
|
+
* This is the TEARDOWN verb. For a lock use setLocked() — a lock never destroys the
|
|
585
|
+
* session. revokeSession() does NOT touch walletState: the two axes are orthogonal.
|
|
586
|
+
*/
|
|
587
|
+
revokeSession(): void;
|
|
588
|
+
/** Destroy the host, clean up all resources. Idempotent. */
|
|
357
589
|
destroy(): void;
|
|
358
590
|
private handleMessage;
|
|
359
591
|
private handleHandshake;
|
|
@@ -367,6 +599,39 @@ declare class ConnectHost {
|
|
|
367
599
|
private cleanupEventSubscriptions;
|
|
368
600
|
/** Push an event to the dApp without requiring a sphere_subscribe call. */
|
|
369
601
|
private pushClientEvent;
|
|
602
|
+
/** The bound Sphere, or a typed refusal. The ONLY way the router may reach Sphere.
|
|
603
|
+
* Unreachable in practice — the gate guarantees 'live' before the router is entered —
|
|
604
|
+
* so this is defence in depth, not the primary mechanism. */
|
|
605
|
+
private requireSphere;
|
|
606
|
+
/** SNAPSHOT read. `undefined` means "we never saw an identity": callers MUST refuse,
|
|
607
|
+
* never answer undefined-as-success — a dApp reads that as "the wallet has no
|
|
608
|
+
* identity". Two explicit methods instead of one dual-mode method, so nobody can serve
|
|
609
|
+
* a snapshot believing it is live. */
|
|
610
|
+
private snapshotIdentity;
|
|
611
|
+
/** InFlightRegistry.onExpire sink. Filled in a later task; declared here so the
|
|
612
|
+
* constructor can wire it. */
|
|
613
|
+
private settleExpired;
|
|
614
|
+
/** Answer every request already in flight with one coded frame each. A request in flight
|
|
615
|
+
* when the Sphere goes away otherwise answers -32603 with a raw JS message, returns
|
|
616
|
+
* `undefined` AS SUCCESS (sphere_getIdentity), or — for a delegated intent — never
|
|
617
|
+
* answers at all until the client's own 120 s timeout. */
|
|
618
|
+
private settleInFlight;
|
|
619
|
+
/**
|
|
620
|
+
* Notify-only. The host has ALREADY answered and never waits for the wallet.
|
|
621
|
+
*
|
|
622
|
+
* The wallet's only permitted reaction is a PASSIVE badge in its PERMANENT chrome; a
|
|
623
|
+
* dApp request may trigger a CONSENT prompt but never a credential prompt. Volume is
|
|
624
|
+
* bounded by checkRateLimit(), which guards all three entry points — there is no
|
|
625
|
+
* coalescing, no cooldown and no cap by design.
|
|
626
|
+
*
|
|
627
|
+
* A throwing handler must not break the host.
|
|
628
|
+
*/
|
|
629
|
+
private notifyLockedRequest;
|
|
630
|
+
/** Last-resort answer for a handler that threw before its own catch could run.
|
|
631
|
+
* Id-bearing frames get a coded error (InFlightRegistry guarantees exactly one answer
|
|
632
|
+
* per id); a handshake gets today's empty refusal, because a failed handshake must
|
|
633
|
+
* reveal nothing. */
|
|
634
|
+
private sendUnhandledError;
|
|
370
635
|
private getPublicIdentity;
|
|
371
636
|
private stripTokenSdkData;
|
|
372
637
|
private sendResult;
|
|
@@ -406,6 +671,8 @@ declare class ConnectClient {
|
|
|
406
671
|
private grantedPermissions;
|
|
407
672
|
private identity;
|
|
408
673
|
private walletNet;
|
|
674
|
+
private walletProto;
|
|
675
|
+
private locked;
|
|
409
676
|
private connected;
|
|
410
677
|
private pendingRequests;
|
|
411
678
|
private eventHandlers;
|
|
@@ -426,6 +693,27 @@ declare class ConnectClient {
|
|
|
426
693
|
get walletIdentity(): PublicIdentity | null;
|
|
427
694
|
/** Wallet's active network, received during handshake. */
|
|
428
695
|
get walletNetwork(): NetworkInfo | null;
|
|
696
|
+
/**
|
|
697
|
+
* The wallet's Connect protocol version, captured from the handshake response `v`.
|
|
698
|
+
* Null before the first handshake response and after a disconnect.
|
|
699
|
+
*
|
|
700
|
+
* Feature-detect with it: compare against '2.1' to decide whether the wallet can be
|
|
701
|
+
* trusted to send wallet:unlocked / wallet:disconnected. A Connect 2.0 wallet destroys
|
|
702
|
+
* the session on lock and never emits either, so a dApp waiting for them against one
|
|
703
|
+
* waits forever.
|
|
704
|
+
*
|
|
705
|
+
* CAVEAT: on an ERROR response the host echoes the dApp's own `v` back
|
|
706
|
+
* (ConnectHost.sendHandshakeResponse), so after a refused connection this may be the
|
|
707
|
+
* dApp's version rather than the wallet's. Only trust it after a successful handshake.
|
|
708
|
+
*/
|
|
709
|
+
get walletProtocol(): string | null;
|
|
710
|
+
/**
|
|
711
|
+
* Whether the wallet was locked at the last handshake or lifecycle event.
|
|
712
|
+
* A locked client is still CONNECTED: `isConnected` stays true and `session` stays valid.
|
|
713
|
+
* Requests answer WALLET_LOCKED (4009) until `wallet:unlocked` arrives on the SAME
|
|
714
|
+
* session — do not disconnect, do not clear the session, do not re-handshake.
|
|
715
|
+
*/
|
|
716
|
+
get walletLocked(): boolean;
|
|
429
717
|
/** Send a query request and return the result */
|
|
430
718
|
query<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
|
|
431
719
|
/** Send an intent request. The wallet will open its UI for user confirmation. */
|
|
@@ -433,9 +721,10 @@ declare class ConnectClient {
|
|
|
433
721
|
/** Subscribe to a wallet event. Returns unsubscribe function. */
|
|
434
722
|
on(event: string, handler: ConnectEventHandler): () => void;
|
|
435
723
|
private handleMessage;
|
|
724
|
+
private dispatchEvent;
|
|
436
725
|
private handleHandshakeResponse;
|
|
437
726
|
private handlePendingResponse;
|
|
438
727
|
private cleanup;
|
|
439
728
|
}
|
|
440
729
|
|
|
441
|
-
export { ALL_PERMISSIONS, ConnectClient, type ConnectClientConfig, ConnectError, type ConnectEventHandler, ConnectHost, type ConnectHostConfig, type ConnectResult, type ConnectSession, type ConnectTransport, type DAppMetadata, DEFAULT_PERMISSIONS, ERROR_CODES, type ErrorCode, HOST_READY_TIMEOUT, HOST_READY_TYPE, INTENT_ACTIONS, INTENT_PERMISSIONS, type IntentAction, METHOD_PERMISSIONS, type NetworkInfo, PERMISSION_SCOPES, type PermissionScope, type PublicIdentity, RPC_METHODS, type RpcMethod, SPHERE_CONNECT_NAMESPACE, SPHERE_CONNECT_VERSION, SPHERE_NETWORKS, type SphereConnectMessage, type SphereEventMessage, type SphereHandshake, type SphereIntentRequest, type SphereIntentResult, type SphereRpcError, type SphereRpcRequest, type SphereRpcResponse, WALLET_EVENTS, type WalletEvent, createRequestId, hasIntentPermission, hasMethodPermission, isSphereConnectMessage, validatePermissions };
|
|
730
|
+
export { ALL_PERMISSIONS, AUTO_PUSHED_EVENTS, ConnectClient, type ConnectClientConfig, ConnectError, type ConnectEventHandler, ConnectHost, type ConnectHostConfig, type ConnectResult, type ConnectSession, type ConnectTransport, type DAppMetadata, DEFAULT_PERMISSIONS, ERROR_CODES, type ErrorCode, HOST_READY_TIMEOUT, HOST_READY_TYPE, INTENT_ACTIONS, INTENT_PERMISSIONS, type IntentAction, type IntentContext, type LockedRequestContext, METHOD_PERMISSIONS, type NetworkInfo, PERMISSION_SCOPES, type PermissionScope, type PublicIdentity, RPC_METHODS, type RpcMethod, SPHERE_CONNECT_NAMESPACE, SPHERE_CONNECT_VERSION, SPHERE_NETWORKS, type SphereConnectMessage, type SphereEventMessage, type SphereHandshake, type SphereIntentRequest, type SphereIntentResult, type SphereRpcError, type SphereRpcRequest, type SphereRpcResponse, WALLET_EVENTS, type WalletDisconnectedPayload, type WalletEvent, type WalletIdentityChangedPayload, type WalletLockedData, type WalletLockedPayload, type WalletState, type WalletUnlockedPayload, createRequestId, hasIntentPermission, hasMethodPermission, isAutoPushedEvent, isSphereConnectMessage, validatePermissions };
|