UNPKG

@walletconnect/core

Version:
1 lines 231 kB
{"version":3,"file":"index.cjs","sources":["../src/constants/core.ts","../src/constants/crypto.ts","../src/constants/keychain.ts","../src/constants/messages.ts","../src/constants/publisher.ts","../src/constants/relayer.ts","../src/constants/store.ts","../src/constants/subscriber.ts","../src/constants/pairing.ts","../src/constants/history.ts","../src/constants/expirer.ts","../src/constants/verify.ts","../src/constants/echo.ts","../src/constants/events.ts","../src/controllers/keychain.ts","../src/controllers/crypto.ts","../src/controllers/messages.ts","../src/controllers/publisher.ts","../src/controllers/topicmap.ts","../src/controllers/subscriber.ts","../src/controllers/relayer.ts","../src/controllers/store.ts","../src/controllers/pairing.ts","../src/controllers/history.ts","../src/controllers/expirer.ts","../src/controllers/verify.ts","../src/controllers/echo.ts","../src/controllers/events.ts","../src/core.ts","../src/index.ts"],"sourcesContent":["export const CORE_PROTOCOL = \"wc\";\nexport const CORE_VERSION = 2;\nexport const CORE_CONTEXT = \"core\";\n\nexport const CORE_STORAGE_PREFIX = `${CORE_PROTOCOL}@${CORE_VERSION}:${CORE_CONTEXT}:`;\n\nexport const CORE_DEFAULT = {\n name: CORE_CONTEXT,\n logger: \"error\",\n};\n\nexport const CORE_STORAGE_OPTIONS = {\n database: \":memory:\",\n};\n","import { ONE_DAY } from \"@walletconnect/time\";\n\nexport const CRYPTO_CONTEXT = \"crypto\";\n\nexport const CRYPTO_CLIENT_SEED = \"client_ed25519_seed\";\n\nexport const CRYPTO_JWT_TTL = ONE_DAY;\n","export const KEYCHAIN_CONTEXT = \"keychain\";\n\nexport const KEYCHAIN_STORAGE_VERSION = \"0.3\";\n","export const MESSAGES_CONTEXT = \"messages\";\n\nexport const MESSAGES_STORAGE_VERSION = \"0.3\";\n","import { SIX_HOURS } from \"@walletconnect/time\";\n\nexport const PUBLISHER_DEFAULT_TTL = SIX_HOURS;\n\nexport const PUBLISHER_CONTEXT = \"publisher\";\n","export const RELAYER_DEFAULT_PROTOCOL = \"irn\";\n\nexport const RELAYER_DEFAULT_LOGGER = \"error\";\n\nexport const RELAYER_DEFAULT_RELAY_URL = \"wss://relay.walletconnect.org\";\n\nexport const RELAYER_CONTEXT = \"relayer\";\n\nexport const RELAYER_EVENTS = {\n message: \"relayer_message\",\n message_ack: \"relayer_message_ack\",\n connect: \"relayer_connect\",\n disconnect: \"relayer_disconnect\",\n error: \"relayer_error\",\n connection_stalled: \"relayer_connection_stalled\",\n transport_closed: \"relayer_transport_closed\",\n publish: \"relayer_publish\",\n};\n\nexport const RELAYER_SUBSCRIBER_SUFFIX = \"_subscription\";\n\nexport const RELAYER_PROVIDER_EVENTS = {\n payload: \"payload\",\n connect: \"connect\",\n disconnect: \"disconnect\",\n error: \"error\",\n};\n\nexport const RELAYER_RECONNECT_TIMEOUT = 0.1;\n\nexport const RELAYER_STORAGE_OPTIONS = {\n database: \":memory:\",\n};\n\n// Updated automatically via `new-version` npm script.\n\nexport const RELAYER_SDK_VERSION = \"2.23.9\";\n\n// delay to wait before closing the transport connection after init if not active\nexport const RELAYER_TRANSPORT_CUTOFF = 10_000;\n\nexport const TRANSPORT_TYPES = {\n link_mode: \"link_mode\",\n relay: \"relay\",\n} as const;\n\nexport const MESSAGE_DIRECTION = {\n inbound: \"inbound\",\n outbound: \"outbound\",\n} as const;\n","export const STORE_STORAGE_VERSION = \"0.3\";\n\nexport const WALLETCONNECT_CLIENT_ID = \"WALLETCONNECT_CLIENT_ID\";\nexport const WALLETCONNECT_LINK_MODE_APPS = \"WALLETCONNECT_LINK_MODE_APPS\";\n","import { THIRTY_DAYS, FIVE_SECONDS } from \"@walletconnect/time\";\n\nexport const SUBSCRIBER_EVENTS = {\n created: \"subscription_created\",\n deleted: \"subscription_deleted\",\n expired: \"subscription_expired\",\n disabled: \"subscription_disabled\",\n sync: \"subscription_sync\",\n resubscribed: \"subscription_resubscribed\",\n};\n\nexport const SUBSCRIBER_DEFAULT_TTL = THIRTY_DAYS;\n\nexport const SUBSCRIBER_CONTEXT = \"subscription\";\n\nexport const SUBSCRIBER_STORAGE_VERSION = \"0.3\";\n\nexport const PENDING_SUB_RESOLUTION_TIMEOUT = FIVE_SECONDS * 1000;\n","import { THIRTY_DAYS, ONE_DAY, THIRTY_SECONDS } from \"@walletconnect/time\";\nimport { RelayerTypes, PairingJsonRpcTypes } from \"@walletconnect/types\";\n\nexport const PAIRING_CONTEXT = \"pairing\";\n\nexport const PAIRING_STORAGE_VERSION = \"0.3\";\n\nexport const PAIRING_DEFAULT_TTL = THIRTY_DAYS;\n\nexport const PAIRING_RPC_OPTS: Record<\n PairingJsonRpcTypes.WcMethod | \"unregistered_method\",\n {\n req: RelayerTypes.PublishOptions;\n res: RelayerTypes.PublishOptions;\n }\n> = {\n wc_pairingDelete: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1000,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1001,\n },\n },\n wc_pairingPing: {\n req: {\n ttl: THIRTY_SECONDS,\n prompt: false,\n tag: 1002,\n },\n res: {\n ttl: THIRTY_SECONDS,\n prompt: false,\n tag: 1003,\n },\n },\n unregistered_method: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 0,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 0,\n },\n },\n};\n\nexport const PAIRING_EVENTS = {\n create: \"pairing_create\",\n expire: \"pairing_expire\",\n delete: \"pairing_delete\",\n ping: \"pairing_ping\",\n};\n","export const HISTORY_EVENTS = {\n created: \"history_created\",\n updated: \"history_updated\",\n deleted: \"history_deleted\",\n sync: \"history_sync\",\n};\n\nexport const HISTORY_CONTEXT = \"history\";\n\nexport const HISTORY_STORAGE_VERSION = \"0.3\";\n","import { ONE_DAY } from \"@walletconnect/time\";\n\nexport const EXPIRER_CONTEXT = \"expirer\";\n\nexport const EXPIRER_EVENTS = {\n created: \"expirer_created\",\n deleted: \"expirer_deleted\",\n expired: \"expirer_expired\",\n sync: \"expirer_sync\",\n};\n\nexport const EXPIRER_STORAGE_VERSION = \"0.3\";\n\nexport const EXPIRER_DEFAULT_TTL = ONE_DAY;\n","export const VERIFY_CONTEXT = \"verify-api\";\n\nconst VERIFY_SERVER_COM = \"https://verify.walletconnect.com\";\nconst VERIFY_SERVER_ORG = \"https://verify.walletconnect.org\";\nexport const VERIFY_SERVER = VERIFY_SERVER_ORG;\nexport const VERIFY_SERVER_V3 = `${VERIFY_SERVER}/v3`;\n\nexport const TRUSTED_VERIFY_URLS = [VERIFY_SERVER_COM, VERIFY_SERVER_ORG];\n","export const ECHO_CONTEXT = \"echo\";\n\nexport const ECHO_URL = \"https://echo.walletconnect.com\";\n","export const EVENT_CLIENT_CONTEXT = \"event-client\";\n\nexport const EVENT_CLIENT_PAIRING_TRACES = {\n pairing_started: \"pairing_started\",\n pairing_uri_validation_success: \"pairing_uri_validation_success\",\n pairing_uri_not_expired: \"pairing_uri_not_expired\",\n store_new_pairing: \"store_new_pairing\",\n subscribing_pairing_topic: \"subscribing_pairing_topic\",\n subscribe_pairing_topic_success: \"subscribe_pairing_topic_success\",\n existing_pairing: \"existing_pairing\",\n pairing_not_expired: \"pairing_not_expired\",\n emit_inactive_pairing: \"emit_inactive_pairing\",\n emit_session_proposal: \"emit_session_proposal\",\n subscribing_to_pairing_topic: \"subscribing_to_pairing_topic\",\n};\n\nexport const EVENT_CLIENT_PAIRING_ERRORS = {\n no_wss_connection: \"no_wss_connection\",\n no_internet_connection: \"no_internet_connection\",\n malformed_pairing_uri: \"malformed_pairing_uri\",\n active_pairing_already_exists: \"active_pairing_already_exists\",\n subscribe_pairing_topic_failure: \"subscribe_pairing_topic_failure\",\n pairing_expired: \"pairing_expired\",\n proposal_expired: \"proposal_expired\",\n proposal_listener_not_found: \"proposal_listener_not_found\",\n};\n\nexport const EVENT_CLIENT_SESSION_TRACES = {\n session_approve_started: \"session_approve_started\",\n proposal_not_expired: \"proposal_not_expired\",\n session_namespaces_validation_success: \"session_namespaces_validation_success\",\n create_session_topic: \"create_session_topic\",\n subscribing_session_topic: \"subscribing_session_topic\",\n subscribe_session_topic_success: \"subscribe_session_topic_success\",\n publishing_session_approve: \"publishing_session_approve\",\n session_approve_publish_success: \"session_approve_publish_success\",\n store_session: \"store_session\",\n publishing_session_settle: \"publishing_session_settle\",\n session_settle_publish_success: \"session_settle_publish_success\",\n session_request_response_started: \"session_request_response_started\",\n session_request_response_validation_success: \"session_request_response_validation_success\",\n session_request_response_publish_started: \"session_request_response_publish_started\",\n};\n\nexport const EVENT_CLIENT_SESSION_ERRORS = {\n no_internet_connection: \"no_internet_connection\",\n no_wss_connection: \"no_wss_connection\",\n proposal_expired: \"proposal_expired\",\n subscribe_session_topic_failure: \"subscribe_session_topic_failure\",\n session_approve_publish_failure: \"session_approve_publish_failure\",\n session_settle_publish_failure: \"session_settle_publish_failure\",\n session_approve_namespace_validation_failure: \"session_approve_namespace_validation_failure\",\n proposal_not_found: \"proposal_not_found\",\n session_request_response_validation_failure: \"session_request_response_validation_failure\",\n session_request_response_publish_failure: \"session_request_response_publish_failure\",\n};\n\nexport const EVENT_CLIENT_AUTHENTICATE_TRACES = {\n authenticated_session_approve_started: \"authenticated_session_approve_started\",\n authenticated_session_not_expired: \"authenticated_session_not_expired\",\n chains_caip2_compliant: \"chains_caip2_compliant\",\n chains_evm_compliant: \"chains_evm_compliant\",\n create_authenticated_session_topic: \"create_authenticated_session_topic\",\n cacaos_verified: \"cacaos_verified\",\n store_authenticated_session: \"store_authenticated_session\",\n subscribing_authenticated_session_topic: \"subscribing_authenticated_session_topic\",\n subscribe_authenticated_session_topic_success: \"subscribe_authenticated_session_topic_success\",\n publishing_authenticated_session_approve: \"publishing_authenticated_session_approve\",\n authenticated_session_approve_publish_success: \"authenticated_session_approve_publish_success\",\n};\n\nexport const EVENT_CLIENT_AUTHENTICATE_ERRORS = {\n no_internet_connection: \"no_internet_connection\",\n no_wss_connection: \"no_wss_connection\",\n missing_session_authenticate_request: \"missing_session_authenticate_request\",\n session_authenticate_request_expired: \"session_authenticate_request_expired\",\n chains_caip2_compliant_failure: \"chains_caip2_compliant_failure\",\n chains_evm_compliant_failure: \"chains_evm_compliant_failure\",\n invalid_cacao: \"invalid_cacao\",\n subscribe_authenticated_session_topic_failure: \"subscribe_authenticated_session_topic_failure\",\n authenticated_session_approve_publish_failure: \"authenticated_session_approve_publish_failure\",\n authenticated_session_pending_request_not_found:\n \"authenticated_session_pending_request_not_found\",\n};\n\nexport const EVENTS_STORAGE_VERSION = 0.1;\n\nexport const EVENTS_STORAGE_CONTEXT = \"event-client\";\n\nexport const EVENTS_STORAGE_CLEANUP_INTERVAL = 86400;\n\nexport const EVENTS_CLIENT_API_URL = \"https://pulse.walletconnect.org/batch\";\n","import { generateChildLogger, getLoggerContext, Logger } from \"@walletconnect/logger\";\nimport { ICore, IKeyChain } from \"@walletconnect/types\";\nimport { getInternalError, mapToObj, objToMap } from \"@walletconnect/utils\";\n\nimport {\n CORE_STORAGE_PREFIX,\n KEYCHAIN_CONTEXT,\n KEYCHAIN_STORAGE_VERSION,\n} from \"../constants/index.js\";\n\nexport class KeyChain implements IKeyChain {\n public keychain = new Map<string, string>();\n public name = KEYCHAIN_CONTEXT;\n public version = KEYCHAIN_STORAGE_VERSION;\n\n private initialized = false;\n private storagePrefix = CORE_STORAGE_PREFIX;\n\n constructor(\n public core: ICore,\n public logger: Logger,\n ) {\n this.core = core;\n this.logger = generateChildLogger(logger, this.name);\n }\n\n public init: IKeyChain[\"init\"] = async () => {\n if (!this.initialized) {\n const keychain = await this.getKeyChain();\n if (typeof keychain !== \"undefined\") {\n this.keychain = keychain;\n }\n this.initialized = true;\n }\n };\n\n get context() {\n return getLoggerContext(this.logger);\n }\n\n get storageKey() {\n return this.storagePrefix + this.version + this.core.customStoragePrefix + \"//\" + this.name;\n }\n\n public has: IKeyChain[\"has\"] = (tag) => {\n this.isInitialized();\n return this.keychain.has(tag);\n };\n\n public set: IKeyChain[\"set\"] = async (tag, key) => {\n this.isInitialized();\n this.keychain.set(tag, key);\n await this.persist();\n };\n\n public get: IKeyChain[\"get\"] = (tag) => {\n this.isInitialized();\n const key = this.keychain.get(tag);\n if (typeof key === \"undefined\") {\n const { message } = getInternalError(\"NO_MATCHING_KEY\", `${this.name}: ${tag}`);\n throw new Error(message);\n }\n return key;\n };\n\n public del: IKeyChain[\"del\"] = async (tag) => {\n this.isInitialized();\n this.keychain.delete(tag);\n await this.persist();\n };\n\n // ---------- Private ----------------------------------------------- //\n\n private async setKeyChain(keychain: Map<string, string>) {\n await this.core.storage.setItem<Record<string, string>>(this.storageKey, mapToObj(keychain));\n }\n\n private async getKeyChain() {\n const keychain = await this.core.storage.getItem<Record<string, string>>(this.storageKey);\n return typeof keychain !== \"undefined\" ? objToMap(keychain) : undefined;\n }\n\n private async persist() {\n await this.setKeyChain(this.keychain);\n }\n\n private isInitialized() {\n if (!this.initialized) {\n const { message } = getInternalError(\"NOT_INITIALIZED\", this.name);\n throw new Error(message);\n }\n }\n}\n","import { generateChildLogger, getLoggerContext, Logger } from \"@walletconnect/logger\";\nimport { safeJsonParse, safeJsonStringify } from \"@walletconnect/safe-json\";\nimport { ICore, ICrypto, IKeyChain } from \"@walletconnect/types\";\nimport * as relayAuth from \"@walletconnect/relay-auth\";\nimport { fromString } from \"uint8arrays/from-string\";\nimport {\n decrypt,\n deriveSymKey,\n encrypt,\n generateKeyPair as generateKeyPairUtil,\n hashKey,\n getInternalError,\n generateRandomBytes32,\n validateEncoding,\n validateDecoding,\n isTypeOneEnvelope,\n isTypeTwoEnvelope,\n encodeTypeTwoEnvelope,\n decodeTypeTwoEnvelope,\n deserialize,\n decodeTypeByte,\n BASE16,\n BASE64,\n} from \"@walletconnect/utils\";\nimport { toString } from \"uint8arrays\";\n\nimport { CRYPTO_CONTEXT, CRYPTO_CLIENT_SEED, CRYPTO_JWT_TTL } from \"../constants/index.js\";\nimport { KeyChain } from \"./keychain.js\";\n\nexport class Crypto implements ICrypto {\n public name = CRYPTO_CONTEXT;\n public keychain: ICrypto[\"keychain\"];\n public readonly randomSessionIdentifier = generateRandomBytes32();\n\n private initialized = false;\n private clientId: string | undefined;\n constructor(\n public core: ICore,\n public logger: Logger,\n keychain?: IKeyChain,\n ) {\n this.core = core;\n this.logger = generateChildLogger(logger, this.name);\n this.keychain = keychain || new KeyChain(this.core, this.logger);\n }\n\n public init: ICrypto[\"init\"] = async () => {\n if (!this.initialized) {\n await this.keychain.init();\n this.initialized = true;\n }\n };\n\n get context() {\n return getLoggerContext(this.logger);\n }\n\n public hasKeys: ICrypto[\"hasKeys\"] = (tag) => {\n this.isInitialized();\n return this.keychain.has(tag);\n };\n\n public getClientId: ICrypto[\"getClientId\"] = async () => {\n this.isInitialized();\n if (this.clientId) {\n return this.clientId;\n }\n const seed = await this.getClientSeed();\n const keyPair = relayAuth.generateKeyPair(seed);\n const clientId = relayAuth.encodeIss(keyPair.publicKey);\n this.clientId = clientId;\n return clientId;\n };\n\n public generateKeyPair: ICrypto[\"generateKeyPair\"] = () => {\n this.isInitialized();\n const keyPair = generateKeyPairUtil();\n return this.setPrivateKey(keyPair.publicKey, keyPair.privateKey);\n };\n\n public signJWT: ICrypto[\"signJWT\"] = async (aud) => {\n this.isInitialized();\n const seed = await this.getClientSeed();\n const keyPair = relayAuth.generateKeyPair(seed);\n const sub = this.randomSessionIdentifier;\n const ttl = CRYPTO_JWT_TTL;\n const jwt = await relayAuth.signJWT(sub, aud, ttl, keyPair);\n return jwt;\n };\n\n public generateSharedKey: ICrypto[\"generateSharedKey\"] = (\n selfPublicKey,\n peerPublicKey,\n overrideTopic,\n ) => {\n this.isInitialized();\n const selfPrivateKey = this.getPrivateKey(selfPublicKey);\n const symKey = deriveSymKey(selfPrivateKey, peerPublicKey);\n return this.setSymKey(symKey, overrideTopic);\n };\n\n public setSymKey: ICrypto[\"setSymKey\"] = async (symKey, overrideTopic) => {\n this.isInitialized();\n const topic = overrideTopic || hashKey(symKey);\n await this.keychain.set(topic, symKey);\n return topic;\n };\n\n public deleteKeyPair: ICrypto[\"deleteKeyPair\"] = async (publicKey: string) => {\n this.isInitialized();\n await this.keychain.del(publicKey);\n };\n\n public deleteSymKey: ICrypto[\"deleteSymKey\"] = async (topic: string) => {\n this.isInitialized();\n await this.keychain.del(topic);\n };\n\n public encode: ICrypto[\"encode\"] = async (topic, payload, opts) => {\n this.isInitialized();\n const params = validateEncoding(opts);\n const message = safeJsonStringify(payload);\n\n if (isTypeTwoEnvelope(params)) {\n return encodeTypeTwoEnvelope(message, opts?.encoding);\n }\n\n if (isTypeOneEnvelope(params)) {\n const selfPublicKey = params.senderPublicKey;\n const peerPublicKey = params.receiverPublicKey;\n topic = await this.generateSharedKey(selfPublicKey, peerPublicKey);\n }\n const symKey = this.getSymKey(topic);\n const { type, senderPublicKey } = params;\n const result = encrypt({ type, symKey, message, senderPublicKey, encoding: opts?.encoding });\n return result;\n };\n\n public decode: ICrypto[\"decode\"] = async (topic, encoded, opts) => {\n this.isInitialized();\n const params = validateDecoding(encoded, opts);\n if (isTypeTwoEnvelope(params)) {\n const message = decodeTypeTwoEnvelope(encoded, opts?.encoding);\n return safeJsonParse(message);\n }\n if (isTypeOneEnvelope(params)) {\n const selfPublicKey = params.receiverPublicKey;\n const peerPublicKey = params.senderPublicKey;\n topic = await this.generateSharedKey(selfPublicKey, peerPublicKey);\n }\n try {\n const symKey = this.getSymKey(topic);\n const message = decrypt({ symKey, encoded, encoding: opts?.encoding });\n const payload = safeJsonParse(message);\n return payload;\n } catch (error) {\n this.logger.error(\n `Failed to decode message from topic: '${topic}', clientId: '${await this.getClientId()}'`,\n );\n this.logger.error(error);\n }\n };\n\n public getPayloadType: ICrypto[\"getPayloadType\"] = (encoded, encoding = BASE64) => {\n const deserialized = deserialize({ encoded, encoding });\n return decodeTypeByte(deserialized.type);\n };\n\n public getPayloadSenderPublicKey: ICrypto[\"getPayloadSenderPublicKey\"] = (\n encoded,\n encoding = BASE64,\n ) => {\n const deserialized = deserialize({ encoded, encoding });\n return deserialized.senderPublicKey\n ? toString(deserialized.senderPublicKey, BASE16)\n : undefined;\n };\n\n // ---------- Private ----------------------------------------------- //\n\n private async setPrivateKey(publicKey: string, privateKey: string): Promise<string> {\n await this.keychain.set(publicKey, privateKey);\n return publicKey;\n }\n\n private getPrivateKey(publicKey: string) {\n const privateKey = this.keychain.get(publicKey);\n return privateKey;\n }\n\n private async getClientSeed(): Promise<Uint8Array> {\n let seed = \"\";\n try {\n seed = this.keychain.get(CRYPTO_CLIENT_SEED);\n } catch {\n seed = generateRandomBytes32();\n await this.keychain.set(CRYPTO_CLIENT_SEED, seed);\n }\n return fromString(seed, \"base16\");\n }\n\n private getSymKey(topic: string) {\n const symKey = this.keychain.get(topic);\n return symKey;\n }\n\n private isInitialized() {\n if (!this.initialized) {\n const { message } = getInternalError(\"NOT_INITIALIZED\", this.name);\n throw new Error(message);\n }\n }\n}\n","import { generateChildLogger, getLoggerContext, Logger } from \"@walletconnect/logger\";\nimport { ICore, IMessageTracker, MessageRecord } from \"@walletconnect/types\";\nimport { hashMessage, mapToObj, objToMap, getInternalError } from \"@walletconnect/utils\";\nimport {\n CORE_STORAGE_PREFIX,\n MESSAGE_DIRECTION,\n MESSAGES_CONTEXT,\n MESSAGES_STORAGE_VERSION,\n} from \"../constants/index.js\";\n\nexport class MessageTracker extends IMessageTracker {\n public messages = new Map<string, MessageRecord>();\n /**\n * stores messages that have not been acknowledged by the implementing client\n * this is used to prevent losing messages in race conditions such as\n * when a message is received by the relayer before the implementing client is ready to receive it\n */\n public messagesWithoutClientAck = new Map<string, MessageRecord>();\n public name = MESSAGES_CONTEXT;\n public version = MESSAGES_STORAGE_VERSION;\n\n private initialized = false;\n private storagePrefix = CORE_STORAGE_PREFIX;\n\n constructor(\n public logger: Logger,\n public core: ICore,\n ) {\n super(logger, core);\n this.logger = generateChildLogger(logger, this.name);\n this.core = core;\n }\n\n public init: IMessageTracker[\"init\"] = async () => {\n if (!this.initialized) {\n this.logger.trace(`Initialized`);\n try {\n const messages = await this.getRelayerMessages();\n if (typeof messages !== \"undefined\") {\n this.messages = messages;\n }\n const messagesWithoutClientAck = await this.getRelayerMessagesWithoutClientAck();\n if (typeof messagesWithoutClientAck !== \"undefined\") {\n this.messagesWithoutClientAck = messagesWithoutClientAck;\n }\n this.logger.debug(`Successfully Restored records for ${this.name}`);\n this.logger.trace({ type: \"method\", method: \"restore\", size: this.messages.size });\n } catch (e) {\n this.logger.debug(`Failed to Restore records for ${this.name}`);\n this.logger.error(e as any);\n } finally {\n this.initialized = true;\n }\n }\n };\n\n get context(): string {\n return getLoggerContext(this.logger);\n }\n\n get storageKey() {\n return this.storagePrefix + this.version + this.core.customStoragePrefix + \"//\" + this.name;\n }\n\n get storageKeyWithoutClientAck() {\n return (\n this.storagePrefix +\n this.version +\n this.core.customStoragePrefix +\n \"//\" +\n this.name +\n \"_withoutClientAck\"\n );\n }\n\n public set: IMessageTracker[\"set\"] = async (topic, message, direction) => {\n this.isInitialized();\n const hash = hashMessage(message);\n let messages = this.messages.get(topic);\n if (typeof messages === \"undefined\") {\n messages = {};\n }\n if (typeof messages[hash] !== \"undefined\") {\n return hash;\n }\n messages[hash] = message;\n this.messages.set(topic, messages);\n // Only store messages without client ack for inbound messages\n if (direction === MESSAGE_DIRECTION.inbound) {\n const messagesWithoutClientAck = this.messagesWithoutClientAck.get(topic) || {};\n this.messagesWithoutClientAck.set(topic, {\n ...messagesWithoutClientAck,\n [hash]: message,\n });\n }\n\n await this.persist();\n return hash;\n };\n\n public get: IMessageTracker[\"get\"] = (topic) => {\n this.isInitialized();\n let messages = this.messages.get(topic);\n if (typeof messages === \"undefined\") {\n messages = {};\n }\n return messages;\n };\n\n public getWithoutAck: IMessageTracker[\"getWithoutAck\"] = (topics) => {\n this.isInitialized();\n const messages: Record<string, string[]> = {};\n for (const topic of topics) {\n const messagesWithoutClientAck = this.messagesWithoutClientAck.get(topic) || {};\n messages[topic] = Object.values(messagesWithoutClientAck);\n }\n return messages;\n };\n\n public has: IMessageTracker[\"has\"] = (topic, message) => {\n this.isInitialized();\n const messages = this.get(topic);\n const hash = hashMessage(message);\n return typeof messages[hash] !== \"undefined\";\n };\n\n public ack: IMessageTracker[\"ack\"] = async (topic, message) => {\n this.isInitialized();\n const messages = this.messagesWithoutClientAck.get(topic);\n if (typeof messages === \"undefined\") {\n return;\n }\n\n const hash = hashMessage(message);\n\n delete messages[hash];\n if (Object.keys(messages).length === 0) {\n this.messagesWithoutClientAck.delete(topic);\n } else {\n this.messagesWithoutClientAck.set(topic, messages);\n }\n await this.persist();\n };\n\n public del: IMessageTracker[\"del\"] = async (topic) => {\n this.isInitialized();\n this.messages.delete(topic);\n this.messagesWithoutClientAck.delete(topic);\n await this.persist();\n };\n\n // ---------- Private ----------------------------------------------- //\n\n private async setRelayerMessages(messages: Map<string, MessageRecord>): Promise<void> {\n await this.core.storage.setItem<Record<string, MessageRecord>>(\n this.storageKey,\n mapToObj(messages),\n );\n }\n\n private async setRelayerMessagesWithoutClientAck(\n messages: Map<string, MessageRecord>,\n ): Promise<void> {\n await this.core.storage.setItem<Record<string, MessageRecord>>(\n this.storageKeyWithoutClientAck,\n mapToObj(messages),\n );\n }\n\n private async getRelayerMessages(): Promise<Map<string, MessageRecord> | undefined> {\n const messages = await this.core.storage.getItem<Record<string, MessageRecord>>(\n this.storageKey,\n );\n return typeof messages !== \"undefined\" ? objToMap(messages) : undefined;\n }\n\n private async getRelayerMessagesWithoutClientAck(): Promise<\n Map<string, MessageRecord> | undefined\n > {\n const messages = await this.core.storage.getItem<Record<string, MessageRecord>>(\n this.storageKeyWithoutClientAck,\n );\n return typeof messages !== \"undefined\" ? objToMap(messages) : undefined;\n }\n\n private async persist() {\n await this.setRelayerMessages(this.messages);\n await this.setRelayerMessagesWithoutClientAck(this.messagesWithoutClientAck);\n }\n\n private isInitialized() {\n if (!this.initialized) {\n const { message } = getInternalError(\"NOT_INITIALIZED\", this.name);\n throw new Error(message);\n }\n }\n}\n","import { EventEmitter } from \"events\";\nimport { HEARTBEAT_EVENTS } from \"@walletconnect/heartbeat\";\nimport { JsonRpcPayload, RequestArguments } from \"@walletconnect/jsonrpc-types\";\nimport { generateChildLogger, getLoggerContext, Logger } from \"@walletconnect/logger\";\nimport { RelayJsonRpc } from \"@walletconnect/relay-api\";\nimport { IPublisher, IRelayer, RelayerTypes } from \"@walletconnect/types\";\nimport {\n getRelayProtocolApi,\n getRelayProtocolName,\n isUndefined,\n createExpiringPromise,\n} from \"@walletconnect/utils\";\nimport { getBigIntRpcId } from \"@walletconnect/jsonrpc-utils\";\nimport { FIVE_MINUTES, ONE_MINUTE, ONE_SECOND, toMiliseconds } from \"@walletconnect/time\";\n\nimport { PUBLISHER_CONTEXT, PUBLISHER_DEFAULT_TTL, RELAYER_EVENTS } from \"../constants/index.js\";\n\ntype IPublishType = {\n attestation?: string;\n attempt: number;\n request: RequestArguments;\n opts?: RelayerTypes.PublishOptions;\n};\n\nexport class Publisher extends IPublisher {\n public events = new EventEmitter();\n public name = PUBLISHER_CONTEXT;\n public queue = new Map<string, IPublishType>();\n\n private publishTimeout = toMiliseconds(ONE_MINUTE);\n private initialPublishTimeout = toMiliseconds(ONE_SECOND * 15);\n private needsTransportRestart = false;\n\n constructor(\n public relayer: IRelayer,\n public logger: Logger,\n ) {\n super(relayer, logger);\n this.relayer = relayer;\n this.logger = generateChildLogger(logger, this.name);\n this.registerEventListeners();\n }\n\n get context() {\n return getLoggerContext(this.logger);\n }\n\n public publish: IPublisher[\"publish\"] = async (topic, message, opts) => {\n this.logger.debug(`Publishing Payload`);\n this.logger.trace({ type: \"method\", method: \"publish\", params: { topic, message, opts } });\n\n const ttl = opts?.ttl || PUBLISHER_DEFAULT_TTL;\n const prompt = opts?.prompt || false;\n const tag = opts?.tag || 0;\n const id = opts?.id || (getBigIntRpcId().toString() as any);\n\n const api = getRelayProtocolApi(getRelayProtocolName().protocol);\n\n const request: RequestArguments<RelayJsonRpc.PublishParams> = {\n id,\n method: opts?.publishMethod || api.publish,\n params: {\n topic,\n message,\n ttl,\n prompt,\n tag,\n attestation: opts?.attestation,\n ...opts?.tvf,\n },\n };\n\n const failedPublishMessage = `Failed to publish payload, please try again. id:${id} tag:${tag}`;\n try {\n if (isUndefined(request.params?.prompt)) delete request.params?.prompt;\n if (isUndefined(request.params?.tag)) delete request.params?.tag;\n\n /**\n * attempt to publish the payload for <initialPublishTimeout> seconds,\n * if the publish fails, add the payload to the queue and it will be retried on every pulse\n * until it is successfully published or <publishTimeout> seconds have passed\n */\n const publishPromise = new Promise<void>(async (resolve) => {\n const onPublish = ({ id }: { id: string }) => {\n if (request.id?.toString() === id.toString()) {\n this.removeRequestFromQueue(id);\n this.relayer.events.removeListener(RELAYER_EVENTS.publish, onPublish);\n resolve();\n }\n };\n this.relayer.events.on(RELAYER_EVENTS.publish, onPublish);\n const initialPublish = createExpiringPromise(\n new Promise((resolve, reject) => {\n this.rpcPublish(request, opts)\n .then(resolve)\n .catch((e) => {\n this.logger.warn(e, e?.message);\n reject(e);\n });\n }),\n this.initialPublishTimeout,\n `Failed initial publish, retrying.... id:${id} tag:${tag}`,\n );\n try {\n await initialPublish;\n this.events.removeListener(RELAYER_EVENTS.publish, onPublish);\n } catch (e) {\n this.queue.set(id, { request, opts, attempt: 1 });\n this.logger.warn(e, (e as Error)?.message);\n }\n });\n this.logger.trace({\n type: \"method\",\n method: \"publish\",\n params: { id, topic, message, opts },\n });\n\n await createExpiringPromise(publishPromise, this.publishTimeout, failedPublishMessage);\n } catch (e) {\n this.logger.debug(`Failed to Publish Payload`);\n this.logger.error(e as any);\n if (opts?.internal?.throwOnFailedPublish) {\n throw e;\n }\n } finally {\n this.queue.delete(id);\n }\n };\n\n public publishCustom: IPublisher[\"publishCustom\"] = async (params) => {\n this.logger.debug(`Publishing custom payload`);\n this.logger.trace({ type: \"method\", method: \"publishCustom\", params });\n\n const { payload, opts = {} } = params;\n const { attestation, tvf, publishMethod, prompt, tag, ttl = FIVE_MINUTES } = opts;\n\n const id = opts.id || (getBigIntRpcId().toString() as any);\n const api = getRelayProtocolApi(getRelayProtocolName().protocol);\n const method = publishMethod || api.publish;\n const request: RequestArguments<RelayJsonRpc.PublishParams> = {\n id,\n method,\n params: {\n ...payload,\n ttl,\n prompt,\n tag,\n attestation,\n ...tvf,\n },\n };\n const failedPublishMessage = `Failed to publish custom payload, please try again. id:${id} tag:${tag}`;\n try {\n if (isUndefined(request.params?.prompt)) delete request.params?.prompt;\n if (isUndefined(request.params?.tag)) delete request.params?.tag;\n\n /**\n * attempt to publish the payload for <initialPublishTimeout> seconds,\n * if the publish fails, add the payload to the queue and it will be retried on every pulse\n * until it is successfully published or <publishTimeout> seconds have passed\n */\n const publishPromise = new Promise<void>(async (resolve) => {\n const onPublish = ({ id }: { id: string }) => {\n if (request.id?.toString() === id.toString()) {\n this.removeRequestFromQueue(id);\n this.relayer.events.removeListener(RELAYER_EVENTS.publish, onPublish);\n resolve();\n }\n };\n this.relayer.events.on(RELAYER_EVENTS.publish, onPublish);\n const initialPublish = createExpiringPromise(\n new Promise((resolve, reject) => {\n this.rpcPublish(request, opts)\n .then(resolve)\n .catch((e) => {\n this.logger.warn(e, e?.message);\n reject(e);\n });\n }),\n this.initialPublishTimeout,\n `Failed initial custom payload publish, retrying.... method:${method} id:${id} tag:${tag}`,\n );\n try {\n await initialPublish;\n this.events.removeListener(RELAYER_EVENTS.publish, onPublish);\n } catch (e) {\n this.queue.set(id, { request, opts, attempt: 1 });\n this.logger.warn(e, (e as Error)?.message);\n }\n });\n this.logger.trace({\n type: \"method\",\n method: \"publish\",\n params: { id, payload, opts },\n });\n\n await createExpiringPromise(publishPromise, this.publishTimeout, failedPublishMessage);\n } catch (e) {\n this.logger.debug(`Failed to Publish Payload`);\n this.logger.error(e as any);\n if (opts?.internal?.throwOnFailedPublish) {\n throw e;\n }\n } finally {\n this.queue.delete(id);\n }\n };\n\n public on: IPublisher[\"on\"] = (event, listener) => {\n this.events.on(event, listener);\n };\n\n public once: IPublisher[\"once\"] = (event, listener) => {\n this.events.once(event, listener);\n };\n\n public off: IPublisher[\"off\"] = (event, listener) => {\n this.events.off(event, listener);\n };\n\n public removeListener: IPublisher[\"removeListener\"] = (event, listener) => {\n this.events.removeListener(event, listener);\n };\n\n // ---------- Private ----------------------------------------------- //\n\n private async rpcPublish(request: RequestArguments, opts?: RelayerTypes.PublishOptions) {\n this.logger.debug(`Outgoing Relay Payload`);\n this.logger.trace({ type: \"message\", direction: \"outgoing\", request });\n const result = await this.relayer.request(request);\n\n this.relayer.events.emit(RELAYER_EVENTS.publish, { ...request, ...opts });\n this.logger.debug(`Successfully Published Payload`);\n return result;\n }\n\n private removeRequestFromQueue(id: string) {\n this.queue.delete(id);\n }\n\n private checkQueue() {\n this.queue.forEach(async (params, id) => {\n const attempt = params.attempt + 1;\n this.queue.set(id, { ...params, attempt });\n this.logger.warn(\n {},\n `Publisher: queue->publishing: ${params.request.id}, tag: ${params.request.params?.tag}, attempt: ${attempt}`,\n );\n await this.rpcPublish(params.request, params.opts);\n this.logger.warn({}, `Publisher: queue->published: ${params.request.id}`);\n });\n }\n\n private registerEventListeners() {\n this.relayer.core.heartbeat.on(HEARTBEAT_EVENTS.pulse, () => {\n // restart the transport if needed\n // queue will be processed on the next pulse\n if (this.needsTransportRestart) {\n this.needsTransportRestart = false;\n this.relayer.events.emit(RELAYER_EVENTS.connection_stalled);\n return;\n }\n this.checkQueue();\n });\n this.relayer.on(RELAYER_EVENTS.message_ack, (event: JsonRpcPayload) => {\n this.removeRequestFromQueue(event.id.toString());\n });\n }\n}\n","import { ISubscriberTopicMap } from \"@walletconnect/types\";\n\nexport class SubscriberTopicMap implements ISubscriberTopicMap {\n public map = new Map<string, string[]>();\n\n get topics(): string[] {\n return Array.from(this.map.keys());\n }\n\n public set: ISubscriberTopicMap[\"set\"] = (topic, id) => {\n const ids = this.get(topic);\n if (this.exists(topic, id)) return;\n this.map.set(topic, [...ids, id]);\n };\n\n public get: ISubscriberTopicMap[\"get\"] = (topic) => {\n const ids = this.map.get(topic);\n return ids || [];\n };\n\n public exists: ISubscriberTopicMap[\"exists\"] = (topic, id) => {\n const ids = this.get(topic);\n return ids.includes(id);\n };\n\n public delete: ISubscriberTopicMap[\"delete\"] = (topic, id) => {\n if (typeof id === \"undefined\") {\n this.map.delete(topic);\n return;\n }\n if (!this.map.has(topic)) return;\n const ids = this.get(topic);\n if (!this.exists(topic, id)) return;\n const remaining = ids.filter((x) => x !== id);\n if (!remaining.length) {\n this.map.delete(topic);\n return;\n }\n this.map.set(topic, remaining);\n };\n\n public clear: ISubscriberTopicMap[\"clear\"] = () => {\n this.map.clear();\n };\n}\n","import { EventEmitter } from \"events\";\nimport { HEARTBEAT_EVENTS } from \"@walletconnect/heartbeat\";\nimport { ErrorResponse, RequestArguments } from \"@walletconnect/jsonrpc-types\";\nimport { generateChildLogger, getLoggerContext, Logger } from \"@walletconnect/logger\";\nimport { RelayJsonRpc } from \"@walletconnect/relay-api\";\nimport { ONE_SECOND, ONE_MINUTE, toMiliseconds } from \"@walletconnect/time\";\nimport {\n IRelayer,\n ISubscriber,\n RelayerTypes,\n SubscriberEvents,\n SubscriberTypes,\n} from \"@walletconnect/types\";\nimport {\n getSdkError,\n getInternalError,\n getRelayProtocolApi,\n getRelayProtocolName,\n createExpiringPromise,\n hashMessage,\n sleep,\n} from \"@walletconnect/utils\";\nimport {\n CORE_STORAGE_PREFIX,\n SUBSCRIBER_CONTEXT,\n SUBSCRIBER_EVENTS,\n SUBSCRIBER_STORAGE_VERSION,\n RELAYER_EVENTS,\n TRANSPORT_TYPES,\n} from \"../constants/index.js\";\nimport { SubscriberTopicMap } from \"./topicmap.js\";\n\nexport class Subscriber extends ISubscriber {\n public subscriptions = new Map<string, SubscriberTypes.Active>();\n\n public topicMap = new SubscriberTopicMap();\n public events = new EventEmitter();\n public name = SUBSCRIBER_CONTEXT;\n public version = SUBSCRIBER_STORAGE_VERSION;\n public pending = new Map<string, SubscriberTypes.Params>();\n\n private cached: SubscriberTypes.Active[] = [];\n private initialized = false;\n private storagePrefix = CORE_STORAGE_PREFIX;\n private subscribeTimeout = toMiliseconds(ONE_MINUTE);\n private initialSubscribeTimeout = toMiliseconds(ONE_SECOND * 15);\n private clientId: string;\n private batchSubscribeTopicsLimit = 500;\n\n constructor(\n public relayer: IRelayer,\n public logger: Logger,\n ) {\n super(relayer, logger);\n this.relayer = relayer;\n this.logger = generateChildLogger(logger, this.name);\n this.clientId = \"\"; // assigned when calling this.getClientId()\n }\n\n public init: ISubscriber[\"init\"] = async () => {\n if (!this.initialized) {\n this.logger.trace(`Initialized`);\n this.registerEventListeners();\n await this.restore();\n }\n this.initialized = true;\n };\n\n get context() {\n return getLoggerContext(this.logger);\n }\n\n get storageKey() {\n return (\n this.storagePrefix + this.version + this.relayer.core.customStoragePrefix + \"//\" + this.name\n );\n }\n\n get length() {\n return this.subscriptions.size;\n }\n\n get ids() {\n return Array.from(this.subscriptions.keys());\n }\n\n get values() {\n return Array.from(this.subscriptions.values());\n }\n\n get topics() {\n return this.topicMap.topics;\n }\n\n get hasAnyTopics() {\n return (\n this.topicMap.topics.length > 0 ||\n this.pending.size > 0 ||\n this.cached.length > 0 ||\n this.subscriptions.size > 0\n );\n }\n\n public subscribe: ISubscriber[\"subscribe\"] = async (topic, opts) => {\n this.isInitialized();\n this.logger.debug(`Subscribing Topic`);\n this.logger.trace({ type: \"method\", method: \"subscribe\", params: { topic, opts } });\n try {\n const relay = getRelayProtocolName(opts);\n const params = { topic, relay, transportType: opts?.transportType };\n if (!opts?.internal?.skipSubscribe) {\n this.pending.set(topic, params);\n }\n const id = await this.rpcSubscribe(topic, relay, opts);\n if (typeof id === \"string\") {\n this.onSubscribe(id, params);\n this.logger.debug(`Successfully Subscribed Topic`);\n this.logger.trace({ type: \"method\", method: \"subscribe\", params: { topic, opts } });\n }\n return id;\n } catch (e) {\n this.logger.debug(`Failed to Subscribe Topic`);\n this.logger.error(e as any);\n throw e;\n }\n };\n\n public unsubscribe: ISubscriber[\"unsubscribe\"] = async (topic, opts) => {\n this.isInitialized();\n if (typeof opts?.id !== \"undefined\") {\n await this.unsubscribeById(topic, opts.id, opts);\n } else {\n await this.unsubscribeByTopic(topic, opts);\n }\n };\n\n /**\n * returns `true` only if the topic is actively subscribed to i.e. not pending or cached\n */\n public isSubscribed: ISubscriber[\"isSubscribed\"] = (topic: string) => {\n return new Promise((resolve) => {\n resolve(this.topicMap.topics.includes(topic));\n });\n };\n\n /**\n * returns `true` if the topic is known to the subscriber i.e. it is actively subscribed, pending, cached or in the topic map\n */\n public isKnownTopic: ISubscriber[\"isKnownTopic\"] = (topic: string) => {\n return new Promise((resolve) => {\n resolve(\n this.topicMap.topics.includes(topic) ||\n this.pending.has(topic) ||\n this.cached.some((s) => s.topic === topic),\n );\n });\n };\n\n public on: ISubscriber[\"on\"] = (event, listener) => {\n this.events.on(event, listener);\n };\n\n public once: ISubscriber[\"once\"] = (event, listener) => {\n this.events.once(event, listener);\n };\n\n public off: ISubscriber[\"off\"] = (event, listener) => {\n this.events.off(event, listener);\n };\n\n public removeListener: ISubscriber[\"removeListener\"] = (event, listener) => {\n this.events.removeListener(event, listener);\n };\n\n public start: ISubscriber[\"start\"] = async () => {\n await this.onConnect();\n };\n\n public stop: ISubscriber[\"stop\"] = async () => {\n await this.onDisconnect();\n };\n\n // ---------- Private ----------------------------------------------- //\n\n private hasSubscription(id: string, topic: string) {\n let result = false;\n try {\n const subscription = this.getSubscription(id);\n result = subscription.topic === topic;\n } catch (e) {\n // ignore error\n }\n return result;\n }\n\n private reset() {\n this.cached = [];\n this.initialized = true;\n }\n\n private onDisable() {\n // only write to this.cached if there are active subscriptions\n // as this.cached can be overridden if onDisable is called multiple times\n if (this.values.length > 0) {\n this.cached = this.values;\n }\n this.subscriptions.clear();\n this.topicMap.clear();\n }\n\n private async unsubscribeByTopic(topic: string, opts?: RelayerTypes.UnsubscribeOptions) {\n const ids = this.topicMap.get(topic);\n await Promise.all(ids.map(async (id) => await this.unsubscribeById(topic, id, opts)));\n }\n\n private async unsubscribeById(topic: string, id: string, opts?: RelayerTypes.UnsubscribeOptions) {\n this.logger.debug(`Unsubscribing Topic`);\n this.logger.trace({ type: \"method\", method: \"unsubscribe\", params: { topic, id, opts } });\n try {\n // optimistically remove the subscription from the local state\n const reason = getSdkError(\"USER_DISCONNECTED\", `${this.name}, ${topic}`);\n await this.onUnsubscribe(topic, id, reason);\n const relay = getRelayProtocolName(opts);\n await this.restartToComplete({ topic, id, relay });\n await this.rpcUnsubscribe(topic, id, relay);\n this.logger.debug(`Successfully Unsubscribed Topic`);\n this.logger.trace({ type: \"method\", method: \"unsubscribe\", params: { topic, id, opts } });\n } catch (e) {\n this.logger.debug(`Failed to Unsubscribe Topic`);\n this.logger.error(e as any);\n throw e;\n }\n }\n\n private async rpcSubscribe(\n topic: string,\n relay: RelayerTypes.ProtocolOptions,\n opts?: RelayerTypes.SubscribeOptions,\n ) {\n const subId = await this.getSubscriptionId(topic);\n if (opts?.internal?.skipSubscribe) {\n return subId;\n }\n if (!opts || opts?.transportType === TRANSPORT_TYPES.relay) {\n await this.restartToComplete({ topic, id: topic, relay });\n }\n const api = getRelayProtocolApi(relay.protocol);\n const request: RequestArguments<RelayJsonRpc.SubscribeParams> = {\n method: api.subscribe,\n params: {\n topic,\n },\n };\n this.logger.debug(`Outgoing Relay Payload`);\n this.logger.trace({ type: \"payload\", direction: \"outgoing\", request });\n const shouldThrow = opts?.internal?.throwOnFailedPublish;\n try {\n // in link mode, allow the app to update its network state (i.e. active airplane mode) with small delay before attempting to subscribe\n if (opts?.transportType === TRANSPORT_TYPES.link_mode) {\n setTimeout(() => {\n if (this.relayer.connected || this.relayer.connecting) {\n this.relayer.request(request).catch((e) => this.logger.warn(e));\n }\n }, toMiliseconds(ONE_SECOND));\n return subId;\n }\n const subscribePromise = new Promise(async (resolve) => {\n const onSubscribe = (subscription: SubscriberEvents.Created) => {\n if (subscription.topic === topic) {\n this.events.removeListener(SUBSCRIBER_EVENTS.created, onSubscribe);\n resolve(subscription.id);\n }\n };\n this.events.on(SUBSCRIBER_EVENTS.created, onSubscribe);\n try {\n const result = await createExpiringPromise(\n new Promise((resolve, reject) => {\n this.relayer\n .request(request)\n .catch((e) => {\n this.logger.warn(e, e?.message);\n reject(e);\n })\n .then(resolve);\n }),\n this.initialSubscribeTimeout,\n `Subscribing to ${topic} failed, please try again`,\n );\n this.events.removeListener(SUBSCRIBER_EVENTS.created, onSubscribe);\n resolve(result);\n } catch (err) {}\n });\n\n const subscribe = createExpiringPromise(\n subscribePromise,\n this.subscribeTimeout,\n `Subscribing to ${topic} failed, please try again`,\n );\n\n const result = await subscribe;\n if (!result && shouldThrow) {\n throw new Error(`Subscribing to ${topic} failed, please try again`);\n }\n // return null to indicate that the subscription failed\n return result ? subId : null;\n } catch (err) {\n this.logger.debug(`Outgoing Relay Subscribe Payload stalled`);\n this.relayer.events.emit(RELAYER_EVENTS.connection_stalled);\n if (shouldThrow) {\n throw err;\n }\n }\n return null;\n }\n\n private async rpcBatchSubscribe(subscriptions: SubscriberTypes.Params[]): Promise<boolean> {\n if (!subscriptions.length) return true;\n const relay = subscriptions[0].relay;\n const api = getRelayProtocolApi(relay!.protocol);\n const request: RequestArguments<RelayJsonRpc.BatchSubscribeParams> = {\n method: api.batchSubscribe,\n params: {\n topics: subscriptions.map((s) => s.topic),\n },\n };\n this.logger.debug(`Outgoing Relay Payload`);\n this.logger.trace({ type: \"payload\", direction: \"outgoing\", request });\n try {\n await createExpiringPromise(\n new Promise((resolve, reject) => {\n this.relayer\n .request(request)\n .then(resolve)\n .catch((e) => {\n this.logger.warn(e);\n reject(e);\n });\n }),\n this.subscribeTimeout,\n \"rpcBatchSubscribe failed, please try again\",\n );\n return true;\n } catch (err) {\n this.relayer.events.emit(RELAYER_EVENTS.connection_stalled);\n return false;\n }\n }\n\n private async rpcBatchFetchMessages(subscriptions: SubscriberTypes.Params[]) {\n if (!subscriptions.length) return;\n const relay = subscr