@walletconnect/sign-client
Version:
Sign Client for WalletConnect Protocol
1 lines • 216 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/constants/client.ts","../src/constants/history.ts","../src/constants/proposal.ts","../src/constants/session.ts","../src/constants/engine.ts","../src/constants/pendingRequest.ts","../src/constants/verify.ts","../src/constants/auth.ts","../src/controllers/engine.ts","../src/controllers/proposal.ts","../src/controllers/session.ts","../src/controllers/pendingRequest.ts","../src/controllers/authKey.ts","../src/controllers/authPairingTopic.ts","../src/controllers/authRequest.ts","../src/controllers/authStore.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["import { SignClientTypes } from \"@walletconnect/types\";\n\nexport const SIGN_CLIENT_PROTOCOL = \"wc\";\nexport const SIGN_CLIENT_VERSION = 2;\nexport const SIGN_CLIENT_CONTEXT = \"client\";\n\nexport const SIGN_CLIENT_STORAGE_PREFIX = `${SIGN_CLIENT_PROTOCOL}@${SIGN_CLIENT_VERSION}:${SIGN_CLIENT_CONTEXT}:`;\n\nexport const SIGN_CLIENT_DEFAULT = {\n name: SIGN_CLIENT_CONTEXT,\n logger: \"error\",\n controller: false,\n relayUrl: \"wss://relay.walletconnect.org\",\n};\n\nexport const SIGN_CLIENT_EVENTS: Record<SignClientTypes.Event, SignClientTypes.Event> = {\n session_proposal: \"session_proposal\",\n session_update: \"session_update\",\n session_extend: \"session_extend\",\n session_ping: \"session_ping\",\n session_delete: \"session_delete\",\n session_expire: \"session_expire\",\n session_request: \"session_request\",\n session_request_sent: \"session_request_sent\",\n session_event: \"session_event\",\n proposal_expire: \"proposal_expire\",\n session_authenticate: \"session_authenticate\",\n session_request_expire: \"session_request_expire\",\n session_connect: \"session_connect\",\n};\n\nexport const SIGN_CLIENT_STORAGE_OPTIONS = {\n database: \":memory:\",\n};\n\nexport const WALLETCONNECT_DEEPLINK_CHOICE = \"WALLETCONNECT_DEEPLINK_CHOICE\";\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 { THIRTY_DAYS } from \"@walletconnect/time\";\n\nexport const PROPOSAL_CONTEXT = \"proposal\";\n\nexport const PROPOSAL_EXPIRY = THIRTY_DAYS;\n\nexport const PROPOSAL_EXPIRY_MESSAGE = \"Proposal expired\";\n","import { SEVEN_DAYS } from \"@walletconnect/time\";\n\nexport const SESSION_CONTEXT = \"session\";\n\nexport const SESSION_EXPIRY = SEVEN_DAYS;\n","import { FIVE_MINUTES, ONE_DAY, ONE_HOUR, SEVEN_DAYS } from \"@walletconnect/time\";\nimport { EngineTypes } from \"@walletconnect/types\";\n\nexport const ENGINE_CONTEXT = \"engine\";\n\nexport const ENGINE_RPC_OPTS: EngineTypes.RpcOptsMap = {\n wc_sessionPropose: {\n req: {\n ttl: FIVE_MINUTES,\n prompt: true,\n tag: 1100,\n },\n res: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1101,\n },\n reject: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1120,\n },\n autoReject: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1121,\n },\n },\n wc_sessionSettle: {\n req: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1102,\n },\n res: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1103,\n },\n },\n wc_sessionUpdate: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1104,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1105,\n },\n },\n wc_sessionExtend: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1106,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1107,\n },\n },\n wc_sessionRequest: {\n req: {\n ttl: FIVE_MINUTES * 3,\n prompt: true,\n tag: 1108,\n },\n res: {\n ttl: FIVE_MINUTES * 3,\n prompt: false,\n tag: 1109,\n },\n },\n wc_sessionEvent: {\n req: {\n ttl: FIVE_MINUTES,\n prompt: true,\n tag: 1110,\n },\n res: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1111,\n },\n },\n\n wc_sessionDelete: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1112,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1113,\n },\n },\n wc_sessionPing: {\n req: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1114,\n },\n res: {\n ttl: ONE_DAY,\n prompt: false,\n tag: 1115,\n },\n },\n wc_sessionAuthenticate: {\n req: {\n ttl: ONE_HOUR,\n prompt: true,\n tag: 1116,\n },\n res: {\n ttl: ONE_HOUR,\n prompt: false,\n tag: 1117,\n },\n reject: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1118,\n },\n autoReject: {\n ttl: FIVE_MINUTES,\n prompt: false,\n tag: 1119,\n },\n },\n};\n\nexport const SESSION_REQUEST_EXPIRY_BOUNDARIES = {\n min: FIVE_MINUTES,\n max: SEVEN_DAYS,\n};\n\nexport const ENGINE_QUEUE_STATES: { idle: \"IDLE\"; active: \"ACTIVE\" } = {\n idle: \"IDLE\",\n active: \"ACTIVE\",\n};\n\nexport const TVF_METHODS = {\n // evm\n eth_sendTransaction: {\n key: \"\",\n },\n eth_sendRawTransaction: {\n key: \"\",\n },\n wallet_sendCalls: {\n key: \"\",\n },\n\n // solana\n solana_signTransaction: {\n key: \"signature\",\n },\n solana_signAllTransactions: {\n key: \"transactions\",\n },\n solana_signAndSendTransaction: {\n key: \"signature\",\n },\n\n // sui\n sui_signAndExecuteTransaction: {\n key: \"digest\",\n },\n sui_signTransaction: {\n key: \"\",\n },\n\n // hedera\n hedera_signAndExecuteTransaction: {\n key: \"transactionId\",\n },\n hedera_executeTransaction: {\n key: \"transactionId\",\n },\n\n // near\n near_signTransaction: {\n key: \"\",\n },\n near_signTransactions: {\n key: \"\",\n },\n\n // tron\n tron_signTransaction: {\n key: \"txID\",\n },\n\n // xrpl\n xrpl_signTransaction: {\n key: \"\",\n },\n\n xrpl_signTransactionFor: {\n key: \"\",\n },\n\n // algorand\n algo_signTxn: {\n key: \"\",\n },\n\n // bitcoin\n sendTransfer: {\n key: \"txid\",\n },\n\n // stacks\n stacks_stxTransfer: {\n key: \"txId\",\n },\n\n // polkadot\n polkadot_signTransaction: {\n key: \"\",\n },\n\n // cosmos\n cosmos_signDirect: {\n key: \"\",\n },\n};\n","export const REQUEST_CONTEXT = \"request\";\n","export const METHODS_TO_VERIFY = [\n \"wc_sessionPropose\",\n \"wc_sessionRequest\",\n \"wc_authRequest\",\n \"wc_sessionAuthenticate\",\n];\n","export const AUTH_PROTOCOL = \"wc\";\nexport const AUTH_VERSION = 1.5;\nexport const AUTH_CONTEXT = \"auth\";\nexport const AUTH_KEYS_CONTEXT = \"authKeys\";\nexport const AUTH_PAIRING_TOPIC_CONTEXT = \"pairingTopics\";\nexport const AUTH_REQUEST_CONTEXT = \"requests\";\n\nexport const AUTH_STORAGE_PREFIX = `${AUTH_PROTOCOL}@${AUTH_VERSION}:${AUTH_CONTEXT}:`;\nexport const AUTH_PUBLIC_KEY_NAME = `${AUTH_STORAGE_PREFIX}:PUB_KEY`;\n","import {\n EVENT_CLIENT_AUTHENTICATE_ERRORS,\n EVENT_CLIENT_AUTHENTICATE_TRACES,\n EVENT_CLIENT_PAIRING_ERRORS,\n EVENT_CLIENT_PAIRING_TRACES,\n EVENT_CLIENT_SESSION_ERRORS,\n EVENT_CLIENT_SESSION_TRACES,\n EXPIRER_EVENTS,\n PAIRING_EVENTS,\n RELAYER_DEFAULT_PROTOCOL,\n RELAYER_EVENTS,\n TRANSPORT_TYPES,\n VERIFY_SERVER,\n} from \"@walletconnect/core\";\n\nimport {\n formatJsonRpcError,\n formatJsonRpcRequest,\n formatJsonRpcResult,\n payloadId,\n isJsonRpcError,\n isJsonRpcRequest,\n isJsonRpcResponse,\n isJsonRpcResult,\n JsonRpcRequest,\n ErrorResponse,\n getBigIntRpcId,\n} from \"@walletconnect/jsonrpc-utils\";\nimport { FIVE_MINUTES, ONE_SECOND, toMiliseconds } from \"@walletconnect/time\";\nimport {\n EnginePrivate,\n EngineTypes,\n ExpirerTypes,\n IEngine,\n IEngineEvents,\n JsonRpcTypes,\n PendingRequestTypes,\n Verify,\n CoreTypes,\n ProposalTypes,\n RelayerTypes,\n SessionTypes,\n PairingTypes,\n AuthTypes,\n EventClientTypes,\n} from \"@walletconnect/types\";\nimport {\n calcExpiry,\n createDelayedPromise,\n engineEvent,\n getInternalError,\n getSdkError,\n isConformingNamespaces,\n isExpired,\n isSessionCompatible,\n isUndefined,\n isValidController,\n isValidErrorReason,\n isValidEvent,\n isValidId,\n isValidNamespaces,\n isValidNamespacesChainId,\n isValidNamespacesEvent,\n isValidNamespacesRequest,\n isValidObject,\n isValidParams,\n isValidRelay,\n isValidRelays,\n isValidRequest,\n isValidRequestExpiry,\n hashMessage,\n isValidRequiredNamespaces,\n isValidResponse,\n isValidString,\n parseExpirerTarget,\n TYPE_1,\n TYPE_2,\n handleDeeplinkRedirect,\n MemoryStore,\n getDeepLink,\n hashKey,\n getDidAddress,\n formatMessage,\n getMethodsFromRecap,\n buildNamespacesFromAuth,\n createEncodedRecap,\n getChainsFromRecap,\n mergeEncodedRecaps,\n getRecapFromResources,\n validateSignedCacao,\n getNamespacedDidChainId,\n parseChainId,\n getLinkModeURL,\n BASE64,\n BASE64URL,\n getSearchParamFromURL,\n isReactNative,\n isTestRun,\n isValidArray,\n extractSolanaTransactionId,\n getSuiDigest,\n mergeRequiredAndOptionalNamespaces,\n getNearTransactionIdFromSignedTransaction,\n getAlgorandTransactionId,\n buildSignedExtrinsicHash,\n getSignDirectHash,\n LimitedSet,\n getWalletSendCallsHashes,\n getNamespacesChains,\n getNamespacesMethods,\n getNamespacesEvents,\n} from \"@walletconnect/utils\";\nimport EventEmmiter from \"events\";\nimport {\n ENGINE_CONTEXT,\n ENGINE_RPC_OPTS,\n PROPOSAL_EXPIRY_MESSAGE,\n SESSION_EXPIRY,\n SESSION_REQUEST_EXPIRY_BOUNDARIES,\n METHODS_TO_VERIFY,\n WALLETCONNECT_DEEPLINK_CHOICE,\n ENGINE_QUEUE_STATES,\n AUTH_PUBLIC_KEY_NAME,\n TVF_METHODS,\n} from \"../constants/index.js\";\n\nexport class Engine extends IEngine {\n public name = ENGINE_CONTEXT;\n\n private events: IEngineEvents = new EventEmmiter();\n private initialized = false;\n\n /**\n * Queue responsible for processing incoming requests such as session_update, session_event, session_ping etc\n * It's needed when the client receives multiple requests at once from the mailbox immediately after initialization and to avoid attempting to process them all at the same time\n */\n private requestQueue: EngineTypes.EngineQueue<EngineTypes.EventCallback<JsonRpcRequest>> = {\n state: ENGINE_QUEUE_STATES.idle,\n queue: [],\n };\n\n /**\n * Queue responsible for processing incoming session_request\n * The queue emits the next request only after the previous one has been responded to\n */\n private sessionRequestQueue: EngineTypes.EngineQueue<PendingRequestTypes.Struct> = {\n state: ENGINE_QUEUE_STATES.idle,\n queue: [],\n };\n\n // This prevents duplicate emissions due to WalletConnect's at-least-once delivery guarantee.\n // When disableRequestQueue is enabled, consumers must implement additional deduplication.\n private emittedSessionRequests = new LimitedSet({ limit: 500 });\n\n private requestQueueDelay = ONE_SECOND;\n private expectedPairingMethodMap: Map<string, string[]> = new Map();\n // Ephemeral (in-memory) map to store recently deleted items\n private recentlyDeletedMap = new Map<\n string | number,\n \"pairing\" | \"session\" | \"proposal\" | \"request\"\n >();\n\n private recentlyDeletedLimit = 200;\n private relayMessageCache: RelayerTypes.MessageEvent[] = [];\n private pendingSessions: Map<\n number,\n {\n sessionTopic: string;\n pairingTopic: string;\n proposalId: number;\n publicKey: string;\n }\n > = new Map();\n\n constructor(client: IEngine[\"client\"]) {\n super(client);\n }\n\n public init: IEngine[\"init\"] = async () => {\n if (!this.initialized) {\n await this.cleanup();\n this.registerRelayerEvents();\n this.registerExpirerEvents();\n this.registerPairingEvents();\n await this.registerLinkModeListeners();\n this.client.core.pairing.register({ methods: Object.keys(ENGINE_RPC_OPTS) });\n this.initialized = true;\n setTimeout(async () => {\n await this.processPendingMessageEvents();\n\n this.sessionRequestQueue.queue = this.getPendingSessionRequests();\n this.processSessionRequestQueue();\n }, toMiliseconds(this.requestQueueDelay));\n }\n };\n\n private async processPendingMessageEvents() {\n try {\n const topics = this.client.session.keys;\n const pendingMessages = this.client.core.relayer.messages.getWithoutAck(topics);\n for (const [topic, messages] of Object.entries(pendingMessages)) {\n for (const message of messages) {\n try {\n await this.onProviderMessageEvent({\n topic,\n message,\n publishedAt: Date.now(),\n });\n } catch (error) {\n this.client.logger.warn(\n `Error processing pending message event for topic: ${topic}, message: ${message}`,\n );\n }\n }\n }\n } catch (error) {\n this.client.logger.warn(error, \"processPendingMessageEvents failed\");\n }\n }\n\n // ---------- Public ------------------------------------------------ //\n\n public connect: IEngine[\"connect\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n const connectParams = {\n ...params,\n requiredNamespaces: params.requiredNamespaces || {},\n optionalNamespaces: params.optionalNamespaces || {},\n };\n await this.isValidConnect(connectParams);\n\n // requiredNamespaces are deprecated, assign them to optionalNamespaces\n connectParams.optionalNamespaces = mergeRequiredAndOptionalNamespaces(\n connectParams.requiredNamespaces,\n connectParams.optionalNamespaces,\n );\n\n connectParams.requiredNamespaces = {};\n\n const {\n pairingTopic,\n requiredNamespaces,\n optionalNamespaces,\n sessionProperties,\n scopedProperties,\n relays,\n authentication,\n walletPay,\n } = connectParams;\n\n const expiryFromAuthentication = authentication?.[0]?.ttl;\n const expiry =\n expiryFromAuthentication || ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl || FIVE_MINUTES;\n this.validateRequestExpiry(expiry);\n\n let topic = pairingTopic;\n let uri: string | undefined;\n let active = false;\n try {\n if (topic) {\n const pairing = this.client.core.pairing.pairings.get(topic);\n this.client.logger.warn(\n \"connect() with existing pairing topic is deprecated and will be removed in the next major release.\",\n );\n active = pairing.active;\n }\n } catch (error) {\n this.client.logger.error(`connect() -> pairing.get(${topic}) failed`);\n throw error;\n }\n if (!topic || !active) {\n const { topic: newTopic, uri: newUri } = await this.client.core.pairing.create({\n internal: { skipSubscribe: true },\n });\n topic = newTopic;\n uri = newUri;\n }\n // safety check to ensure pairing topic is available\n if (!topic) {\n const { message } = getInternalError(\"NO_MATCHING_KEY\", `connect() pairing topic: ${topic}`);\n throw new Error(message);\n }\n\n const publicKey = await this.client.core.crypto.generateKeyPair();\n\n const expiryTimestamp = calcExpiry(expiry);\n const proposal: ProposalTypes.Struct = {\n requiredNamespaces,\n optionalNamespaces,\n relays: relays ?? [{ protocol: RELAYER_DEFAULT_PROTOCOL }],\n proposer: {\n publicKey,\n metadata: this.client.metadata,\n },\n expiryTimestamp,\n pairingTopic: topic,\n ...(sessionProperties && { sessionProperties }),\n ...(scopedProperties && { scopedProperties }),\n id: payloadId(),\n ...((authentication || walletPay) && {\n requests: {\n authentication: authentication?.map((auth) => {\n const {\n domain,\n chains,\n nonce,\n uri,\n exp,\n nbf,\n type,\n statement,\n requestId,\n resources,\n signatureTypes,\n } = auth;\n const protocolParams: AuthTypes.AuthenticateParams = {\n domain,\n chains,\n nonce,\n type: type ?? \"caip122\",\n aud: uri,\n version: \"1\",\n iat: new Date().toISOString(),\n exp,\n nbf,\n statement,\n requestId,\n resources,\n signatureTypes,\n };\n return protocolParams;\n }),\n walletPay,\n },\n }),\n };\n\n const sessionConnectTarget = engineEvent(\"session_connect\", proposal.id);\n\n const {\n reject,\n resolve,\n done: approval,\n } = createDelayedPromise<SessionTypes.Struct>(expiry, PROPOSAL_EXPIRY_MESSAGE);\n\n const proposalExpireHandler = ({ id }: { id: number }) => {\n if (id === proposal.id) {\n this.client.events.off(\"proposal_expire\", proposalExpireHandler);\n this.pendingSessions.delete(proposal.id);\n // emit the event to trigger reject, this approach automatically cleans up the .once listener below\n this.events.emit(sessionConnectTarget, {\n error: { message: PROPOSAL_EXPIRY_MESSAGE, code: 0 },\n });\n }\n };\n\n this.client.events.on(\"proposal_expire\", proposalExpireHandler);\n this.events.once<\"session_connect\">(sessionConnectTarget, ({ error, session }) => {\n this.client.events.off(\"proposal_expire\", proposalExpireHandler);\n if (error) reject(error);\n else if (session) {\n resolve(session);\n }\n });\n\n await this.setProposal(proposal.id, proposal);\n\n await this.sendProposeSession({\n proposal,\n publishOpts: {\n internal: {\n throwOnFailedPublish: true,\n },\n tvf: {\n correlationId: proposal.id,\n },\n },\n }).catch((error) => {\n this.deleteProposal(proposal.id);\n throw error;\n });\n\n return { uri, approval };\n };\n\n public pair: IEngine[\"pair\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n try {\n return await this.client.core.pairing.pair(params);\n } catch (error) {\n this.client.logger.error(\"pair() failed\");\n throw error;\n }\n };\n\n public approve: IEngine[\"approve\"] = async (params) => {\n const configEvent = this.client.core.eventClient.createEvent({\n properties: {\n topic: params?.id?.toString(),\n trace: [EVENT_CLIENT_SESSION_TRACES.session_approve_started],\n },\n });\n try {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n } catch (error) {\n configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.no_internet_connection);\n throw error;\n }\n try {\n await this.isValidProposalId(params?.id);\n } catch (error) {\n this.client.logger.error(`approve() -> proposal.get(${params?.id}) failed`);\n configEvent.setError(EVENT_CLIENT_SESSION_ERRORS.proposal_not_found);\n throw error;\n }\n\n try {\n await this.isValidApprove(params);\n } catch (error) {\n this.client.logger.error(\"approve() -> isValidApprove() failed\");\n configEvent.setError(\n EVENT_CLIENT_SESSION_ERRORS.session_approve_namespace_validation_failure,\n );\n throw error;\n }\n\n const {\n id,\n relayProtocol,\n namespaces,\n sessionProperties,\n scopedProperties,\n sessionConfig,\n proposalRequestsResponses,\n } = params;\n\n const proposal = this.client.proposal.get(id);\n\n this.client.core.eventClient.deleteEvent({ eventId: configEvent.eventId });\n\n const { pairingTopic, proposer, requiredNamespaces, optionalNamespaces } = proposal;\n\n let event = this.client.core.eventClient?.getEvent({\n topic: pairingTopic,\n }) as EventClientTypes.Event;\n if (!event) {\n event = this.client.core.eventClient?.createEvent({\n type: EVENT_CLIENT_SESSION_TRACES.session_approve_started,\n properties: {\n topic: pairingTopic,\n trace: [\n EVENT_CLIENT_SESSION_TRACES.session_approve_started,\n EVENT_CLIENT_SESSION_TRACES.session_namespaces_validation_success,\n ],\n },\n });\n }\n\n const selfPublicKey = await this.client.core.crypto.generateKeyPair();\n const peerPublicKey = proposer.publicKey;\n const sessionTopic = await this.client.core.crypto.generateSharedKey(\n selfPublicKey,\n peerPublicKey,\n );\n\n const sessionSettle = {\n relay: { protocol: relayProtocol ?? \"irn\" },\n namespaces,\n controller: { publicKey: selfPublicKey, metadata: this.client.metadata },\n expiry: calcExpiry(SESSION_EXPIRY),\n ...(sessionProperties && { sessionProperties }),\n ...(scopedProperties && { scopedProperties }),\n ...(sessionConfig && { sessionConfig }),\n proposalRequestsResponses,\n };\n const transportType = TRANSPORT_TYPES.relay;\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.subscribing_session_topic);\n try {\n await this.client.core.relayer.subscribe(sessionTopic, {\n transportType,\n internal: { skipSubscribe: true },\n });\n } catch (error) {\n event.setError(EVENT_CLIENT_SESSION_ERRORS.subscribe_session_topic_failure);\n throw error;\n }\n\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.subscribe_session_topic_success);\n\n const session: SessionTypes.Struct = {\n ...sessionSettle,\n topic: sessionTopic,\n requiredNamespaces,\n optionalNamespaces,\n pairingTopic,\n acknowledged: false,\n self: sessionSettle.controller,\n peer: {\n publicKey: proposer.publicKey,\n metadata: proposer.metadata,\n },\n controller: selfPublicKey,\n transportType: TRANSPORT_TYPES.relay,\n authentication: proposalRequestsResponses?.authentication,\n walletPayResult: proposalRequestsResponses?.walletPay,\n };\n\n await this.client.session.set(sessionTopic, session);\n\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.store_session);\n\n try {\n await this.sendApproveSession({\n sessionTopic,\n proposal,\n pairingProposalResponse: {\n relay: {\n protocol: relayProtocol ?? \"irn\",\n },\n responderPublicKey: selfPublicKey,\n },\n sessionSettleRequest: sessionSettle,\n publishOpts: {\n internal: {\n throwOnFailedPublish: true,\n },\n tvf: {\n correlationId: id,\n ...this.getTVFApproveParams(session),\n },\n },\n });\n\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_approve_publish_success);\n } catch (error) {\n this.client.logger.error(error);\n // if the publish fails, delete the session and throw an error\n this.client.session.delete(sessionTopic, getSdkError(\"USER_DISCONNECTED\"));\n await this.client.core.relayer.unsubscribe(sessionTopic);\n throw error;\n }\n\n this.client.core.eventClient.deleteEvent({ eventId: event.eventId });\n\n await this.client.core.pairing.updateMetadata({\n topic: pairingTopic,\n metadata: proposer.metadata,\n });\n await this.deleteProposal(id);\n await this.client.core.pairing.activate({ topic: pairingTopic });\n await this.setExpiry(sessionTopic, calcExpiry(SESSION_EXPIRY));\n return {\n topic: sessionTopic,\n acknowledged: () => Promise.resolve(this.client.session.get(sessionTopic)),\n };\n };\n\n public reject: IEngine[\"reject\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n try {\n await this.isValidReject(params);\n } catch (error) {\n this.client.logger.error(\"reject() -> isValidReject() failed\");\n throw error;\n }\n const { id, reason } = params;\n let pairingTopic;\n try {\n const proposal = this.client.proposal.get(id);\n pairingTopic = proposal.pairingTopic;\n } catch (error) {\n this.client.logger.error(`reject() -> proposal.get(${id}) failed`);\n throw error;\n }\n\n if (pairingTopic) {\n await this.sendError({\n id,\n topic: pairingTopic,\n error: reason,\n rpcOpts: ENGINE_RPC_OPTS.wc_sessionPropose.reject,\n });\n }\n\n await this.deleteProposal(id);\n };\n\n public update: IEngine[\"update\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n try {\n await this.isValidUpdate(params);\n } catch (error) {\n this.client.logger.error(\"update() -> isValidUpdate() failed\");\n throw error;\n }\n const { topic, namespaces } = params;\n\n const {\n done: acknowledged,\n resolve,\n reject,\n } = createDelayedPromise<void>(\n FIVE_MINUTES,\n \"Session update request expired without receiving any acknowledgement\",\n );\n const clientRpcId = payloadId();\n const relayRpcId = getBigIntRpcId().toString() as any;\n\n const oldNamespaces = this.client.session.get(topic).namespaces;\n this.events.once(engineEvent(\"session_update\", clientRpcId), ({ error }: any) => {\n if (error) reject(error);\n else {\n resolve();\n }\n });\n // Update the session with the new namespaces, if the publish fails, revert to the old.\n // This allows the client to use the updated session like emitting events\n // without waiting for the peer to acknowledge\n await this.client.session.update(topic, { namespaces });\n await this.sendRequest({\n topic,\n method: \"wc_sessionUpdate\",\n params: { namespaces },\n throwOnFailedPublish: true,\n clientRpcId,\n relayRpcId,\n }).catch((error) => {\n this.client.logger.error(error);\n this.client.session.update(topic, { namespaces: oldNamespaces });\n reject(error);\n });\n return { acknowledged };\n };\n\n public extend: IEngine[\"extend\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n try {\n await this.isValidExtend(params);\n } catch (error) {\n this.client.logger.error(\"extend() -> isValidExtend() failed\");\n throw error;\n }\n\n const { topic } = params;\n const clientRpcId = payloadId();\n const {\n done: acknowledged,\n resolve,\n reject,\n } = createDelayedPromise<void>(\n FIVE_MINUTES,\n \"Session extend request expired without receiving any acknowledgement\",\n );\n this.events.once(engineEvent(\"session_extend\", clientRpcId), ({ error }: any) => {\n if (error) reject(error);\n else resolve();\n });\n\n await this.setExpiry(topic, calcExpiry(SESSION_EXPIRY));\n this.sendRequest({\n topic,\n method: \"wc_sessionExtend\",\n params: {},\n clientRpcId,\n throwOnFailedPublish: true,\n }).catch((e) => {\n reject(e);\n });\n\n return { acknowledged };\n };\n\n public request: IEngine[\"request\"] = async <T>(params: EngineTypes.RequestParams) => {\n this.isInitialized();\n try {\n await this.isValidRequest(params);\n } catch (error) {\n this.client.logger.error(\"request() -> isValidRequest() failed\");\n throw error;\n }\n const { chainId, request, topic, expiry = ENGINE_RPC_OPTS.wc_sessionRequest.req.ttl } = params;\n const session = this.client.session.get(topic);\n\n if (session?.transportType === TRANSPORT_TYPES.relay) {\n await this.confirmOnlineStateOrThrow();\n }\n const clientRpcId = payloadId();\n const relayRpcId = getBigIntRpcId().toString() as any;\n const { done, resolve, reject } = createDelayedPromise<T>(\n expiry,\n \"Request expired. Please try again.\",\n );\n this.events.once<\"session_request\">(\n engineEvent(\"session_request\", clientRpcId),\n ({ error, result }) => {\n if (error) reject(error);\n else resolve(result);\n },\n );\n const protocolMethod = \"wc_sessionRequest\";\n const appLink = this.getAppLinkIfEnabled(session.peer.metadata, session.transportType);\n if (appLink) {\n await this.sendRequest({\n clientRpcId,\n relayRpcId,\n topic,\n method: protocolMethod,\n params: {\n request: {\n ...request,\n expiryTimestamp: calcExpiry(expiry),\n },\n chainId,\n },\n expiry,\n throwOnFailedPublish: true,\n appLink,\n }).catch((error) => reject(error));\n\n this.client.events.emit(\"session_request_sent\", {\n topic,\n request,\n chainId,\n id: clientRpcId,\n });\n const result = await done();\n return result;\n }\n\n const protocolRequestParams: JsonRpcTypes.RequestParams[\"wc_sessionRequest\"] = {\n request: {\n ...request,\n expiryTimestamp: calcExpiry(expiry),\n },\n chainId,\n };\n\n return await Promise.all([\n new Promise<void>(async (resolve) => {\n await this.sendRequest({\n clientRpcId,\n relayRpcId,\n topic,\n method: protocolMethod,\n params: protocolRequestParams,\n expiry,\n throwOnFailedPublish: true,\n tvf: this.getTVFParams(clientRpcId, protocolRequestParams),\n }).catch((error) => reject(error));\n this.client.events.emit(\"session_request_sent\", {\n topic,\n request,\n chainId,\n id: clientRpcId,\n });\n resolve();\n }),\n new Promise<void>(async (resolve) => {\n // only attempt to handle deeplinks if they are not explicitly disabled in the session config\n if (!session.sessionConfig?.disableDeepLink) {\n const wcDeepLink = (await getDeepLink(\n this.client.core.storage,\n WALLETCONNECT_DEEPLINK_CHOICE,\n )) as string;\n await handleDeeplinkRedirect({ id: clientRpcId, topic, wcDeepLink });\n }\n resolve();\n }),\n done(),\n ]).then((result) => result[2]); // order is important here, we want to return the result of the `done` promise\n };\n\n public respond: IEngine[\"respond\"] = async (params) => {\n this.isInitialized();\n const event = this.client.core.eventClient.createEvent({\n properties: {\n topic: params?.topic || params?.response?.id?.toString(),\n trace: [EVENT_CLIENT_SESSION_TRACES.session_request_response_started],\n },\n });\n try {\n await this.isValidRespond(params);\n } catch (error) {\n event.addTrace((error as Error)?.message);\n event.setError(EVENT_CLIENT_SESSION_ERRORS.session_request_response_validation_failure);\n\n throw error;\n }\n\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_request_response_validation_success);\n\n const { topic, response } = params;\n const { id } = response;\n const session = this.client.session.get(topic);\n\n if (session.transportType === TRANSPORT_TYPES.relay) {\n await this.confirmOnlineStateOrThrow();\n }\n\n const appLink = this.getAppLinkIfEnabled(session.peer.metadata, session.transportType);\n try {\n event.addTrace(EVENT_CLIENT_SESSION_TRACES.session_request_response_publish_started);\n if (isJsonRpcResult(response)) {\n await this.sendResult({\n id,\n topic,\n result: response.result,\n throwOnFailedPublish: true,\n appLink,\n });\n } else if (isJsonRpcError(response)) {\n await this.sendError({ id, topic, error: response.error, appLink });\n }\n this.cleanupAfterResponse(params);\n } catch (error) {\n event.addTrace((error as Error)?.message);\n event.setError(EVENT_CLIENT_SESSION_ERRORS.session_request_response_publish_failure);\n throw error;\n }\n };\n\n public ping: IEngine[\"ping\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n try {\n await this.isValidPing(params);\n } catch (error) {\n this.client.logger.error(\"ping() -> isValidPing() failed\");\n throw error;\n }\n const { topic } = params;\n if (this.client.session.keys.includes(topic)) {\n const clientRpcId = payloadId();\n const relayRpcId = getBigIntRpcId().toString() as any;\n const { done, resolve, reject } = createDelayedPromise<void>(\n FIVE_MINUTES,\n \"Ping request expired without receiving any acknowledgement\",\n );\n this.events.once(engineEvent(\"session_ping\", clientRpcId), ({ error }: any) => {\n if (error) reject(error);\n else resolve();\n });\n await Promise.all([\n this.sendRequest({\n topic,\n method: \"wc_sessionPing\",\n params: {},\n throwOnFailedPublish: true,\n clientRpcId,\n relayRpcId,\n }),\n done(),\n ]);\n } else if (this.client.core.pairing.pairings.keys.includes(topic)) {\n this.client.logger.warn(\n \"ping() on pairing topic is deprecated and will be removed in the next major release.\",\n );\n await this.client.core.pairing.ping({ topic });\n }\n };\n\n public emit: IEngine[\"emit\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n await this.isValidEmit(params);\n const { topic, event, chainId } = params;\n const relayRpcId = getBigIntRpcId().toString() as any;\n const clientRpcId = payloadId();\n await this.sendRequest({\n topic,\n method: \"wc_sessionEvent\",\n params: { event, chainId },\n throwOnFailedPublish: true,\n relayRpcId,\n clientRpcId,\n });\n };\n\n public disconnect: IEngine[\"disconnect\"] = async (params) => {\n this.isInitialized();\n await this.confirmOnlineStateOrThrow();\n await this.isValidDisconnect(params);\n const { topic } = params;\n if (this.client.session.keys.includes(topic)) {\n // await an ack to ensure the relay has received the disconnect request\n await this.sendRequest({\n topic,\n method: \"wc_sessionDelete\",\n params: getSdkError(\"USER_DISCONNECTED\"),\n throwOnFailedPublish: true,\n });\n await this.deleteSession({ topic, emitEvent: false });\n } else if (this.client.core.pairing.pairings.keys.includes(topic)) {\n await this.client.core.pairing.disconnect({ topic });\n } else {\n const { message } = getInternalError(\n \"MISMATCHED_TOPIC\",\n `Session or pairing topic not found: ${topic}`,\n );\n throw new Error(message);\n }\n };\n\n public find: IEngine[\"find\"] = (params) => {\n this.isInitialized();\n return this.client.session.getAll().filter((session) => isSessionCompatible(session, params));\n };\n\n public getPendingSessionRequests: IEngine[\"getPendingSessionRequests\"] = () => {\n return this.client.pendingRequest.getAll();\n };\n\n // ---------- Auth ------------------------------------------------ //\n\n public authenticate: IEngine[\"authenticate\"] = async (params, walletUniversalLink) => {\n this.isInitialized();\n this.isValidAuthenticate(params);\n\n const isLinkMode =\n walletUniversalLink &&\n this.client.core.linkModeSupportedApps.includes(walletUniversalLink) &&\n this.client.metadata.redirect?.linkMode;\n\n const transportType: RelayerTypes.TransportType = isLinkMode\n ? TRANSPORT_TYPES.link_mode\n : TRANSPORT_TYPES.relay;\n\n if (transportType === TRANSPORT_TYPES.relay) {\n await this.confirmOnlineStateOrThrow();\n }\n\n const {\n chains,\n statement = \"\",\n uri,\n domain,\n nonce,\n type,\n exp,\n nbf,\n methods = [],\n expiry,\n } = params;\n // reassign resources to remove reference as the array is modified and might cause side effects\n const resources = [...(params.resources || [])];\n\n const { topic: pairingTopic, uri: connectionUri } = await this.client.core.pairing.create({\n methods: [\"wc_sessionAuthenticate\"],\n transportType,\n });\n\n this.client.logger.info({\n message: \"Generated new pairing\",\n pairing: { topic: pairingTopic, uri: connectionUri },\n });\n\n const publicKey = await this.client.core.crypto.generateKeyPair();\n const responseTopic = hashKey(publicKey);\n\n await Promise.all([\n this.client.auth.authKeys.set(AUTH_PUBLIC_KEY_NAME, { responseTopic, publicKey }),\n this.client.auth.pairingTopics.set(responseTopic, { topic: responseTopic, pairingTopic }),\n ]);\n\n // Subscribe to response topic\n await this.client.core.relayer.subscribe(responseTopic, { transportType });\n\n this.client.logger.info(`sending request to new pairing topic: ${pairingTopic}`);\n\n if (methods.length > 0) {\n const { namespace } = parseChainId(chains[0]);\n let recap = createEncodedRecap(namespace, \"request\", methods);\n const existingRecap = getRecapFromResources(resources);\n if (existingRecap) {\n // per Recaps spec, recap must occupy the last position in the resources array\n // using .pop to remove the element given we already checked it's a recap and will replace it\n const mergedRecap = mergeEncodedRecaps(recap, resources.pop() as string);\n recap = mergedRecap;\n }\n resources.push(recap);\n }\n\n // Ensure the expiry is greater than the minimum required for the request - currently 1h\n const authRequestExpiry =\n expiry && expiry > ENGINE_RPC_OPTS.wc_sessionAuthenticate.req.ttl\n ? expiry\n : ENGINE_RPC_OPTS.wc_sessionAuthenticate.req.ttl;\n\n const request: AuthTypes.SessionAuthenticateRequestParams = {\n authPayload: {\n type: type ?? \"caip122\",\n chains,\n statement,\n aud: uri,\n domain,\n version: \"1\",\n nonce,\n iat: new Date().toISOString(),\n exp,\n nbf,\n resources,\n },\n requester: { publicKey, metadata: this.client.metadata },\n expiryTimestamp: calcExpiry(authRequestExpiry),\n };\n\n // ----- build namespaces for fallback session proposal ----- //\n const namespaces = {\n eip155: {\n chains,\n // request `personal_sign` method by default to allow for fallback siwe\n methods: [...new Set([\"personal_sign\", ...methods])],\n events: [\"chainChanged\", \"accountsChanged\"],\n },\n };\n\n const proposal = {\n requiredNamespaces: {},\n optionalNamespaces: namespaces,\n relays: [{ protocol: \"irn\" }],\n pairingTopic,\n proposer: {\n publicKey,\n metadata: this.client.metadata,\n },\n expiryTimestamp: calcExpiry(ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl),\n id: payloadId(),\n };\n\n const { done, resolve, reject } = createDelayedPromise(authRequestExpiry, \"Request expired\");\n\n const authenticateId = payloadId();\n const sessionConnectEventTarget = engineEvent(\"session_connect\", proposal.id);\n const authenticateEventTarget = engineEvent(\"session_request\", authenticateId);\n\n // handle fallback session proposal response\n const onSessionConnect = async ({ error, session }: any) => {\n // cleanup listener for authenticate response\n this.events.off(authenticateEventTarget, onAuthenticate);\n if (error) reject(error);\n else if (session) {\n resolve({\n session,\n });\n }\n };\n // handle session authenticate response\n const onAuthenticate = async (payload: any) => {\n // delete this auth request on response\n // we're using payload from the wallet to establish the session so we don't need to keep this around\n await this.deletePendingAuthRequest(authenticateId, { message: \"fulfilled\", code: 0 });\n if (payload.error) {\n // wallets that do not support wc_sessionAuthenticate will return an error\n // we should not reject the promise in this case as the fallback session proposal will be used\n const error = getSdkError(\"WC_METHOD_UNSUPPORTED\", \"wc_sessionAuthenticate\");\n if (payload.error.code === error.code) return;\n\n // cleanup listener for fallback response\n this.events.off(sessionConnectEventTarget, onSessionConnect);\n return reject(payload.error.message);\n }\n // delete fallback proposal on successful authenticate as the proposal will not be responded to\n await this.deleteProposal(proposal.id);\n // cleanup listener for fallback response\n this.events.off(sessionConnectEventTarget, onSessionConnect);\n\n const {\n cacaos,\n responder,\n }: {\n cacaos: AuthTypes.SessionAuthenticateResponseParams[\"cacaos\"];\n responder: AuthTypes.SessionAuthenticateResponseParams[\"responder\"];\n } = payload.result;\n\n const approvedMethods: string[] = [];\n const approvedAccounts: string[] = [];\n for (const cacao of cacaos) {\n const isValid = await validateSignedCacao({ cacao, projectId: this.client.core.projectId });\n if (!isValid) {\n this.client.logger.error(cacao, \"Signature verification failed\");\n reject(getSdkError(\"SESSION_SETTLEMENT_FAILED\", \"Signature verification failed\"));\n }\n\n const { p: payload } = cacao;\n const recap = getRecapFromResources(payload.resources);\n\n const approvedChains: string[] = [getNamespacedDidChainId(payload.iss) as string];\n const parsedAddress = getDidAddress(payload.iss) as string;\n\n if (recap) {\n const methodsfromRecap = getMethodsFromRecap(recap);\n const chainsFromRecap = getChainsFromRecap(recap);\n approvedMethods.push(...methodsfromRecap);\n approvedChains.push(...chainsFromRecap);\n }\n\n for (const chain of approvedChains) {\n approvedAccounts.push(`${chain}:${parsedAddress}`);\n }\n }\n const sessionTopic = await this.client.core.crypto.generateSharedKey(\n publicKey,\n responder.publicKey,\n );\n\n //create session object\n let session: SessionTypes.Struct | undefined;\n\n if (approvedMethods.length > 0) {\n session = {\n topic: sessionTopic,\n acknowledged: true,\n self: {\n publicKey,\n metadata: this.client.metadata,\n },\n peer: responder,\n controller: responder.publicKey,\n expiry: calcExpiry(SESSION_EXPIRY),\n requiredNamespaces: {},\n optionalNamespaces: {},\n relay: { protocol: \"irn\" },\n pairingTopic,\n namespaces: buildNamespacesFromAuth(\n [...new Set(approvedMethods)],\n [...new Set(approvedAccounts)],\n ),\n transportType,\n };\n\n await this.client.core.relayer.subscribe(sessionTopic, { transportType });\n await this.client.session.set(sessionTopic, session);\n if (pairingTopic) {\n await this.client.core.pairing.updateMetadata({\n topic: pairingTopic,\n metadata: responder.metadata,\n });\n }\n\n session = this.client.session.get(sessionTopic);\n }\n\n if (\n this.client.metadata.redirect?.linkMode &&\n responder.metadata.redirect?.linkMode &&\n responder.metadata.redirect?.universal &&\n walletUniversalLink\n ) {\n // save wallet link in array of apps that support linkMode\n this.client.core.addLinkModeSupportedApp(responder.metadata.redirect.universal);\n\n this.client.session.update(sessionTopic, {\n transportType: TRANSPORT_TYPES.link_mode,\n });\n }\n\n resolve({\n auths: cacaos,\n session,\n });\n };\n\n // subscribe to response events\n this.events.once<\"session_connect\">(sessionConnectEventTarget, onSessionConnect);\n this.events.once(authenticateEventTarget, onAuthenticate);\n\n let linkModeURL;\n try {\n if (isLinkMode) {\n const payload = formatJsonRpcRequest(\"wc_sessionAuthenticate\", request, authenticateId);\n this.client.core.history.set(pairingTopic, payload);\n const message = await this.client.core.crypto.encode(\"\", payload, {\n type: TYPE_2,\n encoding: BASE64URL,\n });\n linkModeURL = getLinkModeURL(walletUniversalLink, pairingTopic, message);\n } else {\n // send both (main & fallback) requests\n await Promise.all([\n this.sendRequest({\n topic: pairingTopic,\n method: \"wc_sessionAuthenticate\",\n params: request,\n expiry: params.expiry,\n throwOnFailedPublish: true,\n clientRpcId: authenticateId,\n }),\n this.sendRequest({\n topic: pairingTopic,\n method: \"wc_sessionPropose\",\n params: proposal,\n expiry: ENGINE_RPC_OPTS.wc_sessionPropose.req.ttl,\n throwOnFailedPublish: true,\n clientRpcId: proposal.id,\n }),\n ]);\n }\n } catch (error) {\n // cleanup listeners on failed publish\n this.events.off(sessionConnectEventTarget, onSessionConnect);\n this.events.off(authenticateEventTarget, onAuthenticate);\n throw error;\n }\n\n await this.setProposal(proposal.id, proposal);\n await this.setAuthRequest(authenticateId, {\n request: {\n ...request,\n verifyContext: {} as any,\n },\n pairingTopic,\n transportType,\n });\n\n return {\n uri: linkModeURL ?? connectionUri,\n response: done,\n } as EngineTypes.SessionAuthenticateResponsePromise;\n };\n\n public approveSessionAuthenticate: IEngine[\"approveSessionAuthenticate\"] = async (\n sessionAuthenticateResponseParams,\n ) => {\n const { id, auths } = sessionAuthenticateResponseParams;\n\n const event = this.client.core.eventClient.createEvent({\n properties: {\n topic: id.toString(),\n trace: [EVENT_CLIENT_AUTHENTICATE_TRACES.authenticated_session_approve_started],\n },\n });\n\n try {\n this.isInitialized();\n } catch (error) {\n event.setError(EVENT_CLIENT_AUTHENTICATE_ERRORS.no_internet_connection);\n throw error;\n }\n\n const pendingRequest = this.getPendingAuthRequest(id);\n\n if (!pendingRequest) {\n event.setError(\n EVENT_CLIENT_AUTHENTICATE_ERRORS.authenticated_session_pending_request_not_found,\n );\n throw new Error(`Could not find pending auth request with id ${id}`);\n }\n\n const transportType = pendingRequest.transportType || TRANSPORT_TYPES.relay;\n if (transportType === TRANSPORT_TYPES.relay) {\n await this.confirmOnlineStateOrThrow();\n }\n\n const receiverPublicKey = pendingRequest.requester.publicKey;\n const senderPublicKey = await this.client.core.crypto.generateKeyPair();\n const responseTopic = hashKey(receiverPublicKey);\n\n const encodeOpts = {\n type: TYPE_1,\n receiverPublicKey,\n senderPublicKey,\n };\n\n const approvedMethods: string[] = [];\n const approvedAccounts: string[] = [];\n for (const cacao of auths) {\n const isValid = await validateSignedCacao({ cacao, projectId: this.client.core.projectId });\n if (!isValid) {\n event.setError(EVENT_CLIENT_AUTHENTICATE_ERRORS.invalid_cacao);\n\n const invalidErr = getSdkError(\n \"SESSION_SETTLEMENT_FAILED\",\n \"Signature verification failed\",\n );\n\n await this.sendError({\n id,\n topic: responseTopic,\n error: invalidErr,\n encodeOpts,\n });\n\n throw new Error(invalidErr.message);\n }\n\n event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.cacaos_verified);\n\n const { p: payload } = cacao;\n const recap = getRecapFromResources(payload.resources);\n\n const approvedChains: string[] = [getNamespacedDidChainId(payload.iss) as string];\n\n const parsedAddress = getDidAddress(payload.iss) as string;\n\n if (recap) {\n const methodsfromRecap = getMethodsFromRecap(recap);\n const chainsFromRecap = getChainsFromRecap(recap);\n approvedMethods.push(...methodsfromRecap);\n approvedChains.push(...chainsFromRecap);\n }\n for (const chain of approvedChains) {\n approvedAccounts.push(`${chain}:${parsedAddress}`);\n }\n }\n\n const sessionTopic = await this.client.core.crypto.generateSharedKey(\n senderPublicKey,\n receiverPublicKey,\n );\n\n event.addTrace(EVENT_CLIENT_AUTHENTICATE_TRACES.create_authenticated_session_topic);\n\n let session: SessionTypes.Struct | undefined;\n if (approvedMethods?.length > 0) {\n session = {\n topic: sessionTopic,\n acknowledged: true,\n self: {\n publicKey: senderPublicKey,\n metadata: this.client.metadata,\n },\n peer: {\n publicKey: receiverPublicKey,\n metadata: pendingReques