@unicitylabs/sphere-sdk 0.3.8 → 0.4.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.
Files changed (49) hide show
  1. package/dist/connect/index.cjs +770 -0
  2. package/dist/connect/index.cjs.map +1 -0
  3. package/dist/connect/index.d.cts +312 -0
  4. package/dist/connect/index.d.ts +312 -0
  5. package/dist/connect/index.js +747 -0
  6. package/dist/connect/index.js.map +1 -0
  7. package/dist/core/index.cjs +2744 -56
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +277 -3
  10. package/dist/core/index.d.ts +277 -3
  11. package/dist/core/index.js +2740 -52
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +271 -0
  14. package/dist/impl/browser/connect/index.cjs.map +1 -0
  15. package/dist/impl/browser/connect/index.d.cts +137 -0
  16. package/dist/impl/browser/connect/index.d.ts +137 -0
  17. package/dist/impl/browser/connect/index.js +248 -0
  18. package/dist/impl/browser/connect/index.js.map +1 -0
  19. package/dist/impl/browser/index.cjs +583 -45
  20. package/dist/impl/browser/index.cjs.map +1 -1
  21. package/dist/impl/browser/index.js +587 -46
  22. package/dist/impl/browser/index.js.map +1 -1
  23. package/dist/impl/browser/ipfs.cjs.map +1 -1
  24. package/dist/impl/browser/ipfs.js.map +1 -1
  25. package/dist/impl/nodejs/connect/index.cjs +372 -0
  26. package/dist/impl/nodejs/connect/index.cjs.map +1 -0
  27. package/dist/impl/nodejs/connect/index.d.cts +178 -0
  28. package/dist/impl/nodejs/connect/index.d.ts +178 -0
  29. package/dist/impl/nodejs/connect/index.js +333 -0
  30. package/dist/impl/nodejs/connect/index.js.map +1 -0
  31. package/dist/impl/nodejs/index.cjs +266 -12
  32. package/dist/impl/nodejs/index.cjs.map +1 -1
  33. package/dist/impl/nodejs/index.d.cts +96 -0
  34. package/dist/impl/nodejs/index.d.ts +96 -0
  35. package/dist/impl/nodejs/index.js +270 -13
  36. package/dist/impl/nodejs/index.js.map +1 -1
  37. package/dist/index.cjs +2761 -56
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +375 -5
  40. package/dist/index.d.ts +375 -5
  41. package/dist/index.js +2750 -52
  42. package/dist/index.js.map +1 -1
  43. package/dist/l1/index.cjs +5 -1
  44. package/dist/l1/index.cjs.map +1 -1
  45. package/dist/l1/index.d.cts +2 -1
  46. package/dist/l1/index.d.ts +2 -1
  47. package/dist/l1/index.js +5 -1
  48. package/dist/l1/index.js.map +1 -1
  49. package/package.json +31 -1
@@ -0,0 +1,770 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // connect/index.ts
21
+ var connect_exports = {};
22
+ __export(connect_exports, {
23
+ ALL_PERMISSIONS: () => ALL_PERMISSIONS,
24
+ ConnectClient: () => ConnectClient,
25
+ ConnectHost: () => ConnectHost,
26
+ DEFAULT_PERMISSIONS: () => DEFAULT_PERMISSIONS,
27
+ ERROR_CODES: () => ERROR_CODES,
28
+ HOST_READY_TIMEOUT: () => HOST_READY_TIMEOUT,
29
+ HOST_READY_TYPE: () => HOST_READY_TYPE,
30
+ INTENT_ACTIONS: () => INTENT_ACTIONS,
31
+ INTENT_PERMISSIONS: () => INTENT_PERMISSIONS,
32
+ METHOD_PERMISSIONS: () => METHOD_PERMISSIONS,
33
+ PERMISSION_SCOPES: () => PERMISSION_SCOPES,
34
+ RPC_METHODS: () => RPC_METHODS,
35
+ SPHERE_CONNECT_NAMESPACE: () => SPHERE_CONNECT_NAMESPACE,
36
+ SPHERE_CONNECT_VERSION: () => SPHERE_CONNECT_VERSION,
37
+ createRequestId: () => createRequestId,
38
+ hasIntentPermission: () => hasIntentPermission,
39
+ hasMethodPermission: () => hasMethodPermission,
40
+ isSphereConnectMessage: () => isSphereConnectMessage,
41
+ validatePermissions: () => validatePermissions
42
+ });
43
+ module.exports = __toCommonJS(connect_exports);
44
+
45
+ // constants.ts
46
+ var STORAGE_KEYS_GLOBAL = {
47
+ /** Encrypted BIP39 mnemonic */
48
+ MNEMONIC: "mnemonic",
49
+ /** Encrypted master private key */
50
+ MASTER_KEY: "master_key",
51
+ /** BIP32 chain code */
52
+ CHAIN_CODE: "chain_code",
53
+ /** HD derivation path (full path like m/44'/0'/0'/0/0) */
54
+ DERIVATION_PATH: "derivation_path",
55
+ /** Base derivation path (like m/44'/0'/0' without chain/index) */
56
+ BASE_PATH: "base_path",
57
+ /** Derivation mode: bip32, wif_hmac, legacy_hmac */
58
+ DERIVATION_MODE: "derivation_mode",
59
+ /** Wallet source: mnemonic, file, unknown */
60
+ WALLET_SOURCE: "wallet_source",
61
+ /** Wallet existence flag */
62
+ WALLET_EXISTS: "wallet_exists",
63
+ /** Current active address index */
64
+ CURRENT_ADDRESS_INDEX: "current_address_index",
65
+ /** Nametag cache per address (separate from tracked addresses registry) */
66
+ ADDRESS_NAMETAGS: "address_nametags",
67
+ /** Active addresses registry (JSON: TrackedAddressesStorage) */
68
+ TRACKED_ADDRESSES: "tracked_addresses",
69
+ /** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
70
+ LAST_WALLET_EVENT_TS: "last_wallet_event_ts",
71
+ /** Group chat: joined groups */
72
+ GROUP_CHAT_GROUPS: "group_chat_groups",
73
+ /** Group chat: messages */
74
+ GROUP_CHAT_MESSAGES: "group_chat_messages",
75
+ /** Group chat: members */
76
+ GROUP_CHAT_MEMBERS: "group_chat_members",
77
+ /** Group chat: processed event IDs for deduplication */
78
+ GROUP_CHAT_PROCESSED_EVENTS: "group_chat_processed_events",
79
+ /** Group chat: last used relay URL (stale data detection) */
80
+ GROUP_CHAT_RELAY_URL: "group_chat_relay_url",
81
+ /** Cached token registry JSON (fetched from remote) */
82
+ TOKEN_REGISTRY_CACHE: "token_registry_cache",
83
+ /** Timestamp of last token registry cache update (ms since epoch) */
84
+ TOKEN_REGISTRY_CACHE_TS: "token_registry_cache_ts",
85
+ /** Cached price data JSON (from CoinGecko or other provider) */
86
+ PRICE_CACHE: "price_cache",
87
+ /** Timestamp of last price cache update (ms since epoch) */
88
+ PRICE_CACHE_TS: "price_cache_ts"
89
+ };
90
+ var STORAGE_KEYS_ADDRESS = {
91
+ /** Pending transfers for this address */
92
+ PENDING_TRANSFERS: "pending_transfers",
93
+ /** Transfer outbox for this address */
94
+ OUTBOX: "outbox",
95
+ /** Conversations for this address */
96
+ CONVERSATIONS: "conversations",
97
+ /** Messages for this address */
98
+ MESSAGES: "messages",
99
+ /** Transaction history for this address */
100
+ TRANSACTION_HISTORY: "transaction_history",
101
+ /** Pending V5 finalization tokens (unconfirmed instant split tokens) */
102
+ PENDING_V5_TOKENS: "pending_v5_tokens"
103
+ };
104
+ var STORAGE_KEYS = {
105
+ ...STORAGE_KEYS_GLOBAL,
106
+ ...STORAGE_KEYS_ADDRESS
107
+ };
108
+ var DEFAULT_BASE_PATH = "m/44'/0'/0'";
109
+ var DEFAULT_DERIVATION_PATH = `${DEFAULT_BASE_PATH}/0/0`;
110
+ var HOST_READY_TYPE = "sphere-connect:host-ready";
111
+ var HOST_READY_TIMEOUT = 3e4;
112
+
113
+ // connect/protocol.ts
114
+ var SPHERE_CONNECT_NAMESPACE = "sphere-connect";
115
+ var SPHERE_CONNECT_VERSION = "1.0";
116
+ var RPC_METHODS = {
117
+ GET_IDENTITY: "sphere_getIdentity",
118
+ GET_BALANCE: "sphere_getBalance",
119
+ GET_ASSETS: "sphere_getAssets",
120
+ GET_FIAT_BALANCE: "sphere_getFiatBalance",
121
+ GET_TOKENS: "sphere_getTokens",
122
+ GET_HISTORY: "sphere_getHistory",
123
+ L1_GET_BALANCE: "sphere_l1GetBalance",
124
+ L1_GET_HISTORY: "sphere_l1GetHistory",
125
+ RESOLVE: "sphere_resolve",
126
+ SUBSCRIBE: "sphere_subscribe",
127
+ UNSUBSCRIBE: "sphere_unsubscribe",
128
+ DISCONNECT: "sphere_disconnect"
129
+ };
130
+ var INTENT_ACTIONS = {
131
+ SEND: "send",
132
+ L1_SEND: "l1_send",
133
+ DM: "dm",
134
+ PAYMENT_REQUEST: "payment_request",
135
+ RECEIVE: "receive",
136
+ SIGN_MESSAGE: "sign_message"
137
+ };
138
+ var ERROR_CODES = {
139
+ // Standard JSON-RPC
140
+ PARSE_ERROR: -32700,
141
+ INVALID_REQUEST: -32600,
142
+ METHOD_NOT_FOUND: -32601,
143
+ INVALID_PARAMS: -32602,
144
+ INTERNAL_ERROR: -32603,
145
+ // Sphere Connect (4xxx)
146
+ NOT_CONNECTED: 4001,
147
+ PERMISSION_DENIED: 4002,
148
+ USER_REJECTED: 4003,
149
+ SESSION_EXPIRED: 4004,
150
+ ORIGIN_BLOCKED: 4005,
151
+ RATE_LIMITED: 4006,
152
+ INSUFFICIENT_BALANCE: 4100,
153
+ INVALID_RECIPIENT: 4101,
154
+ TRANSFER_FAILED: 4102,
155
+ INTENT_CANCELLED: 4200
156
+ };
157
+ function isSphereConnectMessage(msg) {
158
+ if (!msg || typeof msg !== "object") return false;
159
+ const m = msg;
160
+ return m.ns === SPHERE_CONNECT_NAMESPACE && m.v === SPHERE_CONNECT_VERSION;
161
+ }
162
+ function createRequestId() {
163
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
164
+ return crypto.randomUUID();
165
+ }
166
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
167
+ }
168
+
169
+ // connect/permissions.ts
170
+ var PERMISSION_SCOPES = {
171
+ IDENTITY_READ: "identity:read",
172
+ BALANCE_READ: "balance:read",
173
+ TOKENS_READ: "tokens:read",
174
+ HISTORY_READ: "history:read",
175
+ L1_READ: "l1:read",
176
+ EVENTS_SUBSCRIBE: "events:subscribe",
177
+ RESOLVE_PEER: "resolve:peer",
178
+ TRANSFER_REQUEST: "transfer:request",
179
+ L1_TRANSFER: "l1:transfer",
180
+ DM_REQUEST: "dm:request",
181
+ PAYMENT_REQUEST: "payment:request",
182
+ SIGN_REQUEST: "sign:request"
183
+ };
184
+ var ALL_PERMISSIONS = Object.values(PERMISSION_SCOPES);
185
+ var DEFAULT_PERMISSIONS = [
186
+ PERMISSION_SCOPES.IDENTITY_READ
187
+ ];
188
+ var METHOD_PERMISSIONS = {
189
+ [RPC_METHODS.GET_IDENTITY]: PERMISSION_SCOPES.IDENTITY_READ,
190
+ [RPC_METHODS.GET_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,
191
+ [RPC_METHODS.GET_ASSETS]: PERMISSION_SCOPES.BALANCE_READ,
192
+ [RPC_METHODS.GET_FIAT_BALANCE]: PERMISSION_SCOPES.BALANCE_READ,
193
+ [RPC_METHODS.GET_TOKENS]: PERMISSION_SCOPES.TOKENS_READ,
194
+ [RPC_METHODS.GET_HISTORY]: PERMISSION_SCOPES.HISTORY_READ,
195
+ [RPC_METHODS.L1_GET_BALANCE]: PERMISSION_SCOPES.L1_READ,
196
+ [RPC_METHODS.L1_GET_HISTORY]: PERMISSION_SCOPES.L1_READ,
197
+ [RPC_METHODS.RESOLVE]: PERMISSION_SCOPES.RESOLVE_PEER,
198
+ [RPC_METHODS.SUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE,
199
+ [RPC_METHODS.UNSUBSCRIBE]: PERMISSION_SCOPES.EVENTS_SUBSCRIBE
200
+ };
201
+ var INTENT_PERMISSIONS = {
202
+ [INTENT_ACTIONS.SEND]: PERMISSION_SCOPES.TRANSFER_REQUEST,
203
+ [INTENT_ACTIONS.L1_SEND]: PERMISSION_SCOPES.L1_TRANSFER,
204
+ [INTENT_ACTIONS.DM]: PERMISSION_SCOPES.DM_REQUEST,
205
+ [INTENT_ACTIONS.PAYMENT_REQUEST]: PERMISSION_SCOPES.PAYMENT_REQUEST,
206
+ [INTENT_ACTIONS.RECEIVE]: PERMISSION_SCOPES.IDENTITY_READ,
207
+ [INTENT_ACTIONS.SIGN_MESSAGE]: PERMISSION_SCOPES.SIGN_REQUEST
208
+ };
209
+ function hasMethodPermission(granted, method) {
210
+ const required = METHOD_PERMISSIONS[method];
211
+ if (!required) return false;
212
+ return granted.has(required);
213
+ }
214
+ function hasIntentPermission(granted, action) {
215
+ const required = INTENT_PERMISSIONS[action];
216
+ if (!required) return false;
217
+ return granted.has(required);
218
+ }
219
+ function validatePermissions(permissions) {
220
+ const validScopes = new Set(ALL_PERMISSIONS);
221
+ return permissions.every((p) => validScopes.has(p));
222
+ }
223
+
224
+ // connect/host/ConnectHost.ts
225
+ var DEFAULT_SESSION_TTL_MS = 864e5;
226
+ var DEFAULT_MAX_RPS = 20;
227
+ var ConnectHost = class {
228
+ sphere;
229
+ transport;
230
+ config;
231
+ session = null;
232
+ grantedPermissions = /* @__PURE__ */ new Set();
233
+ // Event subscription management
234
+ eventSubscriptions = /* @__PURE__ */ new Map();
235
+ // eventName → unsub
236
+ // Rate limiting
237
+ rateLimitCounter = 0;
238
+ rateLimitResetAt = 0;
239
+ unsubscribeTransport = null;
240
+ constructor(config) {
241
+ this.sphere = config.sphere;
242
+ this.transport = config.transport;
243
+ this.config = config;
244
+ this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));
245
+ }
246
+ /** Get current active session */
247
+ getSession() {
248
+ return this.session;
249
+ }
250
+ /** Revoke the current session */
251
+ revokeSession() {
252
+ if (this.session) {
253
+ this.session.active = false;
254
+ this.cleanupEventSubscriptions();
255
+ this.session = null;
256
+ this.grantedPermissions.clear();
257
+ }
258
+ }
259
+ /** Destroy the host, clean up all resources */
260
+ destroy() {
261
+ this.revokeSession();
262
+ if (this.unsubscribeTransport) {
263
+ this.unsubscribeTransport();
264
+ this.unsubscribeTransport = null;
265
+ }
266
+ }
267
+ // ===========================================================================
268
+ // Message Handling
269
+ // ===========================================================================
270
+ async handleMessage(msg) {
271
+ try {
272
+ if (msg.type === "handshake" && msg.direction === "request") {
273
+ await this.handleHandshake(msg);
274
+ return;
275
+ }
276
+ if (msg.type === "request") {
277
+ await this.handleRpcRequest(msg);
278
+ return;
279
+ }
280
+ if (msg.type === "intent") {
281
+ await this.handleIntentRequest(msg);
282
+ return;
283
+ }
284
+ } catch (error) {
285
+ console.warn("[ConnectHost] Error handling message:", error);
286
+ }
287
+ }
288
+ // ===========================================================================
289
+ // Handshake
290
+ // ===========================================================================
291
+ async handleHandshake(msg) {
292
+ const dapp = msg.dapp;
293
+ if (!dapp) {
294
+ this.sendHandshakeResponse([], void 0, void 0);
295
+ return;
296
+ }
297
+ const requestedPermissions = msg.permissions;
298
+ const { approved, grantedPermissions } = await this.config.onConnectionRequest(
299
+ dapp,
300
+ requestedPermissions
301
+ );
302
+ if (!approved) {
303
+ this.sendHandshakeResponse([], void 0, void 0);
304
+ return;
305
+ }
306
+ const sessionId = createRequestId();
307
+ const allPermissions = [.../* @__PURE__ */ new Set([...DEFAULT_PERMISSIONS, ...grantedPermissions])];
308
+ const ttl = this.config.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS;
309
+ this.session = {
310
+ id: sessionId,
311
+ dapp,
312
+ permissions: allPermissions,
313
+ createdAt: Date.now(),
314
+ expiresAt: ttl > 0 ? Date.now() + ttl : 0,
315
+ active: true
316
+ };
317
+ this.grantedPermissions = new Set(allPermissions);
318
+ const identity = this.getPublicIdentity();
319
+ this.sendHandshakeResponse(allPermissions, sessionId, identity);
320
+ }
321
+ sendHandshakeResponse(permissions, sessionId, identity) {
322
+ this.transport.send({
323
+ ns: SPHERE_CONNECT_NAMESPACE,
324
+ v: SPHERE_CONNECT_VERSION,
325
+ type: "handshake",
326
+ direction: "response",
327
+ permissions,
328
+ sessionId,
329
+ identity
330
+ });
331
+ }
332
+ // ===========================================================================
333
+ // RPC Requests (query)
334
+ // ===========================================================================
335
+ async handleRpcRequest(msg) {
336
+ if (!this.session?.active) {
337
+ this.sendError(msg.id, ERROR_CODES.NOT_CONNECTED, "Not connected");
338
+ return;
339
+ }
340
+ if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {
341
+ this.revokeSession();
342
+ this.sendError(msg.id, ERROR_CODES.SESSION_EXPIRED, "Session expired");
343
+ return;
344
+ }
345
+ if (!this.checkRateLimit()) {
346
+ this.sendError(msg.id, ERROR_CODES.RATE_LIMITED, "Too many requests");
347
+ return;
348
+ }
349
+ if (msg.method === RPC_METHODS.DISCONNECT) {
350
+ this.revokeSession();
351
+ this.sendResult(msg.id, { disconnected: true });
352
+ return;
353
+ }
354
+ if (!hasMethodPermission(this.grantedPermissions, msg.method)) {
355
+ this.sendError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for ${msg.method}`);
356
+ return;
357
+ }
358
+ try {
359
+ const result = await this.executeMethod(msg.method, msg.params ?? {});
360
+ this.sendResult(msg.id, result);
361
+ } catch (error) {
362
+ this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, error.message);
363
+ }
364
+ }
365
+ // ===========================================================================
366
+ // Intent Requests
367
+ // ===========================================================================
368
+ async handleIntentRequest(msg) {
369
+ if (!this.session?.active) {
370
+ this.sendIntentError(msg.id, ERROR_CODES.NOT_CONNECTED, "Not connected");
371
+ return;
372
+ }
373
+ if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {
374
+ this.revokeSession();
375
+ this.sendIntentError(msg.id, ERROR_CODES.SESSION_EXPIRED, "Session expired");
376
+ return;
377
+ }
378
+ if (!hasIntentPermission(this.grantedPermissions, msg.action)) {
379
+ this.sendIntentError(msg.id, ERROR_CODES.PERMISSION_DENIED, `Permission denied for intent: ${msg.action}`);
380
+ return;
381
+ }
382
+ const response = await this.config.onIntent(msg.action, msg.params, this.session);
383
+ if (response.error) {
384
+ this.sendIntentError(msg.id, response.error.code, response.error.message);
385
+ } else {
386
+ this.sendIntentResult(msg.id, response.result);
387
+ }
388
+ }
389
+ // ===========================================================================
390
+ // Method Router
391
+ // ===========================================================================
392
+ async executeMethod(method, params) {
393
+ switch (method) {
394
+ case RPC_METHODS.GET_IDENTITY:
395
+ return this.getPublicIdentity();
396
+ case RPC_METHODS.GET_BALANCE:
397
+ return this.sphere.payments.getBalance(params.coinId);
398
+ case RPC_METHODS.GET_ASSETS:
399
+ return this.sphere.payments.getAssets(params.coinId);
400
+ case RPC_METHODS.GET_FIAT_BALANCE:
401
+ return { fiatBalance: await this.sphere.payments.getFiatBalance() };
402
+ case RPC_METHODS.GET_TOKENS:
403
+ return this.stripTokenSdkData(
404
+ this.sphere.payments.getTokens(
405
+ params.coinId ? { coinId: params.coinId } : void 0
406
+ )
407
+ );
408
+ case RPC_METHODS.GET_HISTORY:
409
+ return this.sphere.payments.getHistory();
410
+ case RPC_METHODS.L1_GET_BALANCE:
411
+ if (!this.sphere.payments.l1) {
412
+ throw new Error("L1 module not available");
413
+ }
414
+ return this.sphere.payments.l1.getBalance();
415
+ case RPC_METHODS.L1_GET_HISTORY:
416
+ if (!this.sphere.payments.l1) {
417
+ throw new Error("L1 module not available");
418
+ }
419
+ return this.sphere.payments.l1.getHistory(params.limit);
420
+ case RPC_METHODS.RESOLVE:
421
+ if (!params.identifier) {
422
+ throw new Error("Missing required parameter: identifier");
423
+ }
424
+ return this.sphere.resolve(params.identifier);
425
+ case RPC_METHODS.SUBSCRIBE:
426
+ return this.handleSubscribe(params.event);
427
+ case RPC_METHODS.UNSUBSCRIBE:
428
+ return this.handleUnsubscribe(params.event);
429
+ default:
430
+ throw new Error(`Unknown method: ${method}`);
431
+ }
432
+ }
433
+ // ===========================================================================
434
+ // Event Subscriptions
435
+ // ===========================================================================
436
+ handleSubscribe(eventName) {
437
+ if (!eventName) throw new Error("Missing required parameter: event");
438
+ if (this.eventSubscriptions.has(eventName)) {
439
+ return { subscribed: true, event: eventName };
440
+ }
441
+ const unsub = this.sphere.on(eventName, (data) => {
442
+ this.transport.send({
443
+ ns: SPHERE_CONNECT_NAMESPACE,
444
+ v: SPHERE_CONNECT_VERSION,
445
+ type: "event",
446
+ event: eventName,
447
+ data
448
+ });
449
+ });
450
+ this.eventSubscriptions.set(eventName, unsub);
451
+ return { subscribed: true, event: eventName };
452
+ }
453
+ handleUnsubscribe(eventName) {
454
+ if (!eventName) throw new Error("Missing required parameter: event");
455
+ const unsub = this.eventSubscriptions.get(eventName);
456
+ if (unsub) {
457
+ unsub();
458
+ this.eventSubscriptions.delete(eventName);
459
+ }
460
+ return { unsubscribed: true, event: eventName };
461
+ }
462
+ cleanupEventSubscriptions() {
463
+ for (const [, unsub] of this.eventSubscriptions) {
464
+ unsub();
465
+ }
466
+ this.eventSubscriptions.clear();
467
+ }
468
+ // ===========================================================================
469
+ // Helpers
470
+ // ===========================================================================
471
+ getPublicIdentity() {
472
+ const id = this.sphere.identity;
473
+ if (!id) return void 0;
474
+ return {
475
+ chainPubkey: id.chainPubkey,
476
+ l1Address: id.l1Address,
477
+ directAddress: id.directAddress,
478
+ nametag: id.nametag
479
+ };
480
+ }
481
+ stripTokenSdkData(tokens) {
482
+ return tokens.map((t) => {
483
+ const token = t;
484
+ const { sdkData: _sdkData, ...publicFields } = token;
485
+ return publicFields;
486
+ });
487
+ }
488
+ sendResult(id, result) {
489
+ this.transport.send({
490
+ ns: SPHERE_CONNECT_NAMESPACE,
491
+ v: SPHERE_CONNECT_VERSION,
492
+ type: "response",
493
+ id,
494
+ result
495
+ });
496
+ }
497
+ sendError(id, code, message) {
498
+ this.transport.send({
499
+ ns: SPHERE_CONNECT_NAMESPACE,
500
+ v: SPHERE_CONNECT_VERSION,
501
+ type: "response",
502
+ id,
503
+ error: { code, message }
504
+ });
505
+ }
506
+ sendIntentResult(id, result) {
507
+ this.transport.send({
508
+ ns: SPHERE_CONNECT_NAMESPACE,
509
+ v: SPHERE_CONNECT_VERSION,
510
+ type: "intent_result",
511
+ id,
512
+ result
513
+ });
514
+ }
515
+ sendIntentError(id, code, message) {
516
+ this.transport.send({
517
+ ns: SPHERE_CONNECT_NAMESPACE,
518
+ v: SPHERE_CONNECT_VERSION,
519
+ type: "intent_result",
520
+ id,
521
+ error: { code, message }
522
+ });
523
+ }
524
+ checkRateLimit() {
525
+ const maxRps = this.config.maxRequestsPerSecond ?? DEFAULT_MAX_RPS;
526
+ const now = Date.now();
527
+ if (now > this.rateLimitResetAt) {
528
+ this.rateLimitCounter = 0;
529
+ this.rateLimitResetAt = now + 1e3;
530
+ }
531
+ this.rateLimitCounter++;
532
+ return this.rateLimitCounter <= maxRps;
533
+ }
534
+ };
535
+
536
+ // connect/client/ConnectClient.ts
537
+ var DEFAULT_TIMEOUT = 3e4;
538
+ var DEFAULT_INTENT_TIMEOUT = 12e4;
539
+ var ConnectClient = class {
540
+ transport;
541
+ dapp;
542
+ requestedPermissions;
543
+ timeout;
544
+ intentTimeout;
545
+ sessionId = null;
546
+ grantedPermissions = [];
547
+ identity = null;
548
+ connected = false;
549
+ pendingRequests = /* @__PURE__ */ new Map();
550
+ eventHandlers = /* @__PURE__ */ new Map();
551
+ unsubscribeTransport = null;
552
+ // Handshake resolver (one-shot)
553
+ handshakeResolver = null;
554
+ constructor(config) {
555
+ this.transport = config.transport;
556
+ this.dapp = config.dapp;
557
+ this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];
558
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
559
+ this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;
560
+ }
561
+ // ===========================================================================
562
+ // Connection
563
+ // ===========================================================================
564
+ /** Connect to the wallet. Returns session info and public identity. */
565
+ async connect() {
566
+ this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));
567
+ return new Promise((resolve, reject) => {
568
+ const timer = setTimeout(() => {
569
+ this.handshakeResolver = null;
570
+ reject(new Error("Connection timeout"));
571
+ }, this.timeout);
572
+ this.handshakeResolver = { resolve, reject, timer };
573
+ this.transport.send({
574
+ ns: SPHERE_CONNECT_NAMESPACE,
575
+ v: SPHERE_CONNECT_VERSION,
576
+ type: "handshake",
577
+ direction: "request",
578
+ permissions: this.requestedPermissions,
579
+ dapp: this.dapp
580
+ });
581
+ });
582
+ }
583
+ /** Disconnect from the wallet */
584
+ async disconnect() {
585
+ if (this.connected) {
586
+ try {
587
+ await this.query(RPC_METHODS.DISCONNECT);
588
+ } catch {
589
+ }
590
+ }
591
+ this.cleanup();
592
+ }
593
+ /** Whether currently connected */
594
+ get isConnected() {
595
+ return this.connected;
596
+ }
597
+ /** Granted permission scopes */
598
+ get permissions() {
599
+ return this.grantedPermissions;
600
+ }
601
+ /** Current session ID */
602
+ get session() {
603
+ return this.sessionId;
604
+ }
605
+ /** Public identity received during handshake */
606
+ get walletIdentity() {
607
+ return this.identity;
608
+ }
609
+ // ===========================================================================
610
+ // Query (read data)
611
+ // ===========================================================================
612
+ /** Send a query request and return the result */
613
+ async query(method, params) {
614
+ if (!this.connected) throw new Error("Not connected");
615
+ const id = createRequestId();
616
+ return new Promise((resolve, reject) => {
617
+ const timer = setTimeout(() => {
618
+ this.pendingRequests.delete(id);
619
+ reject(new Error(`Query timeout: ${method}`));
620
+ }, this.timeout);
621
+ this.pendingRequests.set(id, {
622
+ resolve,
623
+ reject,
624
+ timer
625
+ });
626
+ this.transport.send({
627
+ ns: SPHERE_CONNECT_NAMESPACE,
628
+ v: SPHERE_CONNECT_VERSION,
629
+ type: "request",
630
+ id,
631
+ method,
632
+ params
633
+ });
634
+ });
635
+ }
636
+ // ===========================================================================
637
+ // Intent (trigger wallet UI)
638
+ // ===========================================================================
639
+ /** Send an intent request. The wallet will open its UI for user confirmation. */
640
+ async intent(action, params) {
641
+ if (!this.connected) throw new Error("Not connected");
642
+ const id = createRequestId();
643
+ return new Promise((resolve, reject) => {
644
+ const timer = setTimeout(() => {
645
+ this.pendingRequests.delete(id);
646
+ reject(new Error(`Intent timeout: ${action}`));
647
+ }, this.intentTimeout);
648
+ this.pendingRequests.set(id, {
649
+ resolve,
650
+ reject,
651
+ timer
652
+ });
653
+ this.transport.send({
654
+ ns: SPHERE_CONNECT_NAMESPACE,
655
+ v: SPHERE_CONNECT_VERSION,
656
+ type: "intent",
657
+ id,
658
+ action,
659
+ params
660
+ });
661
+ });
662
+ }
663
+ // ===========================================================================
664
+ // Events
665
+ // ===========================================================================
666
+ /** Subscribe to a wallet event. Returns unsubscribe function. */
667
+ on(event, handler) {
668
+ if (!this.eventHandlers.has(event)) {
669
+ this.eventHandlers.set(event, /* @__PURE__ */ new Set());
670
+ if (this.connected) {
671
+ this.query(RPC_METHODS.SUBSCRIBE, { event }).catch(() => {
672
+ });
673
+ }
674
+ }
675
+ this.eventHandlers.get(event).add(handler);
676
+ return () => {
677
+ const handlers = this.eventHandlers.get(event);
678
+ if (handlers) {
679
+ handlers.delete(handler);
680
+ if (handlers.size === 0) {
681
+ this.eventHandlers.delete(event);
682
+ if (this.connected) {
683
+ this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch(() => {
684
+ });
685
+ }
686
+ }
687
+ }
688
+ };
689
+ }
690
+ // ===========================================================================
691
+ // Message Handling
692
+ // ===========================================================================
693
+ handleMessage(msg) {
694
+ if (msg.type === "handshake" && msg.direction === "response") {
695
+ this.handleHandshakeResponse(msg);
696
+ return;
697
+ }
698
+ if (msg.type === "response") {
699
+ this.handlePendingResponse(msg.id, msg.result, msg.error);
700
+ return;
701
+ }
702
+ if (msg.type === "intent_result") {
703
+ this.handlePendingResponse(msg.id, msg.result, msg.error);
704
+ return;
705
+ }
706
+ if (msg.type === "event") {
707
+ const handlers = this.eventHandlers.get(msg.event);
708
+ if (handlers) {
709
+ for (const handler of handlers) {
710
+ try {
711
+ handler(msg.data);
712
+ } catch {
713
+ }
714
+ }
715
+ }
716
+ }
717
+ }
718
+ handleHandshakeResponse(msg) {
719
+ if (!this.handshakeResolver) return;
720
+ clearTimeout(this.handshakeResolver.timer);
721
+ if (msg.sessionId && msg.identity) {
722
+ this.sessionId = msg.sessionId;
723
+ this.grantedPermissions = msg.permissions;
724
+ this.identity = msg.identity;
725
+ this.connected = true;
726
+ this.handshakeResolver.resolve({
727
+ sessionId: msg.sessionId,
728
+ permissions: this.grantedPermissions,
729
+ identity: msg.identity
730
+ });
731
+ } else {
732
+ this.handshakeResolver.reject(new Error("Connection rejected by wallet"));
733
+ }
734
+ this.handshakeResolver = null;
735
+ }
736
+ handlePendingResponse(id, result, error) {
737
+ const pending = this.pendingRequests.get(id);
738
+ if (!pending) return;
739
+ clearTimeout(pending.timer);
740
+ this.pendingRequests.delete(id);
741
+ if (error) {
742
+ const err = new Error(error.message);
743
+ err.code = error.code;
744
+ err.data = error.data;
745
+ pending.reject(err);
746
+ } else {
747
+ pending.resolve(result);
748
+ }
749
+ }
750
+ // ===========================================================================
751
+ // Cleanup
752
+ // ===========================================================================
753
+ cleanup() {
754
+ if (this.unsubscribeTransport) {
755
+ this.unsubscribeTransport();
756
+ this.unsubscribeTransport = null;
757
+ }
758
+ for (const [, pending] of this.pendingRequests) {
759
+ clearTimeout(pending.timer);
760
+ pending.reject(new Error("Disconnected"));
761
+ }
762
+ this.pendingRequests.clear();
763
+ this.eventHandlers.clear();
764
+ this.connected = false;
765
+ this.sessionId = null;
766
+ this.grantedPermissions = [];
767
+ this.identity = null;
768
+ }
769
+ };
770
+ //# sourceMappingURL=index.cjs.map