@unicitylabs/sphere-sdk 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect/index.cjs +57 -0
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +30 -2
- package/dist/connect/index.d.ts +30 -2
- package/dist/connect/index.js +57 -0
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +84 -52
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +84 -52
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +502 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +181 -1
- package/dist/impl/browser/connect/index.d.ts +181 -1
- package/dist/impl/browser/connect/index.js +502 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/index.cjs +84 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +84 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../core/logger.ts","../../../../constants.ts","../../../../connect/protocol.ts","../../../../connect/permissions.ts","../../../../transport/websocket.ts","../../../../impl/nodejs/connect/WebSocketTransport.ts"],"sourcesContent":["/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * 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 * WebSocket Abstraction\n * Platform-independent WebSocket interface for cross-platform support\n */\n\n// =============================================================================\n// WebSocket Interface\n// =============================================================================\n\n/**\n * Minimal WebSocket interface compatible with browser and Node.js\n */\nexport interface IWebSocket {\n readonly readyState: number;\n\n send(data: string): void;\n close(code?: number, reason?: string): void;\n\n onopen: ((event: unknown) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onmessage: ((event: IMessageEvent) => void) | null;\n}\n\nexport interface IMessageEvent {\n data: string;\n}\n\n/**\n * WebSocket ready states (same as native WebSocket)\n */\nexport const WebSocketReadyState = {\n CONNECTING: 0,\n OPEN: 1,\n CLOSING: 2,\n CLOSED: 3,\n} as const;\n\n/**\n * Factory function to create WebSocket instances\n * Different implementations for browser (native) vs Node.js (ws package)\n */\nexport type WebSocketFactory = (url: string) => IWebSocket;\n\n// =============================================================================\n// UUID Generator\n// =============================================================================\n\n/**\n * Generate a unique ID (platform-independent)\n * Browser: crypto.randomUUID()\n * Node: crypto.randomUUID() or uuid package\n */\nexport type UUIDGenerator = () => string;\n\n/**\n * Default UUID generator using crypto.randomUUID\n * Works in modern browsers and Node 19+\n */\nexport function defaultUUIDGenerator(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","/**\n * WebSocketTransport — Node.js transport for Sphere Connect.\n *\n * Two modes:\n * - Server: wallet runs a WS server, dApps connect to it\n * - Client: dApp connects to wallet's WS server\n *\n * Uses the existing IWebSocket/WebSocketFactory abstraction from transport/websocket.ts.\n */\n\nimport type { ConnectTransport, SphereConnectMessage } from '../../../connect';\nimport { isSphereConnectMessage } from '../../../connect';\nimport type { IWebSocket, WebSocketFactory } from '../../../transport/websocket';\nimport { WebSocketReadyState } from '../../../transport/websocket';\nimport { logger } from '../../../core/logger';\n\n// =============================================================================\n// Configuration\n// =============================================================================\n\nexport interface WebSocketServerConfig {\n /** Port to listen on */\n port: number;\n /** Host to bind to. Default: '0.0.0.0' */\n host?: string;\n}\n\nexport interface WebSocketClientConfig {\n /** WebSocket URL to connect to (e.g., 'ws://localhost:8765') */\n url: string;\n /** Factory for creating WebSocket instances */\n createWebSocket: WebSocketFactory;\n /** Reconnect on disconnect. Default: true */\n autoReconnect?: boolean;\n /** Initial reconnect delay in ms. Default: 2000 */\n reconnectDelayMs?: number;\n /** Max reconnect delay in ms. Default: 30000 */\n maxReconnectDelayMs?: number;\n /** Max reconnect attempts. Default: 10. 0 = unlimited */\n maxReconnectAttempts?: number;\n}\n\n// =============================================================================\n// Server Transport (wallet side)\n// =============================================================================\n\nexport class WebSocketServerTransport implements ConnectTransport {\n private server: unknown = null; // WebSocketServer from 'ws' package\n private clientSocket: IWebSocket | null = null;\n private handlers: Set<(message: SphereConnectMessage) => void> = new Set();\n private config: WebSocketServerConfig;\n\n constructor(config: WebSocketServerConfig) {\n this.config = config;\n }\n\n /** Start the WebSocket server. Must be called before use. */\n async start(): Promise<void> {\n // Dynamic import to avoid bundling ws in browser builds\n const { WebSocketServer } = await import('ws');\n const wss = new WebSocketServer({\n port: this.config.port,\n host: this.config.host ?? '0.0.0.0',\n });\n\n this.server = wss;\n\n wss.on('connection', (ws: IWebSocket) => {\n // Accept only one client at a time\n if (this.clientSocket) {\n ws.close(4000, 'Another client is already connected');\n return;\n }\n\n this.clientSocket = ws;\n\n ws.onmessage = (event: { data: string }) => {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : String(event.data));\n if (isSphereConnectMessage(msg)) {\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch (err) {\n logger.debug('WebSocket', 'Message handler error', err);\n }\n }\n }\n } catch (err) {\n logger.debug('WebSocket', 'Malformed message received', err);\n }\n };\n\n ws.onclose = () => {\n if (this.clientSocket === ws) {\n this.clientSocket = null;\n }\n };\n });\n\n // Wait for server to be listening\n await new Promise<void>((resolve, reject) => {\n wss.on('listening', resolve);\n wss.on('error', reject);\n });\n }\n\n send(message: SphereConnectMessage): void {\n if (this.clientSocket && this.clientSocket.readyState === WebSocketReadyState.OPEN) {\n this.clientSocket.send(JSON.stringify(message));\n }\n }\n\n onMessage(handler: (message: SphereConnectMessage) => void): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n destroy(): void {\n if (this.clientSocket) {\n this.clientSocket.close();\n this.clientSocket = null;\n }\n if (this.server) {\n (this.server as { close: () => void }).close();\n this.server = null;\n }\n this.handlers.clear();\n }\n}\n\n// =============================================================================\n// Client Transport (dApp side)\n// =============================================================================\n\nexport class WebSocketClientTransport implements ConnectTransport {\n private ws: IWebSocket | null = null;\n private handlers: Set<(message: SphereConnectMessage) => void> = new Set();\n private config: WebSocketClientConfig;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n constructor(config: WebSocketClientConfig) {\n this.config = {\n autoReconnect: true,\n reconnectDelayMs: 2000,\n maxReconnectDelayMs: 30000,\n maxReconnectAttempts: 10,\n ...config,\n };\n }\n\n /** Connect to the WebSocket server. Must be called before use. */\n async connect(): Promise<void> {\n return this.doConnect();\n }\n\n send(message: SphereConnectMessage): void {\n if (this.ws && this.ws.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify(message));\n }\n }\n\n onMessage(handler: (message: SphereConnectMessage) => void): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.handlers.clear();\n }\n\n // ===========================================================================\n // Private\n // ===========================================================================\n\n private doConnect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n try {\n this.ws = this.config.createWebSocket(this.config.url);\n } catch (err) {\n reject(err);\n return;\n }\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0;\n resolve();\n };\n\n this.ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data);\n if (isSphereConnectMessage(msg)) {\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch (err) {\n logger.debug('WebSocket', 'Message handler error', err);\n }\n }\n }\n } catch (err) {\n logger.debug('WebSocket', 'Malformed message received', err);\n }\n };\n\n this.ws.onerror = (err) => {\n reject(err);\n };\n\n this.ws.onclose = () => {\n this.ws = null;\n if (!this.destroyed && this.config.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n });\n }\n\n private scheduleReconnect(): void {\n const maxAttempts = this.config.maxReconnectAttempts!;\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {\n return;\n }\n\n this.reconnectAttempts++;\n const baseDelay = this.config.reconnectDelayMs!;\n const maxDelay = this.config.maxReconnectDelayMs!;\n const delay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempts - 1), maxDelay);\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.doConnect().catch((err) => logger.debug('WebSocket', 'Reconnect attempt failed', err));\n }, delay);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\nexport const WebSocketTransport = {\n /** Create a WebSocket server transport (wallet side) */\n createServer(config: WebSocketServerConfig): WebSocketServerTransport {\n return new WebSocketServerTransport(config);\n },\n\n /** Create a WebSocket client transport (dApp side) */\n createClient(config: WebSocketClientConfig): WebSocketClientTransport {\n return new WebSocketClientTransport(config);\n },\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../core/logger.ts","../../../../constants.ts","../../../../connect/protocol.ts","../../../../connect/permissions.ts","../../../../transport/websocket.ts","../../../../impl/nodejs/connect/WebSocketTransport.ts"],"sourcesContent":["/**\n * Centralized SDK Logger\n *\n * A lightweight singleton logger that works across all tsup bundles\n * by storing state on globalThis. Supports three log levels:\n * - debug: detailed messages (only shown when debug=true)\n * - warn: important warnings (ALWAYS shown regardless of debug flag)\n * - error: critical errors (ALWAYS shown regardless of debug flag)\n *\n * Global debug flag enables all logging. Per-tag overrides allow\n * granular control (e.g., only transport debug).\n *\n * @example\n * ```ts\n * import { logger } from '@unicitylabs/sphere-sdk';\n *\n * // Enable all debug logging\n * logger.configure({ debug: true });\n *\n * // Enable only specific tags\n * logger.setTagDebug('Nostr', true);\n *\n * // Usage in SDK classes\n * logger.debug('Payments', 'Transfer started', { amount, recipient });\n * logger.warn('Nostr', 'queryEvents timed out after 5s');\n * logger.error('Sphere', 'Critical failure', error);\n * ```\n */\n\nexport type LogLevel = 'debug' | 'warn' | 'error';\n\nexport type LogHandler = (level: LogLevel, tag: string, message: string, ...args: unknown[]) => void;\n\nexport interface LoggerConfig {\n /** Enable debug logging globally (default: false). When false, only warn and error messages are shown. */\n debug?: boolean;\n /** Custom log handler. If provided, replaces console output. Useful for tests or custom log sinks. */\n handler?: LogHandler | null;\n}\n\n// Use a unique symbol-like key on globalThis to share logger state across tsup bundles\nconst LOGGER_KEY = '__sphere_sdk_logger__';\n\ninterface LoggerState {\n debug: boolean;\n tags: Record<string, boolean>;\n handler: LogHandler | null;\n}\n\nfunction getState(): LoggerState {\n const g = globalThis as unknown as Record<string, unknown>;\n if (!g[LOGGER_KEY]) {\n g[LOGGER_KEY] = { debug: false, tags: {}, handler: null } satisfies LoggerState;\n }\n return g[LOGGER_KEY] as LoggerState;\n}\n\nfunction isEnabled(tag: string): boolean {\n const state = getState();\n // Per-tag override takes priority\n if (tag in state.tags) return state.tags[tag];\n // Fall back to global flag\n return state.debug;\n}\n\nexport const logger = {\n /**\n * Configure the logger. Can be called multiple times (last write wins).\n * Typically called by createBrowserProviders(), createNodeProviders(), or Sphere.init().\n */\n configure(config: LoggerConfig): void {\n const state = getState();\n if (config.debug !== undefined) state.debug = config.debug;\n if (config.handler !== undefined) state.handler = config.handler;\n },\n\n /**\n * Enable/disable debug logging for a specific tag.\n * Per-tag setting overrides the global debug flag.\n *\n * @example\n * ```ts\n * logger.setTagDebug('Nostr', true); // enable only Nostr logs\n * logger.setTagDebug('Nostr', false); // disable Nostr logs even if global debug=true\n * ```\n */\n setTagDebug(tag: string, enabled: boolean): void {\n getState().tags[tag] = enabled;\n },\n\n /**\n * Clear per-tag override, falling back to global debug flag.\n */\n clearTagDebug(tag: string): void {\n delete getState().tags[tag];\n },\n\n /** Returns true if debug mode is enabled for the given tag (or globally). */\n isDebugEnabled(tag?: string): boolean {\n if (tag) return isEnabled(tag);\n return getState().debug;\n },\n\n /**\n * Debug-level log. Only shown when debug is enabled (globally or for this tag).\n * Use for detailed operational information.\n */\n debug(tag: string, message: string, ...args: unknown[]): void {\n if (!isEnabled(tag)) return;\n const state = getState();\n if (state.handler) {\n state.handler('debug', tag, message, ...args);\n } else {\n console.log(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Warning-level log. ALWAYS shown regardless of debug flag.\n * Use for important but non-critical issues (timeouts, retries, degraded state).\n */\n warn(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('warn', tag, message, ...args);\n } else {\n console.warn(`[${tag}]`, message, ...args);\n }\n },\n\n /**\n * Error-level log. ALWAYS shown regardless of debug flag.\n * Use for critical failures that should never be silenced.\n */\n error(tag: string, message: string, ...args: unknown[]): void {\n const state = getState();\n if (state.handler) {\n state.handler('error', tag, message, ...args);\n } else {\n console.error(`[${tag}]`, message, ...args);\n }\n },\n\n /** Reset all logger state (debug flag, tags, handler). Primarily for tests. */\n reset(): void {\n const g = globalThis as unknown as Record<string, unknown>;\n delete g[LOGGER_KEY];\n },\n};\n","/**\n * 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// Wallet-initiated Events (pushed automatically by host, no subscription needed)\n// =============================================================================\n\n/**\n * Events that ConnectHost pushes proactively to connected dApps.\n * dApps can listen with client.on(WALLET_EVENTS.LOCKED, handler) etc.\n * No sphere_subscribe call needed — host sends these unconditionally.\n */\nexport const WALLET_EVENTS = {\n /** Wallet locked or user logged out. dApp shows locked state and waits for unlock.\n * Pushed automatically by ConnectHost — no sphere_subscribe needed. */\n LOCKED: 'wallet:locked',\n /** Active wallet address changed. dApp should update displayed identity.\n * Pushed automatically by ConnectHost — no sphere_subscribe needed. */\n IDENTITY_CHANGED: 'identity:changed',\n} as const;\n\nexport type WalletEvent = (typeof WALLET_EVENTS)[keyof typeof WALLET_EVENTS];\n\n// =============================================================================\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 * WebSocket Abstraction\n * Platform-independent WebSocket interface for cross-platform support\n */\n\n// =============================================================================\n// WebSocket Interface\n// =============================================================================\n\n/**\n * Minimal WebSocket interface compatible with browser and Node.js\n */\nexport interface IWebSocket {\n readonly readyState: number;\n\n send(data: string): void;\n close(code?: number, reason?: string): void;\n\n onopen: ((event: unknown) => void) | null;\n onclose: ((event: unknown) => void) | null;\n onerror: ((event: unknown) => void) | null;\n onmessage: ((event: IMessageEvent) => void) | null;\n}\n\nexport interface IMessageEvent {\n data: string;\n}\n\n/**\n * WebSocket ready states (same as native WebSocket)\n */\nexport const WebSocketReadyState = {\n CONNECTING: 0,\n OPEN: 1,\n CLOSING: 2,\n CLOSED: 3,\n} as const;\n\n/**\n * Factory function to create WebSocket instances\n * Different implementations for browser (native) vs Node.js (ws package)\n */\nexport type WebSocketFactory = (url: string) => IWebSocket;\n\n// =============================================================================\n// UUID Generator\n// =============================================================================\n\n/**\n * Generate a unique ID (platform-independent)\n * Browser: crypto.randomUUID()\n * Node: crypto.randomUUID() or uuid package\n */\nexport type UUIDGenerator = () => string;\n\n/**\n * Default UUID generator using crypto.randomUUID\n * Works in modern browsers and Node 19+\n */\nexport function defaultUUIDGenerator(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === 'x' ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n","/**\n * WebSocketTransport — Node.js transport for Sphere Connect.\n *\n * Two modes:\n * - Server: wallet runs a WS server, dApps connect to it\n * - Client: dApp connects to wallet's WS server\n *\n * Uses the existing IWebSocket/WebSocketFactory abstraction from transport/websocket.ts.\n */\n\nimport type { ConnectTransport, SphereConnectMessage } from '../../../connect';\nimport { isSphereConnectMessage } from '../../../connect';\nimport type { IWebSocket, WebSocketFactory } from '../../../transport/websocket';\nimport { WebSocketReadyState } from '../../../transport/websocket';\nimport { logger } from '../../../core/logger';\n\n// =============================================================================\n// Configuration\n// =============================================================================\n\nexport interface WebSocketServerConfig {\n /** Port to listen on */\n port: number;\n /** Host to bind to. Default: '0.0.0.0' */\n host?: string;\n}\n\nexport interface WebSocketClientConfig {\n /** WebSocket URL to connect to (e.g., 'ws://localhost:8765') */\n url: string;\n /** Factory for creating WebSocket instances */\n createWebSocket: WebSocketFactory;\n /** Reconnect on disconnect. Default: true */\n autoReconnect?: boolean;\n /** Initial reconnect delay in ms. Default: 2000 */\n reconnectDelayMs?: number;\n /** Max reconnect delay in ms. Default: 30000 */\n maxReconnectDelayMs?: number;\n /** Max reconnect attempts. Default: 10. 0 = unlimited */\n maxReconnectAttempts?: number;\n}\n\n// =============================================================================\n// Server Transport (wallet side)\n// =============================================================================\n\nexport class WebSocketServerTransport implements ConnectTransport {\n private server: unknown = null; // WebSocketServer from 'ws' package\n private clientSocket: IWebSocket | null = null;\n private handlers: Set<(message: SphereConnectMessage) => void> = new Set();\n private config: WebSocketServerConfig;\n\n constructor(config: WebSocketServerConfig) {\n this.config = config;\n }\n\n /** Start the WebSocket server. Must be called before use. */\n async start(): Promise<void> {\n // Dynamic import to avoid bundling ws in browser builds\n const { WebSocketServer } = await import('ws');\n const wss = new WebSocketServer({\n port: this.config.port,\n host: this.config.host ?? '0.0.0.0',\n });\n\n this.server = wss;\n\n wss.on('connection', (ws: IWebSocket) => {\n // Accept only one client at a time\n if (this.clientSocket) {\n ws.close(4000, 'Another client is already connected');\n return;\n }\n\n this.clientSocket = ws;\n\n ws.onmessage = (event: { data: string }) => {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : String(event.data));\n if (isSphereConnectMessage(msg)) {\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch (err) {\n logger.debug('WebSocket', 'Message handler error', err);\n }\n }\n }\n } catch (err) {\n logger.debug('WebSocket', 'Malformed message received', err);\n }\n };\n\n ws.onclose = () => {\n if (this.clientSocket === ws) {\n this.clientSocket = null;\n }\n };\n });\n\n // Wait for server to be listening\n await new Promise<void>((resolve, reject) => {\n wss.on('listening', resolve);\n wss.on('error', reject);\n });\n }\n\n send(message: SphereConnectMessage): void {\n if (this.clientSocket && this.clientSocket.readyState === WebSocketReadyState.OPEN) {\n this.clientSocket.send(JSON.stringify(message));\n }\n }\n\n onMessage(handler: (message: SphereConnectMessage) => void): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n destroy(): void {\n if (this.clientSocket) {\n this.clientSocket.close();\n this.clientSocket = null;\n }\n if (this.server) {\n (this.server as { close: () => void }).close();\n this.server = null;\n }\n this.handlers.clear();\n }\n}\n\n// =============================================================================\n// Client Transport (dApp side)\n// =============================================================================\n\nexport class WebSocketClientTransport implements ConnectTransport {\n private ws: IWebSocket | null = null;\n private handlers: Set<(message: SphereConnectMessage) => void> = new Set();\n private config: WebSocketClientConfig;\n private reconnectAttempts = 0;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private destroyed = false;\n\n constructor(config: WebSocketClientConfig) {\n this.config = {\n autoReconnect: true,\n reconnectDelayMs: 2000,\n maxReconnectDelayMs: 30000,\n maxReconnectAttempts: 10,\n ...config,\n };\n }\n\n /** Connect to the WebSocket server. Must be called before use. */\n async connect(): Promise<void> {\n return this.doConnect();\n }\n\n send(message: SphereConnectMessage): void {\n if (this.ws && this.ws.readyState === WebSocketReadyState.OPEN) {\n this.ws.send(JSON.stringify(message));\n }\n }\n\n onMessage(handler: (message: SphereConnectMessage) => void): () => void {\n this.handlers.add(handler);\n return () => {\n this.handlers.delete(handler);\n };\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n this.handlers.clear();\n }\n\n // ===========================================================================\n // Private\n // ===========================================================================\n\n private doConnect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n try {\n this.ws = this.config.createWebSocket(this.config.url);\n } catch (err) {\n reject(err);\n return;\n }\n\n this.ws.onopen = () => {\n this.reconnectAttempts = 0;\n resolve();\n };\n\n this.ws.onmessage = (event) => {\n try {\n const msg = JSON.parse(event.data);\n if (isSphereConnectMessage(msg)) {\n for (const handler of this.handlers) {\n try {\n handler(msg);\n } catch (err) {\n logger.debug('WebSocket', 'Message handler error', err);\n }\n }\n }\n } catch (err) {\n logger.debug('WebSocket', 'Malformed message received', err);\n }\n };\n\n this.ws.onerror = (err) => {\n reject(err);\n };\n\n this.ws.onclose = () => {\n this.ws = null;\n if (!this.destroyed && this.config.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n });\n }\n\n private scheduleReconnect(): void {\n const maxAttempts = this.config.maxReconnectAttempts!;\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {\n return;\n }\n\n this.reconnectAttempts++;\n const baseDelay = this.config.reconnectDelayMs!;\n const maxDelay = this.config.maxReconnectDelayMs!;\n const delay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempts - 1), maxDelay);\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.doConnect().catch((err) => logger.debug('WebSocket', 'Reconnect attempt failed', err));\n }, delay);\n }\n}\n\n// =============================================================================\n// Factory Functions\n// =============================================================================\n\nexport const WebSocketTransport = {\n /** Create a WebSocket server transport (wallet side) */\n createServer(config: WebSocketServerConfig): WebSocketServerTransport {\n return new WebSocketServerTransport(config);\n },\n\n /** Create a WebSocket client transport (dApp side) */\n createClient(config: WebSocketClientConfig): WebSocketClientTransport {\n return new WebSocketClientTransport(config);\n },\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -6619,7 +6619,7 @@ function normalizeToHex(value) {
|
|
|
6619
6619
|
return String(value);
|
|
6620
6620
|
}
|
|
6621
6621
|
function normalizeSdkTokenToStorage(sdkTokenJson) {
|
|
6622
|
-
const txf =
|
|
6622
|
+
const txf = structuredClone(sdkTokenJson);
|
|
6623
6623
|
if (txf.genesis?.data) {
|
|
6624
6624
|
const data = txf.genesis.data;
|
|
6625
6625
|
if (data.tokenId !== void 0) {
|
|
@@ -7910,35 +7910,45 @@ async function parseTokenInfo(tokenData) {
|
|
|
7910
7910
|
}
|
|
7911
7911
|
return defaultInfo;
|
|
7912
7912
|
}
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
}
|
|
7921
|
-
}
|
|
7922
|
-
function extractStateHashFromSdkData(sdkData) {
|
|
7923
|
-
if (!sdkData) return "";
|
|
7913
|
+
var sdkDataCache = /* @__PURE__ */ new Map();
|
|
7914
|
+
var SDK_DATA_CACHE_MAX = 2e3;
|
|
7915
|
+
function parseSdkDataCached(sdkData) {
|
|
7916
|
+
const cached = sdkDataCache.get(sdkData);
|
|
7917
|
+
if (cached) return cached;
|
|
7918
|
+
let tokenId = null;
|
|
7919
|
+
let stateHash = "";
|
|
7924
7920
|
try {
|
|
7925
7921
|
const txf = JSON.parse(sdkData);
|
|
7926
|
-
|
|
7922
|
+
tokenId = txf.genesis?.data?.tokenId || null;
|
|
7923
|
+
stateHash = getCurrentStateHash(txf) || "";
|
|
7927
7924
|
if (!stateHash) {
|
|
7928
7925
|
if (txf.state?.hash) {
|
|
7929
|
-
|
|
7930
|
-
}
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
if (txf.currentStateHash) {
|
|
7935
|
-
return txf.currentStateHash;
|
|
7926
|
+
stateHash = txf.state.hash;
|
|
7927
|
+
} else if (txf.stateHash) {
|
|
7928
|
+
stateHash = txf.stateHash;
|
|
7929
|
+
} else if (txf.currentStateHash) {
|
|
7930
|
+
stateHash = txf.currentStateHash;
|
|
7936
7931
|
}
|
|
7937
7932
|
}
|
|
7938
|
-
return stateHash || "";
|
|
7939
7933
|
} catch {
|
|
7940
|
-
return "";
|
|
7941
7934
|
}
|
|
7935
|
+
const entry = { tokenId, stateHash };
|
|
7936
|
+
if (sdkDataCache.size >= SDK_DATA_CACHE_MAX) {
|
|
7937
|
+
sdkDataCache.clear();
|
|
7938
|
+
}
|
|
7939
|
+
sdkDataCache.set(sdkData, entry);
|
|
7940
|
+
return entry;
|
|
7941
|
+
}
|
|
7942
|
+
function clearSdkDataCache() {
|
|
7943
|
+
sdkDataCache.clear();
|
|
7944
|
+
}
|
|
7945
|
+
function extractTokenIdFromSdkData(sdkData) {
|
|
7946
|
+
if (!sdkData) return null;
|
|
7947
|
+
return parseSdkDataCached(sdkData).tokenId;
|
|
7948
|
+
}
|
|
7949
|
+
function extractStateHashFromSdkData(sdkData) {
|
|
7950
|
+
if (!sdkData) return "";
|
|
7951
|
+
return parseSdkDataCached(sdkData).stateHash;
|
|
7942
7952
|
}
|
|
7943
7953
|
function createTokenStateKey(tokenId, stateHash) {
|
|
7944
7954
|
return `${tokenId}_${stateHash}`;
|
|
@@ -8048,6 +8058,8 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
8048
8058
|
pendingBackgroundTasks = [];
|
|
8049
8059
|
// Repository State (tombstones, archives, forked, history)
|
|
8050
8060
|
tombstones = [];
|
|
8061
|
+
// O(1) lookup set derived from tombstones array. Rebuilt via rebuildTombstoneKeySet().
|
|
8062
|
+
tombstoneKeySet = /* @__PURE__ */ new Set();
|
|
8051
8063
|
archivedTokens = /* @__PURE__ */ new Map();
|
|
8052
8064
|
forkedTokens = /* @__PURE__ */ new Map();
|
|
8053
8065
|
_historyCache = [];
|
|
@@ -8132,8 +8144,10 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
8132
8144
|
}
|
|
8133
8145
|
this.pendingResponseResolvers.clear();
|
|
8134
8146
|
this.tokens.clear();
|
|
8147
|
+
clearSdkDataCache();
|
|
8135
8148
|
this.pendingTransfers.clear();
|
|
8136
8149
|
this.tombstones = [];
|
|
8150
|
+
this.tombstoneKeySet.clear();
|
|
8137
8151
|
this.archivedTokens.clear();
|
|
8138
8152
|
this.forkedTokens.clear();
|
|
8139
8153
|
this._historyCache = [];
|
|
@@ -10254,11 +10268,10 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10254
10268
|
await this.archiveToken(token);
|
|
10255
10269
|
const tombstone = createTombstoneFromToken(token);
|
|
10256
10270
|
if (tombstone) {
|
|
10257
|
-
const
|
|
10258
|
-
|
|
10259
|
-
);
|
|
10260
|
-
if (!alreadyTombstoned) {
|
|
10271
|
+
const key = `${tombstone.tokenId}:${tombstone.stateHash}`;
|
|
10272
|
+
if (!this.tombstoneKeySet.has(key)) {
|
|
10261
10273
|
this.tombstones.push(tombstone);
|
|
10274
|
+
this.tombstoneKeySet.add(key);
|
|
10262
10275
|
logger.debug("Payments", `Created tombstone for ${tombstone.tokenId.slice(0, 8)}..._${tombstone.stateHash.slice(0, 8)}...`);
|
|
10263
10276
|
}
|
|
10264
10277
|
} else {
|
|
@@ -10283,15 +10296,20 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10283
10296
|
}
|
|
10284
10297
|
/**
|
|
10285
10298
|
* Check whether a specific `(tokenId, stateHash)` combination is tombstoned.
|
|
10299
|
+
* Uses O(1) Set lookup instead of O(n) linear scan.
|
|
10286
10300
|
*
|
|
10287
10301
|
* @param tokenId - The genesis token ID.
|
|
10288
10302
|
* @param stateHash - The state hash of the token version to check.
|
|
10289
10303
|
* @returns `true` if the exact combination has been tombstoned.
|
|
10290
10304
|
*/
|
|
10291
10305
|
isStateTombstoned(tokenId, stateHash) {
|
|
10292
|
-
return this.
|
|
10293
|
-
|
|
10294
|
-
|
|
10306
|
+
return this.tombstoneKeySet.has(`${tokenId}:${stateHash}`);
|
|
10307
|
+
}
|
|
10308
|
+
rebuildTombstoneKeySet() {
|
|
10309
|
+
this.tombstoneKeySet.clear();
|
|
10310
|
+
for (const t of this.tombstones) {
|
|
10311
|
+
this.tombstoneKeySet.add(`${t.tokenId}:${t.stateHash}`);
|
|
10312
|
+
}
|
|
10295
10313
|
}
|
|
10296
10314
|
/**
|
|
10297
10315
|
* Merge tombstones received from a remote sync source.
|
|
@@ -10323,11 +10341,10 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10323
10341
|
removedCount++;
|
|
10324
10342
|
}
|
|
10325
10343
|
for (const remoteTombstone of remoteTombstones) {
|
|
10326
|
-
const
|
|
10327
|
-
|
|
10328
|
-
);
|
|
10329
|
-
if (!alreadyExists) {
|
|
10344
|
+
const key = `${remoteTombstone.tokenId}:${remoteTombstone.stateHash}`;
|
|
10345
|
+
if (!this.tombstoneKeySet.has(key)) {
|
|
10330
10346
|
this.tombstones.push(remoteTombstone);
|
|
10347
|
+
this.tombstoneKeySet.add(key);
|
|
10331
10348
|
}
|
|
10332
10349
|
}
|
|
10333
10350
|
if (removedCount > 0) {
|
|
@@ -10343,6 +10360,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10343
10360
|
async pruneTombstones(maxAge) {
|
|
10344
10361
|
const originalCount = this.tombstones.length;
|
|
10345
10362
|
this.tombstones = pruneTombstonesByAge(this.tombstones, maxAge);
|
|
10363
|
+
this.rebuildTombstoneKeySet();
|
|
10346
10364
|
if (this.tombstones.length < originalCount) {
|
|
10347
10365
|
await this.save();
|
|
10348
10366
|
logger.debug("Payments", `Pruned tombstones from ${originalCount} to ${this.tombstones.length}`);
|
|
@@ -10829,6 +10847,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10829
10847
|
}
|
|
10830
10848
|
const savedTokens = new Map(this.tokens);
|
|
10831
10849
|
this.loadFromStorageData(result.merged);
|
|
10850
|
+
const existingGenesisIds = /* @__PURE__ */ new Set();
|
|
10851
|
+
for (const existing of this.tokens.values()) {
|
|
10852
|
+
const gid = extractTokenIdFromSdkData(existing.sdkData);
|
|
10853
|
+
if (gid) existingGenesisIds.add(gid);
|
|
10854
|
+
}
|
|
10832
10855
|
let restoredCount = 0;
|
|
10833
10856
|
for (const [tokenId, token] of savedTokens) {
|
|
10834
10857
|
if (this.tokens.has(tokenId)) continue;
|
|
@@ -10837,17 +10860,11 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
10837
10860
|
if (sdkTokenId && stateHash && this.isStateTombstoned(sdkTokenId, stateHash)) {
|
|
10838
10861
|
continue;
|
|
10839
10862
|
}
|
|
10840
|
-
if (sdkTokenId) {
|
|
10841
|
-
|
|
10842
|
-
for (const existing of this.tokens.values()) {
|
|
10843
|
-
if (extractTokenIdFromSdkData(existing.sdkData) === sdkTokenId) {
|
|
10844
|
-
hasEquivalent = true;
|
|
10845
|
-
break;
|
|
10846
|
-
}
|
|
10847
|
-
}
|
|
10848
|
-
if (hasEquivalent) continue;
|
|
10863
|
+
if (sdkTokenId && existingGenesisIds.has(sdkTokenId)) {
|
|
10864
|
+
continue;
|
|
10849
10865
|
}
|
|
10850
10866
|
this.tokens.set(tokenId, token);
|
|
10867
|
+
if (sdkTokenId) existingGenesisIds.add(sdkTokenId);
|
|
10851
10868
|
restoredCount++;
|
|
10852
10869
|
}
|
|
10853
10870
|
if (restoredCount > 0) {
|
|
@@ -11594,6 +11611,7 @@ var PaymentsModule = class _PaymentsModule {
|
|
|
11594
11611
|
const parsed = parseTxfStorageData(data);
|
|
11595
11612
|
logger.debug("Payments", `loadFromStorageData: parsed ${parsed.tokens.length} tokens, ${parsed.tombstones.length} tombstones, errors=[${parsed.validationErrors.join("; ")}]`);
|
|
11596
11613
|
this.tombstones = parsed.tombstones;
|
|
11614
|
+
this.rebuildTombstoneKeySet();
|
|
11597
11615
|
this.tokens.clear();
|
|
11598
11616
|
for (const token of parsed.tokens) {
|
|
11599
11617
|
const sdkTokenId = extractTokenIdFromSdkData(token.sdkData);
|
|
@@ -18149,9 +18167,16 @@ var Sphere = class _Sphere {
|
|
|
18149
18167
|
emitEvent
|
|
18150
18168
|
});
|
|
18151
18169
|
await payments.load();
|
|
18152
|
-
await
|
|
18153
|
-
|
|
18154
|
-
|
|
18170
|
+
const results = await Promise.allSettled([
|
|
18171
|
+
communications.load(),
|
|
18172
|
+
groupChat?.load(),
|
|
18173
|
+
market?.load()
|
|
18174
|
+
]);
|
|
18175
|
+
for (const r of results) {
|
|
18176
|
+
if (r.status === "rejected") {
|
|
18177
|
+
logger.warn("Sphere", "Module load failed:", r.reason);
|
|
18178
|
+
}
|
|
18179
|
+
}
|
|
18155
18180
|
const moduleSet = {
|
|
18156
18181
|
index,
|
|
18157
18182
|
identity,
|
|
@@ -19366,9 +19391,9 @@ var Sphere = class _Sphere {
|
|
|
19366
19391
|
await this._transport.connect();
|
|
19367
19392
|
}
|
|
19368
19393
|
await this._oracle.initialize();
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19394
|
+
await Promise.all(
|
|
19395
|
+
[...this._tokenStorageProviders.values()].map((p) => p.initialize())
|
|
19396
|
+
);
|
|
19372
19397
|
this.subscribeToProviderEvents();
|
|
19373
19398
|
}
|
|
19374
19399
|
/**
|
|
@@ -19474,10 +19499,17 @@ var Sphere = class _Sphere {
|
|
|
19474
19499
|
identity: this._identity,
|
|
19475
19500
|
emitEvent
|
|
19476
19501
|
});
|
|
19477
|
-
await
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
|
|
19502
|
+
const results = await Promise.allSettled([
|
|
19503
|
+
this._payments.load(),
|
|
19504
|
+
this._communications.load(),
|
|
19505
|
+
this._groupChat?.load(),
|
|
19506
|
+
this._market?.load()
|
|
19507
|
+
]);
|
|
19508
|
+
for (const r of results) {
|
|
19509
|
+
if (r.status === "rejected") {
|
|
19510
|
+
logger.warn("Sphere", "Module load failed:", r.reason);
|
|
19511
|
+
}
|
|
19512
|
+
}
|
|
19481
19513
|
this._addressModules.set(this._currentAddressIndex, {
|
|
19482
19514
|
index: this._currentAddressIndex,
|
|
19483
19515
|
identity: this._identity,
|