@unicitylabs/sphere-sdk 0.5.3 → 0.5.4

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/README.md CHANGED
@@ -16,6 +16,7 @@ A modular TypeScript SDK for Unicity wallet operations supporting both Layer 1 (
16
16
  - **TXF Serialization** - Token eXchange Format for storage and transfer
17
17
  - **Token Validation** - Aggregator-based token verification
18
18
  - **Core Utilities** - Crypto, currency, bech32, base58 functions
19
+ - **Connect Protocol** - dApp ↔ wallet communication via `ConnectClient` / `ConnectHost` (browser extension + popup)
19
20
 
20
21
  ## Installation
21
22
 
@@ -32,6 +33,7 @@ Choose your platform:
32
33
  | **Browser** | [QUICKSTART-BROWSER.md](docs/QUICKSTART-BROWSER.md) | SDK only | IPFS sync (built-in) |
33
34
  | **Node.js** | [QUICKSTART-NODEJS.md](docs/QUICKSTART-NODEJS.md) | SDK + `ws` | IPFS sync (built-in) |
34
35
  | **CLI** | See below | SDK + `tsx` | - |
36
+ | **dApp integration** | [CONNECT.md](docs/CONNECT.md) | SDK only | Sphere extension |
35
37
 
36
38
  ## CLI (Command Line Interface)
37
39
 
@@ -318,10 +318,16 @@ var ConnectHost = class {
318
318
  this.sendHandshakeResponse([], void 0, void 0);
319
319
  return;
320
320
  }
321
+ if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {
322
+ const identity2 = this.getPublicIdentity();
323
+ this.sendHandshakeResponse([...this.grantedPermissions], this.session.id, identity2);
324
+ return;
325
+ }
321
326
  const requestedPermissions = msg.permissions;
322
327
  const { approved, grantedPermissions } = await this.config.onConnectionRequest(
323
328
  dapp,
324
- requestedPermissions
329
+ requestedPermissions,
330
+ msg.silent
325
331
  );
326
332
  if (!approved) {
327
333
  this.sendHandshakeResponse([], void 0, void 0);
@@ -371,8 +377,12 @@ var ConnectHost = class {
371
377
  return;
372
378
  }
373
379
  if (msg.method === RPC_METHODS.DISCONNECT) {
380
+ const disconnectedSession = this.session;
374
381
  this.revokeSession();
375
382
  this.sendResult(msg.id, { disconnected: true });
383
+ if (disconnectedSession && this.config.onDisconnect) {
384
+ Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch(console.warn);
385
+ }
376
386
  return;
377
387
  }
378
388
  if (!hasMethodPermission(this.grantedPermissions, msg.method)) {
@@ -639,6 +649,8 @@ var ConnectClient = class {
639
649
  requestedPermissions;
640
650
  timeout;
641
651
  intentTimeout;
652
+ resumeSessionId;
653
+ silent;
642
654
  sessionId = null;
643
655
  grantedPermissions = [];
644
656
  identity = null;
@@ -654,6 +666,8 @@ var ConnectClient = class {
654
666
  this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];
655
667
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
656
668
  this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;
669
+ this.resumeSessionId = config.resumeSessionId ?? null;
670
+ this.silent = config.silent ?? false;
657
671
  }
658
672
  // ===========================================================================
659
673
  // Connection
@@ -673,7 +687,9 @@ var ConnectClient = class {
673
687
  type: "handshake",
674
688
  direction: "request",
675
689
  permissions: this.requestedPermissions,
676
- dapp: this.dapp
690
+ dapp: this.dapp,
691
+ ...this.resumeSessionId ? { sessionId: this.resumeSessionId } : {},
692
+ ...this.silent ? { silent: true } : {}
677
693
  });
678
694
  });
679
695
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../connect/index.ts","../../constants.ts","../../connect/protocol.ts","../../connect/permissions.ts","../../connect/host/ConnectHost.ts","../../connect/client/ConnectClient.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 } 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 isSphereConnectMessage,\n createRequestId,\n} from './protocol';\n\nexport type {\n RpcMethod,\n IntentAction,\n ErrorCode,\n SphereRpcRequest,\n SphereRpcResponse,\n SphereIntentRequest,\n SphereIntentResult,\n SphereEventMessage,\n SphereHandshake,\n SphereRpcError,\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\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// Types\nexport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n ConnectClientConfig,\n ConnectResult,\n ConnectEventHandler,\n} from './types';\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 * Default encryption key for wallet data\n * WARNING: This is a placeholder. In production, use user-provided password.\n * This key is used when no password is provided to encrypt/decrypt mnemonic.\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 /** 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 (tokens, tombstones, archived, forked) is stored via\n * TokenStorageProvider, not here. This avoids duplication.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Pending transfers for this address */\n PENDING_TRANSFERS: 'pending_transfers',\n /** Transfer outbox for this address */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Transaction history for this address */\n TRANSACTION_HISTORY: 'transaction_history',\n /** Pending V5 finalization tokens (unconfirmed instant split tokens) */\n PENDING_V5_TOKENS: 'pending_v5_tokens',\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 /** Processed V5 split group IDs for Nostr re-delivery dedup */\n PROCESSED_SPLIT_GROUP_IDS: 'processed_split_group_ids',\n /** Processed V6 combined transfer IDs for Nostr re-delivery dedup */\n PROCESSED_COMBINED_TRANSFER_IDS: 'processed_combined_transfer_ids',\n} as const;\n\n/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */\nexport const STORAGE_KEYS = {\n ...STORAGE_KEYS_GLOBAL,\n ...STORAGE_KEYS_ADDRESS,\n} as const;\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/** Default Nostr relays */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.unicity.network',\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 /** Token transfer (Unicity custom - 31113) */\n TOKEN_TRANSFER: 31113,\n /** Payment request (Unicity custom - 31115) */\n PAYMENT_REQUEST: 31115,\n /** Payment request response (Unicity custom - 31116) */\n PAYMENT_REQUEST_RESPONSE: 31116,\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 URL\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n */\nexport const DEFAULT_AGGREGATOR_URL = 'https://aggregator.unicity.network/rpc' as const;\n\n/** Dev aggregator URL */\nexport const DEV_AGGREGATOR_URL = 'https://dev-aggregator.dyndns.org/rpc' as const;\n\n/** Test aggregator URL (Goggregator) */\nexport const TEST_AGGREGATOR_URL = 'https://goggregator-test.unicity.network' as const;\n\n/** Default aggregator request timeout (ms) */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n/** Default API key for aggregator authentication */\nexport const DEFAULT_AGGREGATOR_API_KEY = 'sk_06365a9c44654841a366068bcfc68986' as const;\n\n// =============================================================================\n// IPFS Defaults\n// =============================================================================\n\n/** Default IPFS gateways */\nexport const DEFAULT_IPFS_GATEWAYS = [\n 'https://unicity-ipfs1.dyndns.org',\n] as const;\n\n/** Unicity IPFS bootstrap peers */\nexport const DEFAULT_IPFS_BOOTSTRAP_PEERS = [\n '/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh',\n '/dns4/unicity-ipfs3.dyndns.org/tcp/4001/p2p/12D3KooWQ4aujVE4ShLjdusNZBdffq3TbzrwT2DuWZY9H1Gxhwn6',\n '/dns4/unicity-ipfs4.dyndns.org/tcp/4001/p2p/12D3KooWJ1ByPfUzUrpYvgxKU8NZrR8i6PU1tUgMEbQX9Hh2DEn1',\n '/dns4/unicity-ipfs5.dyndns.org/tcp/4001/p2p/12D3KooWB1MdZZGHN5B8TvWXntbycfe7Cjcz7n6eZ9eykZadvmDv',\n] as const;\n\n/** Unicity dedicated IPFS nodes (HTTP API access) */\nexport const UNICITY_IPFS_NODES = [\n {\n host: 'unicity-ipfs1.dyndns.org',\n peerId: '12D3KooWDKJqEMAhH4nsSSiKtK1VLcas5coUqSPZAfbWbZpxtL4u',\n httpPort: 9080,\n httpsPort: 443,\n },\n] as const;\n\n/**\n * Get IPFS gateway URLs for HTTP API access.\n * @param isSecure - Use HTTPS (default: true). Set false for development.\n */\nexport function getIpfsGatewayUrls(isSecure?: boolean): string[] {\n return UNICITY_IPFS_NODES.map((node) =>\n isSecure !== false\n ? `https://${node.host}`\n : `http://${node.host}:${node.httpPort}`,\n );\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 /** ALPHA token (L1 blockchain) */\n ALPHA: 'ALPHA',\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// L1 (ALPHA Blockchain) Defaults\n// =============================================================================\n\n/** Default Fulcrum electrum server for mainnet */\nexport const DEFAULT_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n/** Testnet Fulcrum electrum server */\nexport const TEST_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Remote token registry URL (GitHub raw) */\nexport const TOKEN_REGISTRY_URL =\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json' as const;\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/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n aggregatorUrl: DEFAULT_AGGREGATOR_URL,\n nostrRelays: DEFAULT_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: DEFAULT_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n testnet: {\n name: 'Testnet',\n aggregatorUrl: TEST_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n dev: {\n name: 'Development',\n aggregatorUrl: DEV_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n} as const;\n\nexport type NetworkType = keyof typeof NETWORKS;\nexport type NetworkConfig = (typeof NETWORKS)[NetworkType];\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\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const SPHERE_CONNECT_NAMESPACE = 'sphere-connect';\nexport const SPHERE_CONNECT_VERSION = '1.0';\n\nexport { HOST_READY_TYPE, HOST_READY_TIMEOUT } from '../constants';\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 L1_GET_BALANCE: 'sphere_l1GetBalance',\n L1_GET_HISTORY: 'sphere_l1GetHistory',\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 L1_SEND: 'l1_send',\n DM: 'dm',\n PAYMENT_REQUEST: 'payment_request',\n RECEIVE: 'receive',\n SIGN_MESSAGE: 'sign_message',\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 INSUFFICIENT_BALANCE: 4100,\n INVALID_RECIPIENT: 4101,\n TRANSFER_FAILED: 4102,\n INTENT_CANCELLED: 4200,\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\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}\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 l1Address: string;\n readonly directAddress?: string;\n readonly nametag?: string;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if a message belongs to the Sphere Connect protocol */\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 return m.ns === SPHERE_CONNECT_NAMESPACE && m.v === 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","/**\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 L1_READ: 'l1:read',\n EVENTS_SUBSCRIBE: 'events:subscribe',\n RESOLVE_PEER: 'resolve:peer',\n TRANSFER_REQUEST: 'transfer:request',\n L1_TRANSFER: 'l1:transfer',\n DM_REQUEST: 'dm:request',\n DM_READ: 'dm:read',\n PAYMENT_REQUEST: 'payment:request',\n SIGN_REQUEST: 'sign:request',\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.L1_GET_BALANCE]: PERMISSION_SCOPES.L1_READ,\n [RPC_METHODS.L1_GET_HISTORY]: PERMISSION_SCOPES.L1_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_READ,\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.L1_SEND]: PERMISSION_SCOPES.L1_TRANSFER,\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};\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","/**\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 type { SphereEventType, SphereEventHandler } from '../../types';\nimport type { ConnectTransport, ConnectSession, ConnectHostConfig } from '../types';\nimport type {\n SphereConnectMessage,\n SphereRpcRequest,\n SphereIntentRequest,\n SphereHandshake,\n PublicIdentity,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n ERROR_CODES,\n createRequestId,\n} from '../protocol';\nimport {\n DEFAULT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n} from '../permissions';\nimport type { PermissionScope } from '../permissions';\n\n// Use a minimal interface for the Sphere dependency to avoid circular imports.\n// ConnectHost only needs these public methods from Sphere.\ninterface SphereInstance {\n readonly identity: { chainPubkey: string; l1Address: string; directAddress?: string; nametag?: string } | null;\n readonly payments: {\n getBalance(coinId?: string): unknown[];\n getAssets(coinId?: string): Promise<unknown[]>;\n getFiatBalance(): Promise<number | null>;\n getTokens(filter?: { coinId?: string }): unknown[];\n getHistory(): unknown[];\n readonly l1?: {\n getBalance(): Promise<unknown>;\n getHistory(limit?: number): Promise<unknown[]>;\n };\n };\n resolve(identifier: string): Promise<unknown>;\n on<T extends SphereEventType>(type: T, handler: SphereEventHandler<T>): () => void;\n readonly communications?: {\n getConversations(): Map<string, ConnectDirectMessage[]>;\n getConversationPage(\n peerPubkey: string,\n options?: { limit?: number; before?: number },\n ): { messages: ConnectDirectMessage[]; hasMore: boolean; oldestTimestamp: number | null };\n getUnreadCount(peerPubkey?: string): number;\n markAsRead(messageIds: string[]): Promise<void>;\n sendDM(recipient: string, content: string): Promise<ConnectDirectMessage>;\n resolvePeerNametag(peerPubkey: string): Promise<string | undefined>;\n };\n}\n\n/** Minimal DM type to avoid circular imports with Sphere core types. */\ninterface ConnectDirectMessage {\n readonly id: string;\n readonly senderPubkey: string;\n readonly senderNametag?: string;\n readonly recipientPubkey: string;\n readonly recipientNametag?: string;\n readonly content: string;\n readonly timestamp: number;\n isRead: boolean;\n}\n\nconst DEFAULT_SESSION_TTL_MS = 86400000; // 24 hours\nconst DEFAULT_MAX_RPS = 20;\n\nexport class ConnectHost {\n private readonly sphere: SphereInstance;\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 } }>\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.sphere = config.sphere as SphereInstance;\n this.transport = config.transport;\n this.config = config;\n\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\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). */\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 } }>,\n ): void {\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 /** Revoke the current session */\n revokeSession(): void {\n if (this.session) {\n this.session.active = false;\n this.cleanupEventSubscriptions();\n this.autoApprovedIntents.clear();\n this.session = null;\n this.grantedPermissions.clear();\n }\n }\n\n /** Destroy the host, clean up all resources */\n destroy(): void {\n this.revokeSession();\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\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 // Swallow errors from malformed messages\n console.warn('[ConnectHost] Error handling message:', error);\n }\n }\n\n // ===========================================================================\n // Handshake\n // ===========================================================================\n\n private async handleHandshake(msg: SphereHandshake): Promise<void> {\n const dapp = msg.dapp;\n if (!dapp) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\n\n const requestedPermissions = msg.permissions as PermissionScope[];\n\n const { approved, grantedPermissions } = await this.config.onConnectionRequest(\n dapp,\n requestedPermissions,\n );\n\n if (!approved) {\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 // Build public identity\n const identity = this.getPublicIdentity();\n\n this.sendHandshakeResponse(allPermissions, sessionId, identity);\n }\n\n private sendHandshakeResponse(\n permissions: string[],\n sessionId: string | undefined,\n identity: PublicIdentity | undefined,\n ): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'response',\n permissions,\n sessionId,\n identity,\n });\n }\n\n // ===========================================================================\n // RPC Requests (query)\n // ===========================================================================\n\n private async handleRpcRequest(msg: SphereRpcRequest): Promise<void> {\n // Session check\n if (!this.session?.active) {\n this.sendError(msg.id, ERROR_CODES.NOT_CONNECTED, 'Not connected');\n return;\n }\n\n // Session expiry\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n this.revokeSession();\n this.sendError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n return;\n }\n\n // Rate limit\n if (!this.checkRateLimit()) {\n this.sendError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // Handle disconnect\n if (msg.method === RPC_METHODS.DISCONNECT) {\n this.revokeSession();\n this.sendResult(msg.id, { disconnected: true });\n return;\n }\n\n // 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 try {\n const result = await this.executeMethod(msg.method, msg.params ?? {});\n this.sendResult(msg.id, result);\n } catch (error) {\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, (error as Error).message);\n }\n }\n\n // ===========================================================================\n // Intent Requests\n // ===========================================================================\n\n private async handleIntentRequest(msg: SphereIntentRequest): Promise<void> {\n // Session check\n if (!this.session?.active) {\n this.sendIntentError(msg.id, ERROR_CODES.NOT_CONNECTED, 'Not connected');\n return;\n }\n\n // Session expiry\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n this.revokeSession();\n this.sendIntentError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n return;\n }\n\n // 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 // Check auto-approve before delegating to wallet UI\n const autoHandler = this.autoApprovedIntents.get(msg.action);\n if (autoHandler) {\n const autoResponse = await autoHandler(msg.action, msg.params, this.session);\n if (autoResponse.error) {\n this.sendIntentError(msg.id, autoResponse.error.code, autoResponse.error.message);\n } else {\n this.sendIntentResult(msg.id, autoResponse.result);\n }\n return;\n }\n\n // Delegate to wallet app\n const response = await this.config.onIntent(msg.action, msg.params, this.session);\n\n if (response.error) {\n this.sendIntentError(msg.id, response.error.code, response.error.message);\n } else {\n this.sendIntentResult(msg.id, response.result);\n }\n }\n\n // ===========================================================================\n // Method Router\n // ===========================================================================\n\n private async executeMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n switch (method) {\n case RPC_METHODS.GET_IDENTITY:\n return this.getPublicIdentity();\n\n case RPC_METHODS.GET_BALANCE:\n return this.sphere.payments.getBalance(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_ASSETS:\n return this.sphere.payments.getAssets(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_FIAT_BALANCE:\n return { fiatBalance: await this.sphere.payments.getFiatBalance() };\n\n case RPC_METHODS.GET_TOKENS:\n return this.stripTokenSdkData(\n this.sphere.payments.getTokens(\n params.coinId ? { coinId: params.coinId as string } : undefined,\n ),\n );\n\n case RPC_METHODS.GET_HISTORY:\n return this.sphere.payments.getHistory();\n\n case RPC_METHODS.L1_GET_BALANCE:\n if (!this.sphere.payments.l1) {\n throw new Error('L1 module not available');\n }\n return this.sphere.payments.l1.getBalance();\n\n case RPC_METHODS.L1_GET_HISTORY:\n if (!this.sphere.payments.l1) {\n throw new Error('L1 module not available');\n }\n return this.sphere.payments.l1.getHistory(params.limit as number | undefined);\n\n case RPC_METHODS.RESOLVE:\n if (!params.identifier) {\n throw new Error('Missing required parameter: identifier');\n }\n return this.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 if (!this.sphere.communications) throw new Error('Communications module not available');\n const convos = this.sphere.communications.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: this.sphere.communications.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 this.sphere.communications!.resolvePeerNametag(peerPubkey).catch(() => 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 if (!this.sphere.communications) throw new Error('Communications module not available');\n if (!params.peerPubkey) throw new Error('Missing required parameter: peerPubkey');\n return this.sphere.communications.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 if (!this.sphere.communications) throw new Error('Communications module not available');\n return {\n unreadCount: this.sphere.communications.getUnreadCount(\n params.peerPubkey as string | undefined,\n ),\n };\n }\n\n case RPC_METHODS.MARK_AS_READ: {\n if (!this.sphere.communications) throw new Error('Communications module not available');\n if (!params.messageIds || !Array.isArray(params.messageIds)) {\n throw new Error('Missing required parameter: messageIds (string[])');\n }\n await this.sphere.communications.markAsRead(params.messageIds as string[]);\n return { marked: true, count: (params.messageIds as string[]).length };\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n\n // ===========================================================================\n // Event Subscriptions\n // ===========================================================================\n\n private handleSubscribe(eventName: string): { subscribed: boolean; event: string } {\n if (!eventName) throw new Error('Missing required parameter: event');\n\n if (this.eventSubscriptions.has(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n const unsub = this.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 Error('Missing required parameter: event');\n\n const unsub = this.eventSubscriptions.get(eventName);\n if (unsub) {\n unsub();\n this.eventSubscriptions.delete(eventName);\n }\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 private getPublicIdentity(): PublicIdentity | undefined {\n const id = this.sphere.identity;\n if (!id) return undefined;\n return {\n chainPubkey: id.chainPubkey,\n l1Address: id.l1Address,\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): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n error: { code, message },\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): 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 },\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 dependencies on the Sphere SDK core.\n */\n\nimport type { ConnectTransport, ConnectClientConfig, ConnectResult, ConnectEventHandler } from '../types';\nimport type {\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n createRequestId,\n} from '../protocol';\nimport { ALL_PERMISSIONS } from '../permissions';\nimport type { PermissionScope } from '../permissions';\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\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 sessionId: string | null = null;\n private grantedPermissions: PermissionScope[] = [];\n private identity: PublicIdentity | null = null;\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 }\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 });\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 // ===========================================================================\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 Error('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 });\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 Error('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(`Intent timeout: ${action}`));\n }, this.intentTimeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\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\n if (this.connected) {\n this.query(RPC_METHODS.SUBSCRIBE, { event }).catch(() => {});\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) {\n this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch(() => {});\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 const handlers = this.eventHandlers.get(msg.event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(msg.data);\n } catch {\n // Ignore handler errors\n }\n }\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 if (msg.sessionId && msg.identity) {\n this.sessionId = msg.sessionId;\n this.grantedPermissions = msg.permissions as PermissionScope[];\n this.identity = msg.identity;\n this.connected = true;\n\n this.handshakeResolver.resolve({\n sessionId: msg.sessionId,\n permissions: this.grantedPermissions,\n identity: msg.identity,\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 const err = new Error(error.message);\n (err as Error & { code: number }).code = error.code;\n (err as Error & { data: unknown }).data = error.data;\n pending.reject(err);\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 // Reject all pending requests\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Disconnected'));\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 }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACuBO,IAAM,sBAAsB;AAAA;AAAA,EAEjC,UAAU;AAAA;AAAA,EAEV,YAAY;AAAA;AAAA,EAEZ,YAAY;AAAA;AAAA,EAEZ,iBAAiB;AAAA;AAAA,EAEjB,WAAW;AAAA;AAAA,EAEX,iBAAiB;AAAA;AAAA,EAEjB,eAAe;AAAA;AAAA,EAEf,eAAe;AAAA;AAAA,EAEf,uBAAuB;AAAA;AAAA,EAEvB,kBAAkB;AAAA;AAAA,EAElB,mBAAmB;AAAA;AAAA,EAEnB,sBAAsB;AAAA;AAAA,EAEtB,sBAAsB;AAAA;AAAA,EAEtB,sBAAsB;AAAA;AAAA,EAEtB,yBAAyB;AAAA;AAAA,EAEzB,aAAa;AAAA;AAAA,EAEb,gBAAgB;AAClB;AAUO,IAAM,uBAAuB;AAAA;AAAA,EAElC,mBAAmB;AAAA;AAAA,EAEnB,QAAQ;AAAA;AAAA,EAER,eAAe;AAAA;AAAA,EAEf,UAAU;AAAA;AAAA,EAEV,qBAAqB;AAAA;AAAA,EAErB,mBAAmB;AAAA;AAAA,EAEnB,mBAAmB;AAAA;AAAA,EAEnB,qBAAqB;AAAA;AAAA,EAErB,oBAAoB;AAAA;AAAA,EAEpB,6BAA6B;AAAA;AAAA,EAE7B,2BAA2B;AAAA;AAAA,EAE3B,iCAAiC;AACnC;AAGO,IAAM,eAAe;AAAA,EAC1B,GAAG;AAAA,EACH,GAAG;AACL;AAsKO,IAAM,oBAAoB;AAG1B,IAAM,0BAA0B,GAAG,iBAAiB;AAsGpD,IAAM,kBAAkB;AAGxB,IAAM,qBAAqB;;;AC5W3B,IAAM,2BAA2B;AACjC,IAAM,yBAAyB;AAQ/B,IAAM,cAAc;AAAA,EACzB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,qBAAqB;AAAA,EACrB,cAAc;AAChB;AAQO,IAAM,iBAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,cAAc;AAChB;AAQO,IAAM,cAAc;AAAA;AAAA,EAEzB,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA;AAAA,EAGhB,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,kBAAkB;AACpB;AAmGO,SAAS,uBAAuB,KAA2C;AAChF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,IAAI;AACV,SAAO,EAAE,OAAO,4BAA4B,EAAE,MAAM;AACtD;AAGO,SAAS,kBAA0B;AACxC,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACjE;;;AClLO,IAAM,oBAAoB;AAAA,EAC/B,eAAe;AAAA,EACf,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,cAAc;AAChB;AAKO,IAAM,kBAA8C,OAAO,OAAO,iBAAiB;AAGnF,IAAM,sBAAkD;AAAA,EAC7D,kBAAkB;AACpB;AAMO,IAAM,qBAAsD;AAAA,EACjE,CAAC,YAAY,YAAY,GAAG,kBAAkB;AAAA,EAC9C,CAAC,YAAY,WAAW,GAAG,kBAAkB;AAAA,EAC7C,CAAC,YAAY,UAAU,GAAG,kBAAkB;AAAA,EAC5C,CAAC,YAAY,gBAAgB,GAAG,kBAAkB;AAAA,EAClD,CAAC,YAAY,UAAU,GAAG,kBAAkB;AAAA,EAC5C,CAAC,YAAY,WAAW,GAAG,kBAAkB;AAAA,EAC7C,CAAC,YAAY,cAAc,GAAG,kBAAkB;AAAA,EAChD,CAAC,YAAY,cAAc,GAAG,kBAAkB;AAAA,EAChD,CAAC,YAAY,OAAO,GAAG,kBAAkB;AAAA,EACzC,CAAC,YAAY,SAAS,GAAG,kBAAkB;AAAA,EAC3C,CAAC,YAAY,WAAW,GAAG,kBAAkB;AAAA,EAC7C,CAAC,YAAY,iBAAiB,GAAG,kBAAkB;AAAA,EACnD,CAAC,YAAY,YAAY,GAAG,kBAAkB;AAAA,EAC9C,CAAC,YAAY,mBAAmB,GAAG,kBAAkB;AAAA,EACrD,CAAC,YAAY,YAAY,GAAG,kBAAkB;AAChD;AAMO,IAAM,qBAAsD;AAAA,EACjE,CAAC,eAAe,IAAI,GAAG,kBAAkB;AAAA,EACzC,CAAC,eAAe,OAAO,GAAG,kBAAkB;AAAA,EAC5C,CAAC,eAAe,EAAE,GAAG,kBAAkB;AAAA,EACvC,CAAC,eAAe,eAAe,GAAG,kBAAkB;AAAA,EACpD,CAAC,eAAe,OAAO,GAAG,kBAAkB;AAAA,EAC5C,CAAC,eAAe,YAAY,GAAG,kBAAkB;AACnD;AAOO,SAAS,oBAAoB,SAA8B,QAAyB;AACzF,QAAM,WAAW,mBAAmB,MAAM;AAC1C,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,QAAQ,IAAI,QAAQ;AAC7B;AAGO,SAAS,oBAAoB,SAA8B,QAAyB;AACzF,QAAM,WAAW,mBAAmB,MAAM;AAC1C,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,QAAQ,IAAI,QAAQ;AAC7B;AAGO,SAAS,oBAAoB,aAAyD;AAC3F,QAAM,cAAc,IAAI,IAAY,eAAe;AACnD,SAAO,YAAY,MAAM,CAAC,MAAM,YAAY,IAAI,CAAC,CAAC;AACpD;;;ACrBA,IAAM,yBAAyB;AAC/B,IAAM,kBAAkB;AAEjB,IAAM,cAAN,MAAkB;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EAET,UAAiC;AAAA,EACjC,qBAAkC,oBAAI,IAAI;AAAA;AAAA,EAG1C,qBAA8C,oBAAI,IAAI;AAAA;AAAA;AAAA,EAGtD,sBAAsB,oBAAI,IAGhC;AAAA;AAAA,EAGM,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EAEnB,uBAA4C;AAAA,EAEpD,YAAY,QAA2B;AACrC,SAAK,SAAS,OAAO;AACrB,SAAK,YAAY,OAAO;AACxB,SAAK,SAAS;AAEd,SAAK,uBAAuB,KAAK,UAAU,UAAU,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EACpF;AAAA;AAAA,EAGA,aAAoC;AAClC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,qBACE,QACA,SAKM;AACN,SAAK,oBAAoB,IAAI,QAAQ,OAAO;AAAA,EAC9C;AAAA;AAAA,EAGA,uBAAuB,QAAsB;AAC3C,SAAK,oBAAoB,OAAO,MAAM;AAAA,EACxC;AAAA;AAAA,EAGA,gBAAsB;AACpB,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,SAAS;AACtB,WAAK,0BAA0B;AAC/B,WAAK,oBAAoB,MAAM;AAC/B,WAAK,UAAU;AACf,WAAK,mBAAmB,MAAM;AAAA,IAChC;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,cAAc;AACnB,QAAI,KAAK,sBAAsB;AAC7B,WAAK,qBAAqB;AAC1B,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cAAc,KAA0C;AACpE,QAAI;AACF,UAAI,IAAI,SAAS,eAAe,IAAI,cAAc,WAAW;AAC3D,cAAM,KAAK,gBAAgB,GAAG;AAC9B;AAAA,MACF;AAEA,UAAI,IAAI,SAAS,WAAW;AAC1B,cAAM,KAAK,iBAAiB,GAAG;AAC/B;AAAA,MACF;AAEA,UAAI,IAAI,SAAS,UAAU;AACzB,cAAM,KAAK,oBAAoB,GAAG;AAClC;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAEd,cAAQ,KAAK,yCAAyC,KAAK;AAAA,IAC7D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,gBAAgB,KAAqC;AACjE,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,MAAM;AACT,WAAK,sBAAsB,CAAC,GAAG,QAAW,MAAS;AACnD;AAAA,IACF;AAEA,UAAM,uBAAuB,IAAI;AAEjC,UAAM,EAAE,UAAU,mBAAmB,IAAI,MAAM,KAAK,OAAO;AAAA,MACzD;AAAA,MACA;AAAA,IACF;AAEA,QAAI,CAAC,UAAU;AACb,WAAK,sBAAsB,CAAC,GAAG,QAAW,MAAS;AACnD;AAAA,IACF;AAGA,UAAM,YAAY,gBAAgB;AAClC,UAAM,iBAAiB,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,qBAAqB,GAAG,kBAAkB,CAAC,CAAC;AACnF,UAAM,MAAM,KAAK,OAAO,gBAAgB;AAExC,SAAK,UAAU;AAAA,MACb,IAAI;AAAA,MACJ;AAAA,MACA,aAAa;AAAA,MACb,WAAW,KAAK,IAAI;AAAA,MACpB,WAAW,MAAM,IAAI,KAAK,IAAI,IAAI,MAAM;AAAA,MACxC,QAAQ;AAAA,IACV;AACA,SAAK,qBAAqB,IAAI,IAAI,cAAc;AAGhD,UAAM,WAAW,KAAK,kBAAkB;AAExC,SAAK,sBAAsB,gBAAgB,WAAW,QAAQ;AAAA,EAChE;AAAA,EAEQ,sBACN,aACA,WACA,UACM;AACN,SAAK,UAAU,KAAK;AAAA,MAClB,IAAI;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,MACN,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAiB,KAAsC;AAEnE,QAAI,CAAC,KAAK,SAAS,QAAQ;AACzB,WAAK,UAAU,IAAI,IAAI,YAAY,eAAe,eAAe;AACjE;AAAA,IACF;AAGA,QAAI,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI,IAAI,KAAK,QAAQ,WAAW;AACrE,WAAK,cAAc;AACnB,WAAK,UAAU,IAAI,IAAI,YAAY,iBAAiB,iBAAiB;AACrE;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,eAAe,GAAG;AAC1B,WAAK,UAAU,IAAI,IAAI,YAAY,cAAc,mBAAmB;AACpE;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,YAAY,YAAY;AACzC,WAAK,cAAc;AACnB,WAAK,WAAW,IAAI,IAAI,EAAE,cAAc,KAAK,CAAC;AAC9C;AAAA,IACF;AAGA,QAAI,CAAC,oBAAoB,KAAK,oBAAoB,IAAI,MAAM,GAAG;AAC7D,WAAK,UAAU,IAAI,IAAI,YAAY,mBAAmB,yBAAyB,IAAI,MAAM,EAAE;AAC3F;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,cAAc,IAAI,QAAQ,IAAI,UAAU,CAAC,CAAC;AACpE,WAAK,WAAW,IAAI,IAAI,MAAM;AAAA,IAChC,SAAS,OAAO;AACd,WAAK,UAAU,IAAI,IAAI,YAAY,gBAAiB,MAAgB,OAAO;AAAA,IAC7E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,oBAAoB,KAAyC;AAEzE,QAAI,CAAC,KAAK,SAAS,QAAQ;AACzB,WAAK,gBAAgB,IAAI,IAAI,YAAY,eAAe,eAAe;AACvE;AAAA,IACF;AAGA,QAAI,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI,IAAI,KAAK,QAAQ,WAAW;AACrE,WAAK,cAAc;AACnB,WAAK,gBAAgB,IAAI,IAAI,YAAY,iBAAiB,iBAAiB;AAC3E;AAAA,IACF;AAGA,QAAI,CAAC,oBAAoB,KAAK,oBAAoB,IAAI,MAAM,GAAG;AAC7D,WAAK,gBAAgB,IAAI,IAAI,YAAY,mBAAmB,iCAAiC,IAAI,MAAM,EAAE;AACzG;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,oBAAoB,IAAI,IAAI,MAAM;AAC3D,QAAI,aAAa;AACf,YAAM,eAAe,MAAM,YAAY,IAAI,QAAQ,IAAI,QAAQ,KAAK,OAAO;AAC3E,UAAI,aAAa,OAAO;AACtB,aAAK,gBAAgB,IAAI,IAAI,aAAa,MAAM,MAAM,aAAa,MAAM,OAAO;AAAA,MAClF,OAAO;AACL,aAAK,iBAAiB,IAAI,IAAI,aAAa,MAAM;AAAA,MACnD;AACA;AAAA,IACF;AAGA,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS,IAAI,QAAQ,IAAI,QAAQ,KAAK,OAAO;AAEhF,QAAI,SAAS,OAAO;AAClB,WAAK,gBAAgB,IAAI,IAAI,SAAS,MAAM,MAAM,SAAS,MAAM,OAAO;AAAA,IAC1E,OAAO;AACL,WAAK,iBAAiB,IAAI,IAAI,SAAS,MAAM;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cAAc,QAAgB,QAAmD;AAC7F,YAAQ,QAAQ;AAAA,MACd,KAAK,YAAY;AACf,eAAO,KAAK,kBAAkB;AAAA,MAEhC,KAAK,YAAY;AACf,eAAO,KAAK,OAAO,SAAS,WAAW,OAAO,MAA4B;AAAA,MAE5E,KAAK,YAAY;AACf,eAAO,KAAK,OAAO,SAAS,UAAU,OAAO,MAA4B;AAAA,MAE3E,KAAK,YAAY;AACf,eAAO,EAAE,aAAa,MAAM,KAAK,OAAO,SAAS,eAAe,EAAE;AAAA,MAEpE,KAAK,YAAY;AACf,eAAO,KAAK;AAAA,UACV,KAAK,OAAO,SAAS;AAAA,YACnB,OAAO,SAAS,EAAE,QAAQ,OAAO,OAAiB,IAAI;AAAA,UACxD;AAAA,QACF;AAAA,MAEF,KAAK,YAAY;AACf,eAAO,KAAK,OAAO,SAAS,WAAW;AAAA,MAEzC,KAAK,YAAY;AACf,YAAI,CAAC,KAAK,OAAO,SAAS,IAAI;AAC5B,gBAAM,IAAI,MAAM,yBAAyB;AAAA,QAC3C;AACA,eAAO,KAAK,OAAO,SAAS,GAAG,WAAW;AAAA,MAE5C,KAAK,YAAY;AACf,YAAI,CAAC,KAAK,OAAO,SAAS,IAAI;AAC5B,gBAAM,IAAI,MAAM,yBAAyB;AAAA,QAC3C;AACA,eAAO,KAAK,OAAO,SAAS,GAAG,WAAW,OAAO,KAA2B;AAAA,MAE9E,KAAK,YAAY;AACf,YAAI,CAAC,OAAO,YAAY;AACtB,gBAAM,IAAI,MAAM,wCAAwC;AAAA,QAC1D;AACA,eAAO,KAAK,OAAO,QAAQ,OAAO,UAAoB;AAAA,MAExD,KAAK,YAAY;AACf,eAAO,KAAK,gBAAgB,OAAO,KAAe;AAAA,MAEpD,KAAK,YAAY;AACf,eAAO,KAAK,kBAAkB,OAAO,KAAe;AAAA,MAEtD,KAAK,YAAY,mBAAmB;AAClC,YAAI,CAAC,KAAK,OAAO,eAAgB,OAAM,IAAI,MAAM,qCAAqC;AACtF,cAAM,SAAS,KAAK,OAAO,eAAe,iBAAiB;AAC3D,cAAM,SAMD,CAAC;AAEN,cAAM,eAA6D,CAAC;AACpE,mBAAW,CAAC,MAAM,QAAQ,KAAK,QAAQ;AACrC,cAAI,SAAS,WAAW,EAAG;AAC3B,gBAAM,OAAO,SAAS,SAAS,SAAS,CAAC;AAEzC,gBAAM,cACJ,SAAS,KAAK,OAAK,EAAE,iBAAiB,QAAQ,EAAE,aAAa,GAAG,iBAC7D,SAAS,KAAK,OAAK,EAAE,oBAAoB,QAAQ,EAAE,gBAAgB,GAAG;AAC3E,gBAAM,MAAM,OAAO;AACnB,iBAAO,KAAK;AAAA,YACV,YAAY;AAAA,YACZ;AAAA,YACA,aAAa;AAAA,YACb,aAAa,KAAK,OAAO,eAAe,eAAe,IAAI;AAAA,YAC3D,cAAc,SAAS;AAAA,UACzB,CAAC;AACD,cAAI,CAAC,aAAa;AAChB,yBAAa,KAAK,EAAE,OAAO,KAAK,YAAY,KAAK,CAAC;AAAA,UACpD;AAAA,QACF;AAEA,YAAI,aAAa,SAAS,GAAG;AAC3B,gBAAM,WAAW,MAAM,QAAQ;AAAA,YAC7B,aAAa;AAAA,cAAI,CAAC,EAAE,WAAW,MAC7B,KAAK,OAAO,eAAgB,mBAAmB,UAAU,EAAE,MAAM,MAAM,MAAS;AAAA,YAClF;AAAA,UACF;AACA,mBAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,gBAAI,SAAS,CAAC,GAAG;AACf,qBAAO,aAAa,CAAC,EAAE,KAAK,EAAE,cAAc,SAAS,CAAC;AAAA,YACxD;AAAA,UACF;AAAA,QACF;AACA,eAAO,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,YAAY,EAAE,YAAY,SAAS;AACvE,eAAO;AAAA,MACT;AAAA,MAEA,KAAK,YAAY,cAAc;AAC7B,YAAI,CAAC,KAAK,OAAO,eAAgB,OAAM,IAAI,MAAM,qCAAqC;AACtF,YAAI,CAAC,OAAO,WAAY,OAAM,IAAI,MAAM,wCAAwC;AAChF,eAAO,KAAK,OAAO,eAAe;AAAA,UAChC,OAAO;AAAA,UACP;AAAA,YACE,OAAO,OAAO;AAAA,YACd,QAAQ,OAAO;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,MAEA,KAAK,YAAY,qBAAqB;AACpC,YAAI,CAAC,KAAK,OAAO,eAAgB,OAAM,IAAI,MAAM,qCAAqC;AACtF,eAAO;AAAA,UACL,aAAa,KAAK,OAAO,eAAe;AAAA,YACtC,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,MAEA,KAAK,YAAY,cAAc;AAC7B,YAAI,CAAC,KAAK,OAAO,eAAgB,OAAM,IAAI,MAAM,qCAAqC;AACtF,YAAI,CAAC,OAAO,cAAc,CAAC,MAAM,QAAQ,OAAO,UAAU,GAAG;AAC3D,gBAAM,IAAI,MAAM,mDAAmD;AAAA,QACrE;AACA,cAAM,KAAK,OAAO,eAAe,WAAW,OAAO,UAAsB;AACzE,eAAO,EAAE,QAAQ,MAAM,OAAQ,OAAO,WAAwB,OAAO;AAAA,MACvE;AAAA,MAEA;AACE,cAAM,IAAI,MAAM,mBAAmB,MAAM,EAAE;AAAA,IAC/C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,WAA2D;AACjF,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,mCAAmC;AAEnE,QAAI,KAAK,mBAAmB,IAAI,SAAS,GAAG;AAC1C,aAAO,EAAE,YAAY,MAAM,OAAO,UAAU;AAAA,IAC9C;AAEA,UAAM,QAAQ,KAAK,OAAO,GAAG,WAA8B,CAAC,SAAkB;AAC5E,WAAK,UAAU,KAAK;AAAA,QAClB,IAAI;AAAA,QACJ,GAAG;AAAA,QACH,MAAM;AAAA,QACN,OAAO;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,SAAK,mBAAmB,IAAI,WAAW,KAAK;AAC5C,WAAO,EAAE,YAAY,MAAM,OAAO,UAAU;AAAA,EAC9C;AAAA,EAEQ,kBAAkB,WAA6D;AACrF,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,mCAAmC;AAEnE,UAAM,QAAQ,KAAK,mBAAmB,IAAI,SAAS;AACnD,QAAI,OAAO;AACT,YAAM;AACN,WAAK,mBAAmB,OAAO,SAAS;AAAA,IAC1C;AACA,WAAO,EAAE,cAAc,MAAM,OAAO,UAAU;AAAA,EAChD;AAAA,EAEQ,4BAAkC;AACxC,eAAW,CAAC,EAAE,KAAK,KAAK,KAAK,oBAAoB;AAC/C,YAAM;AAAA,IACR;AACA,SAAK,mBAAmB,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAAgD;AACtD,UAAM,KAAK,KAAK,OAAO;AACvB,QAAI,CAAC,GAAI,QAAO;AAChB,WAAO;AAAA,MACL,aAAa,GAAG;AAAA,MAChB,WAAW,GAAG;AAAA,MACd,eAAe,GAAG;AAAA,MAClB,SAAS,GAAG;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,kBAAkB,QAA8B;AACtD,WAAO,OAAO,IAAI,CAAC,MAAM;AACvB,YAAM,QAAQ;AAEd,YAAM,EAAE,SAAS,UAAU,GAAG,aAAa,IAAI;AAC/C,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEQ,WAAW,IAAY,QAAuB;AACpD,SAAK,UAAU,KAAK;AAAA,MAClB,IAAI;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,UAAU,IAAY,MAAc,SAAuB;AACjE,SAAK,UAAU,KAAK;AAAA,MAClB,IAAI;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,OAAO,EAAE,MAAM,QAAQ;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,IAAY,QAAuB;AAC1D,SAAK,UAAU,KAAK;AAAA,MAClB,IAAI;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,gBAAgB,IAAY,MAAc,SAAuB;AACvE,SAAK,UAAU,KAAK;AAAA,MAClB,IAAI;AAAA,MACJ,GAAG;AAAA,MACH,MAAM;AAAA,MACN;AAAA,MACA,OAAO,EAAE,MAAM,QAAQ;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEQ,iBAA0B;AAChC,UAAM,SAAS,KAAK,OAAO,wBAAwB;AACnD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,KAAK,kBAAkB;AAC/B,WAAK,mBAAmB;AACxB,WAAK,mBAAmB,MAAM;AAAA,IAChC;AACA,SAAK;AACL,WAAO,KAAK,oBAAoB;AAAA,EAClC;AACF;;;ACziBA,IAAM,kBAAkB;AACxB,IAAM,yBAAyB;AAQxB,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAA2B;AAAA,EAC3B,qBAAwC,CAAC;AAAA,EACzC,WAAkC;AAAA,EAClC,YAAY;AAAA,EAEZ,kBAA+C,oBAAI,IAAI;AAAA,EACvD,gBAAuD,oBAAI,IAAI;AAAA,EAC/D,uBAA4C;AAAA;AAAA,EAG5C,oBAIG;AAAA,EAEX,YAAY,QAA6B;AACvC,SAAK,YAAY,OAAO;AACxB,SAAK,OAAO,OAAO;AACnB,SAAK,uBAAuB,OAAO,eAAe,CAAC,GAAG,eAAe;AACrE,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,gBAAgB,OAAO,iBAAiB;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,UAAkC;AAEtC,SAAK,uBAAuB,KAAK,UAAU,UAAU,KAAK,cAAc,KAAK,IAAI,CAAC;AAElF,WAAO,IAAI,QAAuB,CAAC,SAAS,WAAW;AACrD,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,oBAAoB;AACzB,eAAO,IAAI,MAAM,oBAAoB,CAAC;AAAA,MACxC,GAAG,KAAK,OAAO;AAEf,WAAK,oBAAoB,EAAE,SAAS,QAAQ,MAAM;AAGlD,WAAK,UAAU,KAAK;AAAA,QAClB,IAAI;AAAA,QACJ,GAAG;AAAA,QACH,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa,KAAK;AAAA,QAClB,MAAM,KAAK;AAAA,MACb,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,QAAI,KAAK,WAAW;AAClB,UAAI;AACF,cAAM,KAAK,MAAM,YAAY,UAAU;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAA0C;AAC5C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,UAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,iBAAwC;AAC1C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAmB,QAAgB,QAA8C;AACrF,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,eAAe;AAEpD,UAAM,KAAK,gBAAgB;AAE3B,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,gBAAgB,OAAO,EAAE;AAC9B,eAAO,IAAI,MAAM,kBAAkB,MAAM,EAAE,CAAC;AAAA,MAC9C,GAAG,KAAK,OAAO;AAEf,WAAK,gBAAgB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,WAAK,UAAU,KAAK;AAAA,QAClB,IAAI;AAAA,QACJ,GAAG;AAAA,QACH,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAoB,QAAgB,QAA6C;AACrF,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,eAAe;AAEpD,UAAM,KAAK,gBAAgB;AAE3B,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,gBAAgB,OAAO,EAAE;AAC9B,eAAO,IAAI,MAAM,mBAAmB,MAAM,EAAE,CAAC;AAAA,MAC/C,GAAG,KAAK,aAAa;AAErB,WAAK,gBAAgB,IAAI,IAAI;AAAA,QAC3B;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,WAAK,UAAU,KAAK;AAAA,QAClB,IAAI;AAAA,QACJ,GAAG;AAAA,QACH,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,GAAG,OAAe,SAA0C;AAC1D,QAAI,CAAC,KAAK,cAAc,IAAI,KAAK,GAAG;AAClC,WAAK,cAAc,IAAI,OAAO,oBAAI,IAAI,CAAC;AAEvC,UAAI,KAAK,WAAW;AAClB,aAAK,MAAM,YAAY,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC7D;AAAA,IACF;AACA,SAAK,cAAc,IAAI,KAAK,EAAG,IAAI,OAAO;AAE1C,WAAO,MAAM;AACX,YAAM,WAAW,KAAK,cAAc,IAAI,KAAK;AAC7C,UAAI,UAAU;AACZ,iBAAS,OAAO,OAAO;AACvB,YAAI,SAAS,SAAS,GAAG;AACvB,eAAK,cAAc,OAAO,KAAK;AAC/B,cAAI,KAAK,WAAW;AAClB,iBAAK,MAAM,YAAY,aAAa,EAAE,MAAM,CAAC,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,cAAc,KAAiC;AAErD,QAAI,IAAI,SAAS,eAAe,IAAI,cAAc,YAAY;AAC5D,WAAK,wBAAwB,GAAG;AAChC;AAAA,IACF;AAGA,QAAI,IAAI,SAAS,YAAY;AAC3B,WAAK,sBAAsB,IAAI,IAAI,IAAI,QAAQ,IAAI,KAAK;AACxD;AAAA,IACF;AAGA,QAAI,IAAI,SAAS,iBAAiB;AAChC,WAAK,sBAAsB,IAAI,IAAI,IAAI,QAAQ,IAAI,KAAK;AACxD;AAAA,IACF;AAGA,QAAI,IAAI,SAAS,SAAS;AACxB,YAAM,WAAW,KAAK,cAAc,IAAI,IAAI,KAAK;AACjD,UAAI,UAAU;AACZ,mBAAW,WAAW,UAAU;AAC9B,cAAI;AACF,oBAAQ,IAAI,IAAI;AAAA,UAClB,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,wBAAwB,KAAyD;AACvF,QAAI,CAAC,KAAK,kBAAmB;AAE7B,iBAAa,KAAK,kBAAkB,KAAK;AAEzC,QAAI,IAAI,aAAa,IAAI,UAAU;AACjC,WAAK,YAAY,IAAI;AACrB,WAAK,qBAAqB,IAAI;AAC9B,WAAK,WAAW,IAAI;AACpB,WAAK,YAAY;AAEjB,WAAK,kBAAkB,QAAQ;AAAA,QAC7B,WAAW,IAAI;AAAA,QACf,aAAa,KAAK;AAAA,QAClB,UAAU,IAAI;AAAA,MAChB,CAAC;AAAA,IACH,OAAO;AACL,WAAK,kBAAkB,OAAO,IAAI,MAAM,+BAA+B,CAAC;AAAA,IAC1E;AAEA,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEQ,sBACN,IACA,QACA,OACM;AACN,UAAM,UAAU,KAAK,gBAAgB,IAAI,EAAE;AAC3C,QAAI,CAAC,QAAS;AAEd,iBAAa,QAAQ,KAAK;AAC1B,SAAK,gBAAgB,OAAO,EAAE;AAE9B,QAAI,OAAO;AACT,YAAM,MAAM,IAAI,MAAM,MAAM,OAAO;AACnC,MAAC,IAAiC,OAAO,MAAM;AAC/C,MAAC,IAAkC,OAAO,MAAM;AAChD,cAAQ,OAAO,GAAG;AAAA,IACpB,OAAO;AACL,cAAQ,QAAQ,MAAM;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAgB;AACtB,QAAI,KAAK,sBAAsB;AAC7B,WAAK,qBAAqB;AAC1B,WAAK,uBAAuB;AAAA,IAC9B;AAGA,eAAW,CAAC,EAAE,OAAO,KAAK,KAAK,iBAAiB;AAC9C,mBAAa,QAAQ,KAAK;AAC1B,cAAQ,OAAO,IAAI,MAAM,cAAc,CAAC;AAAA,IAC1C;AACA,SAAK,gBAAgB,MAAM;AAC3B,SAAK,cAAc,MAAM;AAEzB,SAAK,YAAY;AACjB,SAAK,YAAY;AACjB,SAAK,qBAAqB,CAAC;AAC3B,SAAK,WAAW;AAAA,EAClB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../connect/index.ts","../../constants.ts","../../connect/protocol.ts","../../connect/permissions.ts","../../connect/host/ConnectHost.ts","../../connect/client/ConnectClient.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 } 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 isSphereConnectMessage,\n createRequestId,\n} from './protocol';\n\nexport type {\n RpcMethod,\n IntentAction,\n ErrorCode,\n SphereRpcRequest,\n SphereRpcResponse,\n SphereIntentRequest,\n SphereIntentResult,\n SphereEventMessage,\n SphereHandshake,\n SphereRpcError,\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\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// Types\nexport type {\n ConnectTransport,\n ConnectSession,\n ConnectHostConfig,\n ConnectClientConfig,\n ConnectResult,\n ConnectEventHandler,\n} from './types';\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 * Default encryption key for wallet data\n * WARNING: This is a placeholder. In production, use user-provided password.\n * This key is used when no password is provided to encrypt/decrypt mnemonic.\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 /** 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 (tokens, tombstones, archived, forked) is stored via\n * TokenStorageProvider, not here. This avoids duplication.\n */\nexport const STORAGE_KEYS_ADDRESS = {\n /** Pending transfers for this address */\n PENDING_TRANSFERS: 'pending_transfers',\n /** Transfer outbox for this address */\n OUTBOX: 'outbox',\n /** Conversations for this address */\n CONVERSATIONS: 'conversations',\n /** Messages for this address */\n MESSAGES: 'messages',\n /** Transaction history for this address */\n TRANSACTION_HISTORY: 'transaction_history',\n /** Pending V5 finalization tokens (unconfirmed instant split tokens) */\n PENDING_V5_TOKENS: 'pending_v5_tokens',\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 /** Processed V5 split group IDs for Nostr re-delivery dedup */\n PROCESSED_SPLIT_GROUP_IDS: 'processed_split_group_ids',\n /** Processed V6 combined transfer IDs for Nostr re-delivery dedup */\n PROCESSED_COMBINED_TRANSFER_IDS: 'processed_combined_transfer_ids',\n} as const;\n\n/** @deprecated Use STORAGE_KEYS_GLOBAL and STORAGE_KEYS_ADDRESS instead */\nexport const STORAGE_KEYS = {\n ...STORAGE_KEYS_GLOBAL,\n ...STORAGE_KEYS_ADDRESS,\n} as const;\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/** Default Nostr relays */\nexport const DEFAULT_NOSTR_RELAYS = [\n 'wss://relay.unicity.network',\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 /** Token transfer (Unicity custom - 31113) */\n TOKEN_TRANSFER: 31113,\n /** Payment request (Unicity custom - 31115) */\n PAYMENT_REQUEST: 31115,\n /** Payment request response (Unicity custom - 31116) */\n PAYMENT_REQUEST_RESPONSE: 31116,\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 URL\n * Note: The aggregator is conceptually an oracle - a trusted service that provides\n * verifiable truth about token state through cryptographic inclusion proofs.\n */\nexport const DEFAULT_AGGREGATOR_URL = 'https://aggregator.unicity.network/rpc' as const;\n\n/** Dev aggregator URL */\nexport const DEV_AGGREGATOR_URL = 'https://dev-aggregator.dyndns.org/rpc' as const;\n\n/** Test aggregator URL (Goggregator) */\nexport const TEST_AGGREGATOR_URL = 'https://goggregator-test.unicity.network' as const;\n\n/** Default aggregator request timeout (ms) */\nexport const DEFAULT_AGGREGATOR_TIMEOUT = 30000;\n\n/** Default API key for aggregator authentication */\nexport const DEFAULT_AGGREGATOR_API_KEY = 'sk_06365a9c44654841a366068bcfc68986' as const;\n\n// =============================================================================\n// IPFS Defaults\n// =============================================================================\n\n/** Default IPFS gateways */\nexport const DEFAULT_IPFS_GATEWAYS = [\n 'https://unicity-ipfs1.dyndns.org',\n] as const;\n\n/** Unicity IPFS bootstrap peers */\nexport const DEFAULT_IPFS_BOOTSTRAP_PEERS = [\n '/dns4/unicity-ipfs2.dyndns.org/tcp/4001/p2p/12D3KooWLNi5NDPPHbrfJakAQqwBqymYTTwMQXQKEWuCrJNDdmfh',\n '/dns4/unicity-ipfs3.dyndns.org/tcp/4001/p2p/12D3KooWQ4aujVE4ShLjdusNZBdffq3TbzrwT2DuWZY9H1Gxhwn6',\n '/dns4/unicity-ipfs4.dyndns.org/tcp/4001/p2p/12D3KooWJ1ByPfUzUrpYvgxKU8NZrR8i6PU1tUgMEbQX9Hh2DEn1',\n '/dns4/unicity-ipfs5.dyndns.org/tcp/4001/p2p/12D3KooWB1MdZZGHN5B8TvWXntbycfe7Cjcz7n6eZ9eykZadvmDv',\n] as const;\n\n/** Unicity dedicated IPFS nodes (HTTP API access) */\nexport const UNICITY_IPFS_NODES = [\n {\n host: 'unicity-ipfs1.dyndns.org',\n peerId: '12D3KooWDKJqEMAhH4nsSSiKtK1VLcas5coUqSPZAfbWbZpxtL4u',\n httpPort: 9080,\n httpsPort: 443,\n },\n] as const;\n\n/**\n * Get IPFS gateway URLs for HTTP API access.\n * @param isSecure - Use HTTPS (default: true). Set false for development.\n */\nexport function getIpfsGatewayUrls(isSecure?: boolean): string[] {\n return UNICITY_IPFS_NODES.map((node) =>\n isSecure !== false\n ? `https://${node.host}`\n : `http://${node.host}:${node.httpPort}`,\n );\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 /** ALPHA token (L1 blockchain) */\n ALPHA: 'ALPHA',\n /** Test token */\n TEST: 'TEST',\n} as const;\n\n// =============================================================================\n// L1 (ALPHA Blockchain) Defaults\n// =============================================================================\n\n/** Default Fulcrum electrum server for mainnet */\nexport const DEFAULT_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n/** Testnet Fulcrum electrum server */\nexport const TEST_ELECTRUM_URL = 'wss://fulcrum.unicity.network:50004' as const;\n\n// =============================================================================\n// Token Registry Defaults\n// =============================================================================\n\n/** Remote token registry URL (GitHub raw) */\nexport const TOKEN_REGISTRY_URL =\n 'https://raw.githubusercontent.com/unicitynetwork/unicity-ids/refs/heads/main/unicity-ids.testnet.json' as const;\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/** Network configurations */\nexport const NETWORKS = {\n mainnet: {\n name: 'Mainnet',\n aggregatorUrl: DEFAULT_AGGREGATOR_URL,\n nostrRelays: DEFAULT_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: DEFAULT_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n testnet: {\n name: 'Testnet',\n aggregatorUrl: TEST_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n dev: {\n name: 'Development',\n aggregatorUrl: DEV_AGGREGATOR_URL,\n nostrRelays: TEST_NOSTR_RELAYS,\n ipfsGateways: DEFAULT_IPFS_GATEWAYS,\n electrumUrl: TEST_ELECTRUM_URL,\n groupRelays: DEFAULT_GROUP_RELAYS,\n tokenRegistryUrl: TOKEN_REGISTRY_URL,\n },\n} as const;\n\nexport type NetworkType = keyof typeof NETWORKS;\nexport type NetworkConfig = (typeof NETWORKS)[NetworkType];\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\n// =============================================================================\n// Constants\n// =============================================================================\n\nexport const SPHERE_CONNECT_NAMESPACE = 'sphere-connect';\nexport const SPHERE_CONNECT_VERSION = '1.0';\n\nexport { HOST_READY_TYPE, HOST_READY_TIMEOUT } from '../constants';\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 L1_GET_BALANCE: 'sphere_l1GetBalance',\n L1_GET_HISTORY: 'sphere_l1GetHistory',\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 L1_SEND: 'l1_send',\n DM: 'dm',\n PAYMENT_REQUEST: 'payment_request',\n RECEIVE: 'receive',\n SIGN_MESSAGE: 'sign_message',\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 INSUFFICIENT_BALANCE: 4100,\n INVALID_RECIPIENT: 4101,\n TRANSFER_FAILED: 4102,\n INTENT_CANCELLED: 4200,\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODES)[keyof typeof ERROR_CODES];\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}\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 l1Address: string;\n readonly directAddress?: string;\n readonly nametag?: string;\n}\n\n// =============================================================================\n// Helpers\n// =============================================================================\n\n/** Check if a message belongs to the Sphere Connect protocol */\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 return m.ns === SPHERE_CONNECT_NAMESPACE && m.v === 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","/**\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 L1_READ: 'l1:read',\n EVENTS_SUBSCRIBE: 'events:subscribe',\n RESOLVE_PEER: 'resolve:peer',\n TRANSFER_REQUEST: 'transfer:request',\n L1_TRANSFER: 'l1:transfer',\n DM_REQUEST: 'dm:request',\n DM_READ: 'dm:read',\n PAYMENT_REQUEST: 'payment:request',\n SIGN_REQUEST: 'sign:request',\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.L1_GET_BALANCE]: PERMISSION_SCOPES.L1_READ,\n [RPC_METHODS.L1_GET_HISTORY]: PERMISSION_SCOPES.L1_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_READ,\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.L1_SEND]: PERMISSION_SCOPES.L1_TRANSFER,\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};\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","/**\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 type { SphereEventType, SphereEventHandler } from '../../types';\nimport type { ConnectTransport, ConnectSession, ConnectHostConfig } from '../types';\nimport type {\n SphereConnectMessage,\n SphereRpcRequest,\n SphereIntentRequest,\n SphereHandshake,\n PublicIdentity,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n ERROR_CODES,\n createRequestId,\n} from '../protocol';\nimport {\n DEFAULT_PERMISSIONS,\n hasMethodPermission,\n hasIntentPermission,\n} from '../permissions';\nimport type { PermissionScope } from '../permissions';\n\n// Use a minimal interface for the Sphere dependency to avoid circular imports.\n// ConnectHost only needs these public methods from Sphere.\ninterface SphereInstance {\n readonly identity: { chainPubkey: string; l1Address: string; directAddress?: string; nametag?: string } | null;\n readonly payments: {\n getBalance(coinId?: string): unknown[];\n getAssets(coinId?: string): Promise<unknown[]>;\n getFiatBalance(): Promise<number | null>;\n getTokens(filter?: { coinId?: string }): unknown[];\n getHistory(): unknown[];\n readonly l1?: {\n getBalance(): Promise<unknown>;\n getHistory(limit?: number): Promise<unknown[]>;\n };\n };\n resolve(identifier: string): Promise<unknown>;\n on<T extends SphereEventType>(type: T, handler: SphereEventHandler<T>): () => void;\n readonly communications?: {\n getConversations(): Map<string, ConnectDirectMessage[]>;\n getConversationPage(\n peerPubkey: string,\n options?: { limit?: number; before?: number },\n ): { messages: ConnectDirectMessage[]; hasMore: boolean; oldestTimestamp: number | null };\n getUnreadCount(peerPubkey?: string): number;\n markAsRead(messageIds: string[]): Promise<void>;\n sendDM(recipient: string, content: string): Promise<ConnectDirectMessage>;\n resolvePeerNametag(peerPubkey: string): Promise<string | undefined>;\n };\n}\n\n/** Minimal DM type to avoid circular imports with Sphere core types. */\ninterface ConnectDirectMessage {\n readonly id: string;\n readonly senderPubkey: string;\n readonly senderNametag?: string;\n readonly recipientPubkey: string;\n readonly recipientNametag?: string;\n readonly content: string;\n readonly timestamp: number;\n isRead: boolean;\n}\n\nconst DEFAULT_SESSION_TTL_MS = 86400000; // 24 hours\nconst DEFAULT_MAX_RPS = 20;\n\nexport class ConnectHost {\n private readonly sphere: SphereInstance;\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 } }>\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.sphere = config.sphere as SphereInstance;\n this.transport = config.transport;\n this.config = config;\n\n this.unsubscribeTransport = this.transport.onMessage(this.handleMessage.bind(this));\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). */\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 } }>,\n ): void {\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 /** Revoke the current session */\n revokeSession(): void {\n if (this.session) {\n this.session.active = false;\n this.cleanupEventSubscriptions();\n this.autoApprovedIntents.clear();\n this.session = null;\n this.grantedPermissions.clear();\n }\n }\n\n /** Destroy the host, clean up all resources */\n destroy(): void {\n this.revokeSession();\n if (this.unsubscribeTransport) {\n this.unsubscribeTransport();\n this.unsubscribeTransport = null;\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 // Swallow errors from malformed messages\n console.warn('[ConnectHost] Error handling message:', error);\n }\n }\n\n // ===========================================================================\n // Handshake\n // ===========================================================================\n\n private async handleHandshake(msg: SphereHandshake): Promise<void> {\n const dapp = msg.dapp;\n if (!dapp) {\n this.sendHandshakeResponse([], undefined, undefined);\n return;\n }\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 if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {\n const identity = this.getPublicIdentity();\n this.sendHandshakeResponse([...this.grantedPermissions], this.session.id, identity);\n return;\n }\n\n const requestedPermissions = msg.permissions as PermissionScope[];\n\n const { approved, grantedPermissions } = await this.config.onConnectionRequest(\n dapp,\n requestedPermissions,\n msg.silent,\n );\n\n if (!approved) {\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 // Build public identity\n const identity = this.getPublicIdentity();\n\n this.sendHandshakeResponse(allPermissions, sessionId, identity);\n }\n\n private sendHandshakeResponse(\n permissions: string[],\n sessionId: string | undefined,\n identity: PublicIdentity | undefined,\n ): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'handshake',\n direction: 'response',\n permissions,\n sessionId,\n identity,\n });\n }\n\n // ===========================================================================\n // RPC Requests (query)\n // ===========================================================================\n\n private async handleRpcRequest(msg: SphereRpcRequest): Promise<void> {\n // Session check\n if (!this.session?.active) {\n this.sendError(msg.id, ERROR_CODES.NOT_CONNECTED, 'Not connected');\n return;\n }\n\n // Session expiry\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n this.revokeSession();\n this.sendError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n return;\n }\n\n // Rate limit\n if (!this.checkRateLimit()) {\n this.sendError(msg.id, ERROR_CODES.RATE_LIMITED, 'Too many requests');\n return;\n }\n\n // Handle disconnect\n if (msg.method === RPC_METHODS.DISCONNECT) {\n const disconnectedSession = this.session;\n this.revokeSession();\n this.sendResult(msg.id, { disconnected: true });\n if (disconnectedSession && this.config.onDisconnect) {\n // Fire-and-forget: don't block the response\n Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch(console.warn);\n }\n return;\n }\n\n // 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 try {\n const result = await this.executeMethod(msg.method, msg.params ?? {});\n this.sendResult(msg.id, result);\n } catch (error) {\n this.sendError(msg.id, ERROR_CODES.INTERNAL_ERROR, (error as Error).message);\n }\n }\n\n // ===========================================================================\n // Intent Requests\n // ===========================================================================\n\n private async handleIntentRequest(msg: SphereIntentRequest): Promise<void> {\n // Session check\n if (!this.session?.active) {\n this.sendIntentError(msg.id, ERROR_CODES.NOT_CONNECTED, 'Not connected');\n return;\n }\n\n // Session expiry\n if (this.session.expiresAt > 0 && Date.now() > this.session.expiresAt) {\n this.revokeSession();\n this.sendIntentError(msg.id, ERROR_CODES.SESSION_EXPIRED, 'Session expired');\n return;\n }\n\n // 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 // Check auto-approve before delegating to wallet UI\n const autoHandler = this.autoApprovedIntents.get(msg.action);\n if (autoHandler) {\n const autoResponse = await autoHandler(msg.action, msg.params, this.session);\n if (autoResponse.error) {\n this.sendIntentError(msg.id, autoResponse.error.code, autoResponse.error.message);\n } else {\n this.sendIntentResult(msg.id, autoResponse.result);\n }\n return;\n }\n\n // Delegate to wallet app\n const response = await this.config.onIntent(msg.action, msg.params, this.session);\n\n if (response.error) {\n this.sendIntentError(msg.id, response.error.code, response.error.message);\n } else {\n this.sendIntentResult(msg.id, response.result);\n }\n }\n\n // ===========================================================================\n // Method Router\n // ===========================================================================\n\n private async executeMethod(method: string, params: Record<string, unknown>): Promise<unknown> {\n switch (method) {\n case RPC_METHODS.GET_IDENTITY:\n return this.getPublicIdentity();\n\n case RPC_METHODS.GET_BALANCE:\n return this.sphere.payments.getBalance(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_ASSETS:\n return this.sphere.payments.getAssets(params.coinId as string | undefined);\n\n case RPC_METHODS.GET_FIAT_BALANCE:\n return { fiatBalance: await this.sphere.payments.getFiatBalance() };\n\n case RPC_METHODS.GET_TOKENS:\n return this.stripTokenSdkData(\n this.sphere.payments.getTokens(\n params.coinId ? { coinId: params.coinId as string } : undefined,\n ),\n );\n\n case RPC_METHODS.GET_HISTORY:\n return this.sphere.payments.getHistory();\n\n case RPC_METHODS.L1_GET_BALANCE:\n if (!this.sphere.payments.l1) {\n throw new Error('L1 module not available');\n }\n return this.sphere.payments.l1.getBalance();\n\n case RPC_METHODS.L1_GET_HISTORY:\n if (!this.sphere.payments.l1) {\n throw new Error('L1 module not available');\n }\n return this.sphere.payments.l1.getHistory(params.limit as number | undefined);\n\n case RPC_METHODS.RESOLVE:\n if (!params.identifier) {\n throw new Error('Missing required parameter: identifier');\n }\n return this.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 if (!this.sphere.communications) throw new Error('Communications module not available');\n const convos = this.sphere.communications.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: this.sphere.communications.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 this.sphere.communications!.resolvePeerNametag(peerPubkey).catch(() => 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 if (!this.sphere.communications) throw new Error('Communications module not available');\n if (!params.peerPubkey) throw new Error('Missing required parameter: peerPubkey');\n return this.sphere.communications.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 if (!this.sphere.communications) throw new Error('Communications module not available');\n return {\n unreadCount: this.sphere.communications.getUnreadCount(\n params.peerPubkey as string | undefined,\n ),\n };\n }\n\n case RPC_METHODS.MARK_AS_READ: {\n if (!this.sphere.communications) throw new Error('Communications module not available');\n if (!params.messageIds || !Array.isArray(params.messageIds)) {\n throw new Error('Missing required parameter: messageIds (string[])');\n }\n await this.sphere.communications.markAsRead(params.messageIds as string[]);\n return { marked: true, count: (params.messageIds as string[]).length };\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n\n // ===========================================================================\n // Event Subscriptions\n // ===========================================================================\n\n private handleSubscribe(eventName: string): { subscribed: boolean; event: string } {\n if (!eventName) throw new Error('Missing required parameter: event');\n\n if (this.eventSubscriptions.has(eventName)) {\n return { subscribed: true, event: eventName };\n }\n\n const unsub = this.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 Error('Missing required parameter: event');\n\n const unsub = this.eventSubscriptions.get(eventName);\n if (unsub) {\n unsub();\n this.eventSubscriptions.delete(eventName);\n }\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 private getPublicIdentity(): PublicIdentity | undefined {\n const id = this.sphere.identity;\n if (!id) return undefined;\n return {\n chainPubkey: id.chainPubkey,\n l1Address: id.l1Address,\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): void {\n this.transport.send({\n ns: SPHERE_CONNECT_NAMESPACE,\n v: SPHERE_CONNECT_VERSION,\n type: 'response',\n id,\n error: { code, message },\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): 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 },\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 dependencies on the Sphere SDK core.\n */\n\nimport type { ConnectTransport, ConnectClientConfig, ConnectResult, ConnectEventHandler } from '../types';\nimport type {\n SphereConnectMessage,\n DAppMetadata,\n PublicIdentity,\n} from '../protocol';\nimport {\n SPHERE_CONNECT_NAMESPACE,\n SPHERE_CONNECT_VERSION,\n RPC_METHODS,\n createRequestId,\n} from '../protocol';\nimport { ALL_PERMISSIONS } from '../permissions';\nimport type { PermissionScope } from '../permissions';\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\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\n private sessionId: string | null = null;\n private grantedPermissions: PermissionScope[] = [];\n private identity: PublicIdentity | null = null;\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 }\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 ...(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 // ===========================================================================\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 Error('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 });\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 Error('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(`Intent timeout: ${action}`));\n }, this.intentTimeout);\n\n this.pendingRequests.set(id, {\n resolve: resolve as (v: unknown) => void,\n reject,\n timer,\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\n if (this.connected) {\n this.query(RPC_METHODS.SUBSCRIBE, { event }).catch(() => {});\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) {\n this.query(RPC_METHODS.UNSUBSCRIBE, { event }).catch(() => {});\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 const handlers = this.eventHandlers.get(msg.event);\n if (handlers) {\n for (const handler of handlers) {\n try {\n handler(msg.data);\n } catch {\n // Ignore handler errors\n }\n }\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 if (msg.sessionId && msg.identity) {\n this.sessionId = msg.sessionId;\n this.grantedPermissions = msg.permissions as PermissionScope[];\n this.identity = msg.identity;\n this.connected = true;\n\n this.handshakeResolver.resolve({\n sessionId: msg.sessionId,\n permissions: this.grantedPermissions,\n identity: msg.identity,\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 const err = new Error(error.message);\n (err as Error & { code: number }).code = error.code;\n (err as Error & { data: unknown }).data = error.data;\n pending.reject(err);\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 // Reject all pending requests\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Disconnected'));\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 }\n}\n"],"mappings":"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","names":["identity"]}
@@ -102,6 +102,8 @@ interface SphereHandshake extends SphereMessageBase {
102
102
  readonly dapp?: DAppMetadata;
103
103
  readonly sessionId?: string;
104
104
  readonly identity?: PublicIdentity;
105
+ /** If true, wallet must NOT open any approval UI. Immediately reject if origin is not already approved. */
106
+ readonly silent?: boolean;
105
107
  }
106
108
  interface SphereRpcError {
107
109
  readonly code: number;
@@ -185,8 +187,9 @@ interface ConnectHostConfig {
185
187
  sphere: unknown;
186
188
  /** Transport layer for communication */
187
189
  transport: ConnectTransport;
188
- /** Called when dApp requests connection. Wallet shows approval UI. */
189
- onConnectionRequest: (dapp: DAppMetadata, requestedPermissions: PermissionScope[]) => Promise<{
190
+ /** Called when dApp requests connection. Wallet shows approval UI.
191
+ * When `silent` is true, the wallet must NOT open any UI — return rejected immediately if origin is unknown. */
192
+ onConnectionRequest: (dapp: DAppMetadata, requestedPermissions: PermissionScope[], silent?: boolean) => Promise<{
190
193
  approved: boolean;
191
194
  grantedPermissions: PermissionScope[];
192
195
  }>;
@@ -198,6 +201,8 @@ interface ConnectHostConfig {
198
201
  message: string;
199
202
  };
200
203
  }>;
204
+ /** Called when dApp explicitly disconnects. Wallet can revoke persisted permissions. */
205
+ onDisconnect?: (session: ConnectSession) => void | Promise<void>;
201
206
  /** Session time-to-live in ms. Default: 86400000 (24h). 0 = no expiry. */
202
207
  sessionTtlMs?: number;
203
208
  /** Max requests per second per session. Default: 20. */
@@ -214,6 +219,12 @@ interface ConnectClientConfig {
214
219
  timeout?: number;
215
220
  /** Timeout for intent requests in ms (user interaction). Default: 120000. */
216
221
  intentTimeout?: number;
222
+ /** Existing session ID to resume. If the host still has an active session
223
+ * with this ID, the connection is restored without re-showing the approval UI. */
224
+ resumeSessionId?: string;
225
+ /** If true, the connection will silently fail if the origin is not already approved by the wallet.
226
+ * No approval UI will be shown. Used for auto-connect on page load. */
227
+ silent?: boolean;
217
228
  }
218
229
  interface ConnectResult {
219
230
  readonly sessionId: string;
@@ -292,6 +303,8 @@ declare class ConnectClient {
292
303
  private readonly requestedPermissions;
293
304
  private readonly timeout;
294
305
  private readonly intentTimeout;
306
+ private readonly resumeSessionId;
307
+ private readonly silent;
295
308
  private sessionId;
296
309
  private grantedPermissions;
297
310
  private identity;
@@ -102,6 +102,8 @@ interface SphereHandshake extends SphereMessageBase {
102
102
  readonly dapp?: DAppMetadata;
103
103
  readonly sessionId?: string;
104
104
  readonly identity?: PublicIdentity;
105
+ /** If true, wallet must NOT open any approval UI. Immediately reject if origin is not already approved. */
106
+ readonly silent?: boolean;
105
107
  }
106
108
  interface SphereRpcError {
107
109
  readonly code: number;
@@ -185,8 +187,9 @@ interface ConnectHostConfig {
185
187
  sphere: unknown;
186
188
  /** Transport layer for communication */
187
189
  transport: ConnectTransport;
188
- /** Called when dApp requests connection. Wallet shows approval UI. */
189
- onConnectionRequest: (dapp: DAppMetadata, requestedPermissions: PermissionScope[]) => Promise<{
190
+ /** Called when dApp requests connection. Wallet shows approval UI.
191
+ * When `silent` is true, the wallet must NOT open any UI — return rejected immediately if origin is unknown. */
192
+ onConnectionRequest: (dapp: DAppMetadata, requestedPermissions: PermissionScope[], silent?: boolean) => Promise<{
190
193
  approved: boolean;
191
194
  grantedPermissions: PermissionScope[];
192
195
  }>;
@@ -198,6 +201,8 @@ interface ConnectHostConfig {
198
201
  message: string;
199
202
  };
200
203
  }>;
204
+ /** Called when dApp explicitly disconnects. Wallet can revoke persisted permissions. */
205
+ onDisconnect?: (session: ConnectSession) => void | Promise<void>;
201
206
  /** Session time-to-live in ms. Default: 86400000 (24h). 0 = no expiry. */
202
207
  sessionTtlMs?: number;
203
208
  /** Max requests per second per session. Default: 20. */
@@ -214,6 +219,12 @@ interface ConnectClientConfig {
214
219
  timeout?: number;
215
220
  /** Timeout for intent requests in ms (user interaction). Default: 120000. */
216
221
  intentTimeout?: number;
222
+ /** Existing session ID to resume. If the host still has an active session
223
+ * with this ID, the connection is restored without re-showing the approval UI. */
224
+ resumeSessionId?: string;
225
+ /** If true, the connection will silently fail if the origin is not already approved by the wallet.
226
+ * No approval UI will be shown. Used for auto-connect on page load. */
227
+ silent?: boolean;
217
228
  }
218
229
  interface ConnectResult {
219
230
  readonly sessionId: string;
@@ -292,6 +303,8 @@ declare class ConnectClient {
292
303
  private readonly requestedPermissions;
293
304
  private readonly timeout;
294
305
  private readonly intentTimeout;
306
+ private readonly resumeSessionId;
307
+ private readonly silent;
295
308
  private sessionId;
296
309
  private grantedPermissions;
297
310
  private identity;
@@ -274,10 +274,16 @@ var ConnectHost = class {
274
274
  this.sendHandshakeResponse([], void 0, void 0);
275
275
  return;
276
276
  }
277
+ if (msg.sessionId && this.session?.active && this.session.id === msg.sessionId) {
278
+ const identity2 = this.getPublicIdentity();
279
+ this.sendHandshakeResponse([...this.grantedPermissions], this.session.id, identity2);
280
+ return;
281
+ }
277
282
  const requestedPermissions = msg.permissions;
278
283
  const { approved, grantedPermissions } = await this.config.onConnectionRequest(
279
284
  dapp,
280
- requestedPermissions
285
+ requestedPermissions,
286
+ msg.silent
281
287
  );
282
288
  if (!approved) {
283
289
  this.sendHandshakeResponse([], void 0, void 0);
@@ -327,8 +333,12 @@ var ConnectHost = class {
327
333
  return;
328
334
  }
329
335
  if (msg.method === RPC_METHODS.DISCONNECT) {
336
+ const disconnectedSession = this.session;
330
337
  this.revokeSession();
331
338
  this.sendResult(msg.id, { disconnected: true });
339
+ if (disconnectedSession && this.config.onDisconnect) {
340
+ Promise.resolve(this.config.onDisconnect(disconnectedSession)).catch(console.warn);
341
+ }
332
342
  return;
333
343
  }
334
344
  if (!hasMethodPermission(this.grantedPermissions, msg.method)) {
@@ -595,6 +605,8 @@ var ConnectClient = class {
595
605
  requestedPermissions;
596
606
  timeout;
597
607
  intentTimeout;
608
+ resumeSessionId;
609
+ silent;
598
610
  sessionId = null;
599
611
  grantedPermissions = [];
600
612
  identity = null;
@@ -610,6 +622,8 @@ var ConnectClient = class {
610
622
  this.requestedPermissions = config.permissions ?? [...ALL_PERMISSIONS];
611
623
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
612
624
  this.intentTimeout = config.intentTimeout ?? DEFAULT_INTENT_TIMEOUT;
625
+ this.resumeSessionId = config.resumeSessionId ?? null;
626
+ this.silent = config.silent ?? false;
613
627
  }
614
628
  // ===========================================================================
615
629
  // Connection
@@ -629,7 +643,9 @@ var ConnectClient = class {
629
643
  type: "handshake",
630
644
  direction: "request",
631
645
  permissions: this.requestedPermissions,
632
- dapp: this.dapp
646
+ dapp: this.dapp,
647
+ ...this.resumeSessionId ? { sessionId: this.resumeSessionId } : {},
648
+ ...this.silent ? { silent: true } : {}
633
649
  });
634
650
  });
635
651
  }