UNPKG

@walletconnect/utils

Version:
1 lines • 156 kB
{"version":3,"file":"index.cjs","sources":["../src/caip.ts","../src/errors.ts","../src/misc.ts","../src/signatures.ts","../src/cacao.ts","../src/crypto.ts","../src/relay.ts","../src/uri.ts","../src/namespaces.ts","../src/validators.ts","../src/network.ts","../src/memoryStore.ts","../src/polkadot.ts","../src/logger.ts"],"sourcesContent":["import { SessionTypes, ProposalTypes } from \"@walletconnect/types\";\n\ninterface ChainIdParams {\n namespace: string;\n reference: string;\n}\n\ninterface AccountIdParams extends ChainIdParams {\n address: string;\n}\n\nconst CAIP_DELIMITER = \":\";\n\nexport function parseChainId(chain: string): ChainIdParams {\n const [namespace, reference] = chain.split(CAIP_DELIMITER);\n return { namespace, reference };\n}\n\nexport function formatChainId(params: ChainIdParams): string {\n const { namespace, reference } = params;\n return [namespace, reference].join(CAIP_DELIMITER);\n}\n\nexport function parseAccountId(account: string): AccountIdParams {\n const [namespace, reference, address] = account.split(CAIP_DELIMITER);\n return { namespace, reference, address };\n}\n\nexport function formatAccountId(params: AccountIdParams): string {\n const { namespace, reference, address } = params;\n return [namespace, reference, address].join(CAIP_DELIMITER);\n}\n\nexport function getUniqueValues(array: string[], parser: (str: string) => string): string[] {\n const unique: string[] = [];\n array.forEach((str) => {\n const value = parser(str);\n if (!unique.includes(value)) unique.push(value);\n });\n return unique;\n}\n\nexport function getAddressFromAccount(account: string) {\n const { address } = parseAccountId(account);\n return address;\n}\n\nexport function getChainFromAccount(account: string) {\n const { namespace, reference } = parseAccountId(account);\n const chain = formatChainId({ namespace, reference });\n return chain;\n}\n\nexport function formatAccountWithChain(address: string, chain: string) {\n const { namespace, reference } = parseChainId(chain);\n const account = formatAccountId({ namespace, reference, address });\n return account;\n}\n\nexport function getAddressesFromAccounts(accounts: string[]) {\n return getUniqueValues(accounts, getAddressFromAccount);\n}\n\nexport function getChainsFromAccounts(accounts: string[]) {\n return getUniqueValues(accounts, getChainFromAccount);\n}\n\nexport function getAccountsFromNamespaces(\n namespaces: SessionTypes.Namespaces,\n keys: string[] = [],\n): string[] {\n const accounts: string[] = [];\n Object.keys(namespaces).forEach((key) => {\n if (keys.length && !keys.includes(key)) return;\n const ns = namespaces[key];\n accounts.push(...ns.accounts);\n });\n return accounts;\n}\n\nexport function getChainsFromNamespaces(\n namespaces: SessionTypes.Namespaces,\n keys: string[] = [],\n): string[] {\n const chains: string[] = [];\n Object.keys(namespaces).forEach((key) => {\n if (keys.length && !keys.includes(key)) return;\n const ns = namespaces[key];\n chains.push(...getChainsFromAccounts(ns.accounts));\n });\n return chains;\n}\n\nexport function getChainsFromRequiredNamespaces(\n requiredNamespaces: ProposalTypes.RequiredNamespaces,\n keys: string[] = [],\n): string[] {\n const chains: string[] = [];\n Object.keys(requiredNamespaces).forEach((key) => {\n if (keys.length && !keys.includes(key)) return;\n const ns = requiredNamespaces[key];\n chains.push(...getChainsFromNamespace(key, ns));\n });\n return chains;\n}\n\nexport function getChainsFromNamespace(\n namespace: string,\n namespaceProps: ProposalTypes.BaseRequiredNamespace,\n) {\n // check if chainId is provided in the key as `eip155:1` or in the namespace as chains[]\n return namespace.includes(\":\") ? [namespace] : namespaceProps.chains || [];\n}\n","/**\n * Types\n */\nexport type SdkErrorKey = keyof typeof SDK_ERRORS;\nexport type InternalErrorKey = keyof typeof INTERNAL_ERRORS;\n\n/**\n * Constants\n */\nexport const SDK_ERRORS = {\n /* ----- INVALID (1xxx) ----- */\n INVALID_METHOD: {\n message: \"Invalid method.\",\n code: 1001,\n },\n INVALID_EVENT: {\n message: \"Invalid event.\",\n code: 1002,\n },\n INVALID_UPDATE_REQUEST: {\n message: \"Invalid update request.\",\n code: 1003,\n },\n INVALID_EXTEND_REQUEST: {\n message: \"Invalid extend request.\",\n code: 1004,\n },\n INVALID_SESSION_SETTLE_REQUEST: {\n message: \"Invalid session settle request.\",\n code: 1005,\n },\n /* ----- UNAUTHORIZED (3xxx) ----- */\n UNAUTHORIZED_METHOD: {\n message: \"Unauthorized method.\",\n code: 3001,\n },\n UNAUTHORIZED_EVENT: {\n message: \"Unauthorized event.\",\n code: 3002,\n },\n UNAUTHORIZED_UPDATE_REQUEST: {\n message: \"Unauthorized update request.\",\n code: 3003,\n },\n UNAUTHORIZED_EXTEND_REQUEST: {\n message: \"Unauthorized extend request.\",\n code: 3004,\n },\n /* ----- REJECTED (5xxx) ----- */\n USER_REJECTED: {\n message: \"User rejected.\",\n code: 5000,\n },\n USER_REJECTED_CHAINS: {\n message: \"User rejected chains.\",\n code: 5001,\n },\n USER_REJECTED_METHODS: {\n message: \"User rejected methods.\",\n code: 5002,\n },\n USER_REJECTED_EVENTS: {\n message: \"User rejected events.\",\n code: 5003,\n },\n UNSUPPORTED_CHAINS: {\n message: \"Unsupported chains.\",\n code: 5100,\n },\n UNSUPPORTED_METHODS: {\n message: \"Unsupported methods.\",\n code: 5101,\n },\n UNSUPPORTED_EVENTS: {\n message: \"Unsupported events.\",\n code: 5102,\n },\n UNSUPPORTED_ACCOUNTS: {\n message: \"Unsupported accounts.\",\n code: 5103,\n },\n UNSUPPORTED_NAMESPACE_KEY: {\n message: \"Unsupported namespace key.\",\n code: 5104,\n },\n /* ----- REASON (6xxx) ----- */\n USER_DISCONNECTED: {\n message: \"User disconnected.\",\n code: 6000,\n },\n /* ----- FAILURE (7xxx) ----- */\n SESSION_SETTLEMENT_FAILED: {\n message: \"Session settlement failed.\",\n code: 7000,\n },\n /* ----- PAIRING (10xxx) ----- */\n WC_METHOD_UNSUPPORTED: {\n message: \"Unsupported wc_ method.\",\n code: 10001,\n },\n};\n\nexport const INTERNAL_ERRORS = {\n NOT_INITIALIZED: {\n message: \"Not initialized.\",\n code: 1,\n },\n NO_MATCHING_KEY: {\n message: \"No matching key.\",\n code: 2,\n },\n RESTORE_WILL_OVERRIDE: {\n message: \"Restore will override.\",\n code: 3,\n },\n RESUBSCRIBED: {\n message: \"Resubscribed.\",\n code: 4,\n },\n MISSING_OR_INVALID: {\n message: \"Missing or invalid.\",\n code: 5,\n },\n EXPIRED: {\n message: \"Expired.\",\n code: 6,\n },\n UNKNOWN_TYPE: {\n message: \"Unknown type.\",\n code: 7,\n },\n MISMATCHED_TOPIC: {\n message: \"Mismatched topic.\",\n code: 8,\n },\n NON_CONFORMING_NAMESPACES: {\n message: \"Non conforming namespaces.\",\n code: 9,\n },\n};\n\n/**\n * Utilities\n */\nexport function getInternalError(key: InternalErrorKey, context?: string | number) {\n const { message, code } = INTERNAL_ERRORS[key];\n return {\n message: context ? `${message} ${context}` : message,\n code,\n };\n}\n\nexport function getSdkError(key: SdkErrorKey, context?: string | number) {\n const { message, code } = SDK_ERRORS[key];\n return {\n message: context ? `${message} ${context}` : message,\n code,\n };\n}\n\nexport interface SDKError extends Error {\n code?: number;\n}\n","import { detect } from \"detect-browser\";\nimport { FIVE_MINUTES, fromMiliseconds, toMiliseconds } from \"@walletconnect/time\";\nimport {\n SignClientTypes,\n RelayerClientMetadata,\n EngineTypes,\n RelayerTypes,\n} from \"@walletconnect/types\";\nimport { getDocument, getLocation, getNavigator } from \"@walletconnect/window-getters\";\nimport { getWindowMetadata } from \"@walletconnect/window-metadata\";\nimport { ErrorResponse } from \"@walletconnect/jsonrpc-utils\";\nimport { IKeyValueStorage } from \"@walletconnect/keyvaluestorage\";\n\nimport { getInternalError, SDKError } from \"./errors.js\";\n\n// -- constants -----------------------------------------//\nexport const REACT_NATIVE_PRODUCT = \"ReactNative\";\n\nexport const ENV_MAP = {\n reactNative: \"react-native\",\n node: \"node\",\n browser: \"browser\",\n unknown: \"unknown\",\n};\n\nexport const EMPTY_SPACE = \" \";\n\nexport const COLON = \":\";\n\nexport const SLASH = \"/\";\n\nexport const DEFAULT_DEPTH = 2;\n\nexport const ONE_THOUSAND = 1000;\n\nexport const SDK_TYPE = \"js\";\n\n// -- env -----------------------------------------------//\n\nexport function isNode(): boolean {\n return (\n typeof process !== \"undefined\" &&\n typeof process.versions !== \"undefined\" &&\n typeof process.versions.node !== \"undefined\"\n );\n}\n\nexport function isReactNative(): boolean {\n return !getDocument() && !!getNavigator() && navigator.product === REACT_NATIVE_PRODUCT;\n}\n\nexport function isAndroid(): boolean {\n return (\n isReactNative() &&\n typeof global !== \"undefined\" &&\n typeof (global as any)?.Platform !== \"undefined\" &&\n (global as any)?.Platform.OS === \"android\"\n );\n}\n\nexport function isIos(): boolean {\n return (\n isReactNative() &&\n typeof global !== \"undefined\" &&\n typeof (global as any)?.Platform !== \"undefined\" &&\n (global as any)?.Platform.OS === \"ios\"\n );\n}\n\nexport function isBrowser(): boolean {\n return !isNode() && !!getNavigator() && !!getDocument();\n}\n\nexport function getEnvironment(): string {\n if (isReactNative()) return ENV_MAP.reactNative;\n if (isNode()) return ENV_MAP.node;\n if (isBrowser()) return ENV_MAP.browser;\n return ENV_MAP.unknown;\n}\n\nexport function getAppId(): string | undefined {\n try {\n if (\n isReactNative() &&\n typeof global !== \"undefined\" &&\n typeof (global as any)?.Application !== \"undefined\"\n ) {\n return (global as any).Application?.applicationId;\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\n// -- query -----------------------------------------------//\n\nexport function appendToQueryString(\n queryString: string,\n newQueryParams: Record<string, string | number | boolean | undefined>,\n): string {\n const urlSearchParams = new URLSearchParams(queryString);\n\n Object.entries(newQueryParams)\n .sort(([a], [b]) => a.localeCompare(b))\n .forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n urlSearchParams.set(key, String(value));\n }\n });\n\n return urlSearchParams.toString();\n}\n\n// -- metadata ----------------------------------------------//\n\nexport function populateAppMetadata(metadata?: SignClientTypes.Metadata): SignClientTypes.Metadata {\n const appMetadata = getAppMetadata();\n try {\n if (metadata?.url && appMetadata.url) {\n if (new URL(metadata.url).host !== new URL(appMetadata.url).host) {\n console.warn(\n `The configured WalletConnect 'metadata.url':${metadata.url} differs from the actual page url:${appMetadata.url}. This is probably unintended and can lead to issues.`,\n );\n metadata.url = appMetadata.url;\n }\n }\n\n if (metadata?.icons?.length && metadata.icons.length > 0) {\n metadata.icons = metadata.icons.filter((icon) => icon !== \"\");\n }\n\n return {\n ...appMetadata,\n ...metadata,\n url: metadata?.url || appMetadata.url,\n name: metadata?.name || appMetadata.name,\n description: metadata?.description || appMetadata.description,\n icons:\n metadata?.icons?.length && metadata.icons.length > 0 ? metadata.icons : appMetadata.icons,\n };\n } catch (error) {\n console.warn(\"Error populating app metadata\", error);\n return metadata || appMetadata;\n }\n}\n\nexport function getAppMetadata(): SignClientTypes.Metadata {\n return (\n getWindowMetadata() || {\n name: \"\",\n description: \"\",\n url: \"\",\n icons: [\"\"],\n }\n );\n}\n\nexport function getRelayClientMetadata(protocol: string, version: number): RelayerClientMetadata {\n const env = getEnvironment();\n\n const metadata: RelayerClientMetadata = { protocol, version, env };\n if (env === \"browser\") {\n metadata.host = getLocation()?.host || \"unknown\";\n }\n return metadata;\n}\n\n// -- rpcUrl ----------------------------------------------//\n\nexport function getJavascriptOS() {\n const env = getEnvironment();\n // global.Platform is set by react-native-compat\n if (\n env === ENV_MAP.reactNative &&\n typeof global !== \"undefined\" &&\n typeof (global as any)?.Platform !== \"undefined\"\n ) {\n const { OS, Version } = (global as any).Platform;\n return [OS, Version].join(\"-\");\n }\n\n const info = detect();\n if (info === null) return \"unknown\";\n const os = info.os ? info.os.replace(\" \", \"\").toLowerCase() : \"unknown\";\n if (info.type === \"browser\") {\n return [os, info.name, info.version].join(\"-\");\n }\n return [os, info.version].join(\"-\");\n}\n\nexport function getJavascriptID() {\n const env = getEnvironment();\n return env === ENV_MAP.browser ? [env, getLocation()?.host || \"unknown\"].join(\":\") : env;\n}\n\nexport function formatUA(protocol: string, version: number, sdkVersion: string) {\n const os = getJavascriptOS();\n const id = getJavascriptID();\n return [[protocol, version].join(\"-\"), [SDK_TYPE, sdkVersion].join(\"-\"), os, id].join(\"/\");\n}\nconsole;\n\nexport function formatRelayRpcUrl({\n protocol,\n version,\n relayUrl,\n sdkVersion,\n auth,\n projectId,\n useOnCloseEvent,\n bundleId,\n packageName,\n}: RelayerTypes.RpcUrlParams) {\n const splitUrl = relayUrl.split(\"?\");\n const ua = formatUA(protocol, version, sdkVersion);\n const params = {\n auth,\n ua,\n projectId,\n useOnCloseEvent: useOnCloseEvent || undefined,\n packageName: packageName || undefined,\n bundleId: bundleId || undefined,\n };\n const queryString = appendToQueryString(splitUrl[1] || \"\", params);\n return splitUrl[0] + \"?\" + queryString;\n}\n\nexport function getHttpUrl(url: string) {\n // regex from https://stackoverflow.com/questions/3883871/regexp-to-grab-protocol-from-url\n const matches = url.match(/^[^:]+(?=:\\/\\/)/gi) || [];\n let protocol = matches[0];\n const domain = typeof protocol !== \"undefined\" ? url.split(\"://\")[1] : url;\n protocol = protocol === \"wss\" ? \"https\" : \"http\";\n return [protocol, domain].join(\"://\");\n}\n\n// -- assert ------------------------------------------------- //\n\nexport function assertType(obj: any, key: string, type: string) {\n // eslint-disable-next-line valid-typeof\n if (!obj[key] || typeof obj[key] !== type) {\n throw new Error(`Missing or invalid \"${key}\" param`);\n }\n}\n\n// -- context ------------------------------------------------- //\n\nexport function parseContextNames(context: string, depth = DEFAULT_DEPTH) {\n return getLastItems(context.split(SLASH), depth);\n}\n\nexport function formatMessageContext(context: string): string {\n return parseContextNames(context).join(EMPTY_SPACE);\n}\n\n// -- array ------------------------------------------------- //\n\nexport function hasOverlap(a: any[], b: any[]): boolean {\n const matches = a.filter((x) => b.includes(x));\n return matches.length === a.length;\n}\n\nexport function getLastItems(arr: any[], depth = DEFAULT_DEPTH): any[] {\n return arr.slice(Math.max(arr.length - depth, 0));\n}\n\n// -- map ------------------------------------------------- //\n\nexport function mapToObj<T = any>(map: Map<string, T>): Record<string, T> {\n return Object.fromEntries(map.entries());\n}\n\nexport function objToMap<T = any>(obj: Record<string, T>): Map<string, T> {\n return new Map<string, T>(Object.entries<T>(obj));\n}\n\nexport function mapEntries<A = any, B = any>(\n obj: Record<string, A>,\n cb: (x: A) => B,\n): Record<string, B> {\n const res: any = {};\n Object.keys(obj).forEach((key) => {\n res[key] = cb(obj[key]);\n });\n return res;\n}\n\n// -- enum ------------------------------------------------- //\n\n// source: https://github.com/microsoft/TypeScript/issues/3192#issuecomment-261720275\nexport const enumify = <T extends { [index: string]: U }, U extends string>(x: T): T => x;\n\n// -- string ------------------------------------------------- //\n\nexport function capitalizeWord(word: string) {\n return word.trim().replace(/^\\w/, (c) => c.toUpperCase());\n}\n\nexport function capitalize(str: string) {\n return str\n .split(EMPTY_SPACE)\n .map((w) => capitalizeWord(w))\n .join(EMPTY_SPACE);\n}\n\n// -- promises --------------------------------------------- //\nexport function createDelayedPromise<T>(\n expiry: number = FIVE_MINUTES,\n expireErrorMessage?: string,\n) {\n const timeout = toMiliseconds(expiry || FIVE_MINUTES);\n let cacheResolve: undefined | ((value: T | PromiseLike<T>) => void);\n let cacheReject: undefined | ((value?: ErrorResponse) => void);\n let cacheTimeout: undefined | NodeJS.Timeout;\n let result: Promise<Awaited<T>> | Promise<T> | undefined;\n\n const done = () =>\n new Promise<T>((promiseResolve, promiseReject) => {\n if (result) {\n return promiseResolve(result);\n }\n cacheTimeout = setTimeout(() => {\n const expiredError = getInternalError(\"EXPIRED\");\n const err = new Error(expireErrorMessage || expiredError.message) as SDKError;\n err.code = expiredError.code;\n promiseReject(err);\n }, timeout);\n cacheResolve = promiseResolve;\n cacheReject = promiseReject;\n });\n const resolve = (value?: T) => {\n if (cacheTimeout && cacheResolve) {\n clearTimeout(cacheTimeout);\n cacheResolve(value as T);\n result = Promise.resolve(value) as Promise<Awaited<T>>;\n }\n };\n const reject = (value?: ErrorResponse) => {\n if (cacheTimeout && cacheReject) {\n clearTimeout(cacheTimeout);\n cacheReject(value);\n }\n };\n\n return {\n resolve,\n reject,\n done,\n };\n}\n\nexport function createExpiringPromise<T>(\n promise: Promise<T>,\n expiry: number,\n expireErrorMessage?: string,\n) {\n return new Promise(async (resolve, reject) => {\n const timeout = setTimeout(() => reject(new Error(expireErrorMessage)), expiry);\n try {\n const result = await promise;\n resolve(result);\n } catch (error) {\n reject(error);\n }\n clearTimeout(timeout);\n });\n}\n\n// -- expirer --------------------------------------------- //\n\nexport function formatExpirerTarget(type: \"topic\" | \"id\", value: string | number): string {\n if (typeof value === \"string\" && value.startsWith(`${type}:`)) return value;\n if (type.toLowerCase() === \"topic\") {\n if (typeof value !== \"string\")\n throw new Error(`Value must be \"string\" for expirer target type: topic`);\n return `topic:${value}`;\n } else if (type.toLowerCase() === \"id\") {\n if (typeof value !== \"number\")\n throw new Error(`Value must be \"number\" for expirer target type: id`);\n return `id:${value}`;\n }\n throw new Error(`Unknown expirer target type: ${type}`);\n}\n\nexport function formatTopicTarget(topic: string): string {\n return formatExpirerTarget(\"topic\", topic);\n}\n\nexport function formatIdTarget(id: number): string {\n return formatExpirerTarget(\"id\", id);\n}\n\nexport function parseExpirerTarget(target: string) {\n const [type, value] = target.split(\":\");\n const parsed: { id?: number; topic?: string } = { id: undefined, topic: undefined };\n if (type === \"topic\" && typeof value === \"string\") {\n parsed.topic = value;\n } else if (type === \"id\" && Number.isInteger(Number(value))) {\n parsed.id = Number(value);\n } else {\n throw new Error(`Invalid target, expected id:number or topic:string, got ${type}:${value}`);\n }\n\n return parsed;\n}\n\nexport function calcExpiry(ttl: number, now?: number): number {\n return fromMiliseconds((now || Date.now()) + toMiliseconds(ttl));\n}\n\nexport function isExpired(expiry: number) {\n return Date.now() >= toMiliseconds(expiry);\n}\n\n// -- events ---------------------------------------------- //\n\nexport function engineEvent(event: EngineTypes.Event, id?: number | string | undefined) {\n return `${event}${id ? `:${id}` : \"\"}`;\n}\n\nexport function mergeArrays<T>(a: T[] = [], b: T[] = []): T[] {\n return [...new Set([...a, ...b])];\n}\n\nexport async function handleDeeplinkRedirect({\n id,\n topic,\n wcDeepLink,\n}: {\n id: number;\n topic: string;\n wcDeepLink: string;\n}) {\n try {\n if (!wcDeepLink) return;\n\n const json = typeof wcDeepLink === \"string\" ? JSON.parse(wcDeepLink) : wcDeepLink;\n const deeplink = json?.href;\n if (typeof deeplink !== \"string\") return;\n const link = formatDeeplinkUrl(deeplink, id, topic);\n const env = getEnvironment();\n\n if (env === ENV_MAP.browser) {\n if (!getDocument()?.hasFocus()) {\n console.warn(\"Document does not have focus, skipping deeplink.\");\n return;\n }\n\n openDeeplink(link);\n } else if (env === ENV_MAP.reactNative) {\n // global.Linking is set by react-native-compat\n if (typeof (global as any)?.Linking !== \"undefined\") {\n await (global as any).Linking.openURL(link);\n }\n }\n } catch (err) {\n // Silent error, just log in console\n // eslint-disable-next-line no-console\n console.error(err);\n }\n}\n\nexport function formatDeeplinkUrl(deeplink: string, requestId: number, sessionTopic: string) {\n const payload = `requestId=${requestId}&sessionTopic=${sessionTopic}`;\n if (deeplink.endsWith(\"/\")) deeplink = deeplink.slice(0, -1);\n let link = `${deeplink}`;\n if (deeplink.startsWith(\"https://t.me\")) {\n const startApp = deeplink.includes(\"?\") ? \"&startapp=\" : \"?startapp=\";\n link = `${link}${startApp}${toBase64(payload, true)}`;\n } else {\n link = `${link}/wc?${payload}`;\n }\n return link;\n}\n\nexport function openDeeplink(url: string) {\n let target = \"_self\";\n if (isIframe()) {\n target = \"_top\";\n } else if (isTelegram() || url.startsWith(\"https://\") || url.startsWith(\"http://\")) {\n target = \"_blank\";\n }\n\n window.open(url, target, \"noreferrer noopener\");\n}\n\nexport async function getDeepLink(storage: IKeyValueStorage, key: string) {\n let link: string | undefined = \"\";\n try {\n if (isBrowser()) {\n link = localStorage.getItem(key) as string;\n if (link) return link;\n }\n link = await storage.getItem(key);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error(err);\n }\n return link;\n}\n\nexport function getCommonValuesInArrays<T = string | number | boolean>(arr1: T[], arr2: T[]): T[] {\n return arr1.filter((value) => arr2.includes(value));\n}\n\nexport function getSearchParamFromURL(url: string, param: any) {\n const include = url.includes(param);\n if (!include) return null;\n const params = url.split(/([&,?,=])/);\n const index = params.indexOf(param);\n const value = params[index + 2];\n return value;\n}\n\nexport function uuidv4() {\n if (typeof crypto !== \"undefined\" && crypto?.randomUUID) {\n return crypto.randomUUID();\n }\n\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/gu, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n\n return v.toString(16);\n });\n}\n\nexport function isTestRun() {\n return typeof process !== \"undefined\" && process.env.IS_VITEST === \"true\";\n}\n\nexport function isTelegram() {\n return (\n typeof window !== \"undefined\" &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (Boolean((window as any).TelegramWebviewProxy) ||\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Boolean((window as any).Telegram) ||\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Boolean((window as any).TelegramWebviewProxyProto))\n );\n}\n\nexport function isIframe() {\n try {\n return window.self !== window.top;\n } catch {\n return false;\n }\n}\n\nexport function toBase64(input: string, removePadding = false): string {\n const bytes = new TextEncoder().encode(input);\n const chars = new Array<string>(bytes.length);\n for (let i = 0; i < bytes.length; i++) {\n chars[i] = String.fromCharCode(bytes[i]);\n }\n const encoded = btoa(chars.join(\"\"));\n return removePadding ? encoded.replace(/[=]/g, \"\") : encoded;\n}\n\nexport function fromBase64(encodedString: string): string {\n const padded = encodedString + \"=\".repeat((4 - (encodedString.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n\nexport function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport class LimitedSet<T> {\n private limit: number;\n private set: Set<T>;\n\n constructor({ limit }: { limit: number }) {\n this.limit = limit;\n this.set = new Set<T>();\n }\n\n add(item: T) {\n if (this.set.has(item)) return;\n\n if (this.set.size >= this.limit) {\n // Remove the oldest entry (FIFO)\n const firstKey = this.set.values().next().value;\n if (firstKey) {\n this.set.delete(firstKey);\n }\n }\n\n this.set.add(item);\n }\n\n has(item: T) {\n return this.set.has(item);\n }\n}\n","import { keccak_256 } from \"@noble/hashes/sha3\";\nimport { Secp256k1, Signature } from \"ox\";\nimport { sha256, sha512_256 } from \"@noble/hashes/sha2\";\nimport { blake2b } from \"@noble/hashes/blake2\";\nimport { encode as msgpackEncode, decode as msgpackDecode } from \"@msgpack/msgpack\";\nimport { base32, base58 } from \"@scure/base\";\nimport { concat, toString } from \"uint8arrays\";\nimport { AuthTypes } from \"@walletconnect/types\";\n\nimport { parseChainId } from \"./caip.js\";\n\nconst DEFAULT_RPC_URL = \"https://rpc.walletconnect.org/v1\";\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const padded = b64 + \"=\".repeat((4 - (b64.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nexport function hashEthereumMessage(message: string) {\n const prefix = `\\x19Ethereum Signed Message:\\n${message.length}`;\n const prefixedMessage = new TextEncoder().encode(prefix + message);\n return \"0x\" + toString(keccak_256(prefixedMessage), \"base16\");\n}\n\nexport async function verifySignature(\n address: string,\n reconstructedMessage: string,\n cacaoSignature: AuthTypes.CacaoSignature,\n chainId: string,\n projectId: string,\n baseRpcUrl?: string,\n): Promise<boolean> {\n // Determine if this signature is from an EOA or a contract.\n switch (cacaoSignature.t) {\n case \"eip191\":\n return await isValidEip191Signature(address, reconstructedMessage, cacaoSignature.s);\n case \"eip1271\":\n return await isValidEip1271Signature(\n address,\n reconstructedMessage,\n cacaoSignature.s,\n chainId,\n projectId,\n baseRpcUrl,\n );\n break;\n default:\n throw new Error(\n `verifySignature failed: Attempted to verify CacaoSignature with unknown type: ${cacaoSignature.t}`,\n );\n }\n}\n\nexport function isValidEip191Signature(\n address: string,\n message: string,\n signature: string,\n): boolean {\n const parsedSignature = Signature.fromHex(signature as `0x${string}`);\n const recoveredAddress = Secp256k1.recoverAddress({\n payload: hashEthereumMessage(message) as `0x${string}`,\n signature: parsedSignature,\n });\n return recoveredAddress.toLowerCase() === address.toLowerCase();\n}\n\nexport async function isValidEip1271Signature(\n address: string,\n reconstructedMessage: string,\n signature: string,\n chainId: string,\n projectId: string,\n baseRpcUrl?: string,\n) {\n const parsedChain = parseChainId(chainId);\n if (!parsedChain.namespace || !parsedChain.reference) {\n throw new Error(\n `isValidEip1271Signature failed: chainId must be in CAIP-2 format, received: ${chainId}`,\n );\n }\n try {\n const eip1271MagicValue = \"0x1626ba7e\";\n const dynamicTypeOffset = \"0000000000000000000000000000000000000000000000000000000000000040\";\n const nonPrefixedSignature = signature.substring(2);\n const dynamicTypeLength = (nonPrefixedSignature.length / 2).toString(16).padStart(64, \"0\");\n const nonPrefixedHashedMessage = (\n reconstructedMessage.startsWith(\"0x\")\n ? reconstructedMessage\n : hashEthereumMessage(reconstructedMessage)\n ).substring(2);\n const data =\n eip1271MagicValue +\n nonPrefixedHashedMessage +\n dynamicTypeOffset +\n dynamicTypeLength +\n nonPrefixedSignature;\n const response = await fetch(\n `${baseRpcUrl || DEFAULT_RPC_URL}/?chainId=${chainId}&projectId=${projectId}`,\n {\n headers: {\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n body: JSON.stringify({\n id: generateJsonRpcId(),\n jsonrpc: \"2.0\",\n method: \"eth_call\",\n params: [{ to: address, data }, \"latest\"],\n }),\n },\n );\n const { result } = await response.json();\n if (!result) return false;\n\n // Remove right-padded zeros from result to get only the concrete recovered value.\n const recoveredValue = result.slice(0, eip1271MagicValue.length);\n return recoveredValue.toLowerCase() === eip1271MagicValue.toLowerCase();\n } catch (error: any) {\n // eslint-disable-next-line no-console\n console.error(\"isValidEip1271Signature: \", error);\n return false;\n }\n}\n\nfunction generateJsonRpcId() {\n return Date.now() + Math.floor(Math.random() * 1000);\n}\n\nexport function extractSolanaTransactionId(solanaTransaction: string): string {\n const bytes = base64ToBytes(solanaTransaction);\n\n const signatureCount = bytes[0];\n if (signatureCount === 0) {\n throw new Error(\"No signatures found\");\n }\n\n const signatureEndPos = 1 + signatureCount * 64;\n if (bytes.length < signatureEndPos) {\n throw new Error(\"Transaction data too short for claimed signature count\");\n }\n\n if (bytes.length < 100) {\n throw new Error(\"Transaction too short\");\n }\n\n const signatureBytes = bytes.slice(1, 65);\n return base58.encode(signatureBytes);\n}\n\nexport function getSuiDigest(transaction: string) {\n const txBytes = base64ToBytes(transaction);\n const typeTagBytes = new TextEncoder().encode(\"TransactionData::\");\n\n const dataWithTag = new Uint8Array(typeTagBytes.length + txBytes.length);\n dataWithTag.set(typeTagBytes);\n dataWithTag.set(txBytes, typeTagBytes.length);\n\n const hash = blake2b(dataWithTag, { dkLen: 32 });\n return base58.encode(hash);\n}\n\nexport function getNearTransactionIdFromSignedTransaction(signedTransaction: unknown) {\n const hash = new Uint8Array(sha256(getNearUint8ArrayFromBytes(signedTransaction)));\n const hashBase58 = base58.encode(hash);\n return hashBase58;\n}\n\nexport function getNearUint8ArrayFromBytes(bytes: unknown) {\n if (bytes instanceof Uint8Array) {\n return bytes;\n } else if (Array.isArray(bytes)) {\n return new Uint8Array(bytes);\n } else if (typeof bytes === \"object\" && (bytes as any)?.data) {\n return new Uint8Array(Object.values((bytes as any).data));\n } else if (typeof bytes === \"object\" && bytes) {\n return new Uint8Array(Object.values(bytes));\n } else {\n throw new Error(\"getNearUint8ArrayFromBytes: Unexpected result type from bytes array\");\n }\n}\n\nexport function getAlgorandTransactionId(transaction: string) {\n const signedTxnBytes = base64ToBytes(transaction);\n\n const decoded = msgpackDecode(signedTxnBytes) as any;\n\n const unsignedTxn = decoded.txn;\n if (!unsignedTxn) {\n throw new Error(\"Invalid signed transaction: missing 'txn' field\");\n }\n\n const serializedUnsignedTxn = msgpackEncode(unsignedTxn);\n\n const txPrefix = new TextEncoder().encode(\"TX\");\n const toHash = concat([txPrefix, new Uint8Array(serializedUnsignedTxn)]);\n\n const hash = sha512_256(toHash);\n return base32.encode(hash).replace(/=+$/, \"\");\n}\n\nfunction encodeVarint(value: number | bigint): Uint8Array {\n const result: number[] = [];\n let v = BigInt(value);\n while (v >= 0x80n) {\n result.push(Number((v & 0x7fn) | 0x80n));\n v >>= 7n;\n }\n result.push(Number(v));\n return new Uint8Array(result);\n}\n\nexport function getSignDirectHash(payload: {\n signed: {\n chainId: string;\n accountNumber: string;\n authInfoBytes: string;\n bodyBytes: string;\n };\n signature: {\n pub_key: {\n type: string;\n value: string;\n };\n signature: string;\n };\n}) {\n const bodyBytes = base64ToBytes(payload.signed.bodyBytes);\n const authInfoBytes = base64ToBytes(payload.signed.authInfoBytes);\n const signature = base64ToBytes(payload.signature.signature);\n\n const chunks: Uint8Array[] = [];\n\n chunks.push(new Uint8Array([0x0a]));\n chunks.push(encodeVarint(bodyBytes.length));\n chunks.push(bodyBytes);\n\n chunks.push(new Uint8Array([0x12]));\n chunks.push(encodeVarint(authInfoBytes.length));\n chunks.push(authInfoBytes);\n\n chunks.push(new Uint8Array([0x1a]));\n chunks.push(encodeVarint(signature.length));\n chunks.push(signature);\n\n const txRawBytes = concat(chunks);\n const hashBytes = sha256(txRawBytes);\n\n return toString(hashBytes, \"base16\").toUpperCase();\n}\n\nexport function getWalletSendCallsHashes(\n result: string | { id: string; capabilities: { caip345: { transactionHashes: string[] } } },\n) {\n const hashes: string[] = [];\n try {\n if (typeof result === \"string\") {\n hashes.push(result);\n return hashes;\n }\n\n if (typeof result !== \"object\") {\n return hashes;\n }\n\n if (result?.id) {\n hashes.push(result.id);\n }\n\n const txHashes = result?.capabilities?.caip345?.transactionHashes;\n\n if (txHashes) {\n hashes.push(...txHashes);\n }\n } catch (error) {\n console.warn(\"getWalletSendCallsHashes failed: \", error);\n }\n\n return hashes;\n}\n","import { AuthTypes } from \"@walletconnect/types\";\nimport { getCommonValuesInArrays } from \"./misc.js\";\nimport { verifySignature } from \"./signatures.js\";\nconst didPrefix = \"did:pkh:\";\nconst NAMESPACE_DISPLAY_NAMES = {\n eip155: \"Ethereum\",\n solana: \"Solana\",\n bip122: \"Bitcoin\",\n};\n\nconst getNamespaceNameFromNamespace = (namespace?: string) => {\n if (!namespace) return \"\";\n const displayName = NAMESPACE_DISPLAY_NAMES[namespace as keyof typeof NAMESPACE_DISPLAY_NAMES];\n return displayName || namespace;\n};\n\nexport const getDidAddressSegments = (iss: string) => {\n return iss?.split(\":\");\n};\n\nexport const getDidChainId = (iss: string) => {\n const segments = iss && getDidAddressSegments(iss);\n if (segments) {\n return iss.includes(didPrefix) ? segments[3] : segments[1];\n }\n return undefined;\n};\n\nexport const getDidAddressNamespace = (iss: string) => {\n const segments = iss && getDidAddressSegments(iss);\n if (segments) {\n return iss.includes(didPrefix) ? segments[2] : segments[0];\n }\n return undefined;\n};\n\nexport const getNamespacedDidChainId = (iss: string) => {\n const segments = iss && getDidAddressSegments(iss);\n if (segments) {\n return segments[2] + \":\" + segments[3];\n }\n return undefined;\n};\n\nexport const getDidAddress = (iss: string) => {\n const segments = iss && getDidAddressSegments(iss);\n if (segments) {\n return segments.pop();\n }\n return undefined;\n};\n\nexport async function validateSignedCacao(params: { cacao: AuthTypes.Cacao; projectId?: string }) {\n const { cacao, projectId } = params;\n const { s: signature, p: payload } = cacao;\n const reconstructed = formatMessage(payload, payload.iss);\n const walletAddress = getDidAddress(payload.iss) as string;\n const isValid = await verifySignature(\n walletAddress,\n reconstructed,\n signature,\n getNamespacedDidChainId(payload.iss) as string,\n projectId as string,\n );\n\n return isValid;\n}\n\nexport const formatMessage = (cacao: AuthTypes.FormatMessageParams, iss: string) => {\n const didNamespace = getDidAddressNamespace(iss);\n if (!didNamespace) {\n throw new Error(\"Invalid issuer: \" + iss);\n }\n const header = `${cacao.domain} wants you to sign in with your ${getNamespaceNameFromNamespace(didNamespace)} account:`;\n const walletAddress = getDidAddress(iss);\n\n if (!cacao.aud && !cacao.uri) {\n throw new Error(\"Either `aud` or `uri` is required to construct the message\");\n }\n\n let statement = cacao.statement || undefined;\n const uri = `URI: ${cacao.aud || cacao.uri}`;\n const version = `Version: ${cacao.version}`;\n const chainId = `Chain ID: ${getDidChainId(iss)}`;\n const nonce = `Nonce: ${cacao.nonce}`;\n const issuedAt = `Issued At: ${cacao.iat}`;\n const expirationTime = cacao.exp ? `Expiration Time: ${cacao.exp}` : undefined;\n const notBefore = cacao.nbf ? `Not Before: ${cacao.nbf}` : undefined;\n const requestId = cacao.requestId ? `Request ID: ${cacao.requestId}` : undefined;\n const resources = cacao.resources\n ? `Resources:${cacao.resources.map((resource) => `\\n- ${resource}`).join(\"\")}`\n : undefined;\n const recap = getRecapFromResources(cacao.resources);\n if (recap) {\n const decoded = decodeRecap(recap);\n statement = formatStatementFromRecap(statement, decoded);\n }\n\n const message = [\n header,\n walletAddress,\n ``,\n statement,\n ``,\n uri,\n version,\n chainId,\n nonce,\n issuedAt,\n expirationTime,\n notBefore,\n requestId,\n resources,\n ]\n .filter((val) => val !== undefined && val !== null) // remove unnecessary empty lines\n .join(\"\\n\");\n\n return message;\n};\n\nexport function buildAuthObject(\n requestPayload: AuthTypes.PayloadParams,\n signature: AuthTypes.CacaoSignature,\n iss: string,\n) {\n if (!iss.includes(\"did:pkh:\")) {\n iss = `did:pkh:${iss}`;\n }\n\n const authObject: AuthTypes.Cacao = {\n h: {\n t: \"caip122\",\n },\n p: {\n iss,\n domain: requestPayload.domain,\n aud: requestPayload.aud,\n version: requestPayload.version,\n nonce: requestPayload.nonce,\n iat: requestPayload.iat,\n statement: requestPayload.statement,\n requestId: requestPayload.requestId,\n resources: requestPayload.resources,\n nbf: requestPayload.nbf,\n exp: requestPayload.exp,\n },\n s: signature,\n };\n return authObject;\n}\ntype PopulateAuthPayloadParams = {\n authPayload: AuthTypes.PayloadParams;\n chains: string[];\n methods: string[];\n};\nexport function populateAuthPayload(params: PopulateAuthPayloadParams): AuthTypes.PayloadParams {\n const { authPayload, chains, methods } = params;\n const statement = authPayload.statement || \"\";\n\n if (!chains?.length) return authPayload;\n\n const requested = authPayload.chains;\n const supported = chains;\n\n const supportedChains = getCommonValuesInArrays<string>(requested, supported);\n if (!supportedChains?.length) {\n throw new Error(\"No supported chains\");\n }\n\n const requestedRecaps = getDecodedRecapFromResources(authPayload.resources);\n if (!requestedRecaps) return authPayload;\n\n isValidRecap(requestedRecaps);\n const resource = getRecapResource(requestedRecaps, \"eip155\");\n let updatedResources = authPayload?.resources || [];\n\n if (resource?.length) {\n const actions = getReCapActions(resource);\n const supportedActions = getCommonValuesInArrays<string>(actions, methods);\n if (!supportedActions?.length) {\n throw new Error(\n `Supported methods don't satisfy the requested: ${JSON.stringify(\n actions,\n )}, supported: ${JSON.stringify(methods)}`,\n );\n }\n const formattedActions = assignAbilityToActions(\"request\", supportedActions as string[], {\n chains: supportedChains,\n });\n const updatedRecap = addResourceToRecap(requestedRecaps, \"eip155\", formattedActions);\n // remove recap from resources as we will add the updated one\n updatedResources = authPayload?.resources?.slice(0, -1) || [];\n updatedResources.push(encodeRecap(updatedRecap));\n }\n\n return {\n ...authPayload,\n statement: buildRecapStatement(statement, getRecapFromResources(updatedResources)),\n chains: supportedChains,\n resources: authPayload?.resources || updatedResources.length > 0 ? updatedResources : undefined,\n };\n}\n\nexport function getDecodedRecapFromResources(resources?: string[]) {\n const resource = getRecapFromResources(resources);\n if (!resource) return;\n if (!isRecap(resource)) return;\n return decodeRecap(resource);\n}\n\nexport function recapHasResource(recap: any, resource: string) {\n return recap?.att?.hasOwnProperty(resource);\n}\n\nexport function getRecapResource(recap: any, resource: string): any[] {\n return recap?.att?.[resource] ? Object.keys(recap?.att?.[resource]) : [];\n}\n\nexport function getRecapAbilitiesFromResource(actions: any[]) {\n return actions?.map((action) => Object.keys(action)) || [];\n}\n\nexport function getReCapActions(abilities: any[]) {\n return abilities?.map((ability) => ability.split(\"/\")?.[1]) || [];\n}\n\nexport function base64Encode(input: unknown): string {\n const json = JSON.stringify(input);\n const bytes = new TextEncoder().encode(json);\n const chars = new Array<string>(bytes.length);\n for (let i = 0; i < bytes.length; i++) {\n chars[i] = String.fromCharCode(bytes[i]);\n }\n return btoa(chars.join(\"\"));\n}\n\nexport function base64Decode(encodedString: string): string {\n const padded = encodedString + \"=\".repeat((4 - (encodedString.length % 4)) % 4);\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return JSON.parse(new TextDecoder().decode(bytes));\n}\n\nexport function isValidRecap(recap: any) {\n if (!recap) throw new Error(\"No recap provided, value is undefined\");\n if (!recap.att) throw new Error(\"No `att` property found\");\n const resources = Object.keys(recap.att);\n if (!resources?.length) throw new Error(\"No resources found in `att` property\");\n resources.forEach((resource) => {\n const resourceAbilities = recap.att[resource];\n if (Array.isArray(resourceAbilities))\n throw new Error(`Resource must be an object: ${resource}`);\n if (typeof resourceAbilities !== \"object\")\n throw new Error(`Resource must be an object: ${resource}`);\n if (!Object.keys(resourceAbilities).length)\n throw new Error(`Resource object is empty: ${resource}`);\n\n Object.keys(resourceAbilities).forEach((ability) => {\n const limits = resourceAbilities[ability];\n if (!Array.isArray(limits))\n throw new Error(`Ability limits ${ability} must be an array of objects, found: ${limits}`);\n if (!limits.length)\n throw new Error(`Value of ${ability} is empty array, must be an array with objects`);\n limits.forEach((limit) => {\n if (typeof limit !== \"object\")\n throw new Error(\n `Ability limits (${ability}) must be an array of objects, found: ${limit}`,\n );\n });\n });\n });\n}\n\nexport function createRecap(resource: string, ability: string, actions: string[], limits = {}) {\n actions?.sort((a, b) => a.localeCompare(b));\n return {\n att: { [resource]: assignAbilityToActions(ability, actions, limits) },\n };\n}\n\ntype RecapType = {\n att: {\n [key: string]: Record<string, unknown>;\n };\n};\nexport function addResourceToRecap(recap: RecapType, resource: string, actions: unknown[]) {\n recap.att[resource] = {\n ...actions,\n };\n const keys = Object.keys(recap.att)?.sort((a, b) => a.localeCompare(b));\n const baseRecap: RecapType = { att: {} };\n const sorted = keys.reduce((obj, key) => {\n obj.att[key] = recap.att[key];\n return obj;\n }, baseRecap);\n return sorted;\n}\n\nexport function assignAbilityToActions(ability: string, actions: string[], limits = {}) {\n // sort resources alphabetically\n actions = actions?.sort((a, b) => a.localeCompare(b));\n const abilities = actions.map((action) => {\n return {\n [`${ability}/${action}`]: [limits],\n };\n });\n return Object.assign({}, ...abilities);\n}\n\nexport function encodeRecap(recap: any) {\n isValidRecap(recap);\n // remove the padding from the base64 string as per recap spec\n return `urn:recap:${base64Encode(recap).replace(/=/g, \"\")}`;\n}\n\nexport function decodeRecap(recap: any): RecapType {\n // base64Decode adds padding internally so don't need to add it back if it was removed\n const decoded = base64Decode(recap.replace(\"urn:recap:\", \"\"));\n isValidRecap(decoded);\n return decoded as unknown as RecapType;\n}\n\nexport function createEncodedRecap(resource: string, ability: string, actions: string[]): string {\n const recap = createRecap(resource, ability, actions);\n return encodeRecap(recap);\n}\n\nexport function isRecap(resource: string) {\n return resource && resource.includes(\"urn:recap:\");\n}\n\nexport function mergeEncodedRecaps(recap1: string, recap2: string) {\n const decoded1 = decodeRecap(recap1);\n const decoded2 = decodeRecap(recap2);\n const merged = mergeRecaps(decoded1, decoded2);\n return encodeRecap(merged);\n}\n\nexport function mergeRecaps(recap1: RecapType, recap2: RecapType) {\n isValidRecap(recap1);\n isValidRecap(recap2);\n const keys = Object.keys(recap1.att)\n .concat(Object.keys(recap2.att))\n .sort((a, b) => a.localeCompare(b));\n const mergedRecap: RecapType = { att: {} };\n keys.forEach((key) => {\n const actions = Object.keys(recap1.att?.[key] || {})\n .concat(Object.keys(recap2.att?.[key] || {}))\n .sort((a, b) => a.localeCompare(b));\n actions.forEach((action) => {\n mergedRecap.att[key] = {\n ...mergedRecap.att[key],\n [action]: recap1.att[key]?.[action] || recap2.att[key]?.[action],\n };\n });\n });\n return mergedRecap;\n}\n\nexport function formatStatementFromRecap(statement = \"\", recap: RecapType) {\n isValidRecap(recap);\n const base = \"I further authorize the stated URI to perform the following actions on my behalf: \";\n\n if (statement.includes(base)) return statement;\n\n const statementForRecap: string[] = [];\n let currentCounter = 0;\n Object.keys(recap.att).forEach((resource) => {\n const actions = Object.keys(recap.att[resource]).map((ability: any) => {\n return {\n ability: ability.split(\"/\")[0],\n action: ability.split(\"/\")[1],\n };\n });\n //\n actions.sort((a, b) => a.action.localeCompare(b.action));\n const uniqueAbilities: Record<string, string[]> = {};\n actions.forEach((action: any) => {\n if (!uniqueAbilities[action.ability]) {\n uniqueAbilities[action.ability] = [];\n }\n uniqueAbilities[action.ability].push(action.action);\n });\n const abilities = Object.keys(uniqueAbilities).map((ability) => {\n currentCounter++;\n return `(${currentCounter}) '${ability}': '${uniqueAbilities[ability].join(\n \"', '\",\n )}' for '${resource}'.`;\n });\n statementForRecap.push(abilities.join(\", \").replace(\".,\", \".\"));\n });\n\n const recapStatemet = statementForRecap.join(\" \");\n const recapStatement = `${base}${recapStatemet}`;\n // add a space if there is a statement\n return `${statement ? statement + \" \" : \"\"}${recapStatement}`;\n}\n\nexport function getMethodsFromRecap(recap: string) {\n const decoded = decodeRecap(recap);\n isValidRecap(decoded);\n // methods are only available for eip155 as per the current implementation\n const resource = decoded.att?.eip155;\n if (!resource) return [];\n return Object.keys(resource).map((ability: any) => ability.split(\"/\")[1]);\n}\n\nexport function getChainsFromRecap(recap: string) {\n const decoded = decodeRecap(recap);\n isValidRecap(decoded);\n const chains: string[] = [];\n\n Object.values(decoded.att).forEach((resource: any) => {\n Object.values(resource).forEach((ability: any) => {\n if (ability?.[0]?.chains) {\n chains.push(ability[0].chains);\n }\n });\n });\n return [...new Set(chains.flat())];\n}\n\nexport function buildRecapStatement(statement: string, recap: unknown) {\n if (!recap) return statement;\n const decoded = decodeRecap(recap);\n isValidRecap(decoded);\n return formatStatementFromRecap(statement, decoded);\n}\n\nexport function getRecapFromResources(resources?: string[]) {\n if (!resources) return;\n // per spec, recap is always the last resource\n const resource = resources?.[resources.length - 1];\n return isRecap(resource) ? resource : undefined;\n}\n","import { chacha20poly1305 } from \"@noble/ciphers/chacha\";\nimport { hkdf } from \"@noble/hashes/hkdf\";\nimport { randomBytes } from \"@noble/hashes/utils\";\nimport { sha256 } from \"@noble/hashes/sha256\";\nimport { x25519 } from \"@noble/curves/ed25519\";\nimport { p256 } from \"@noble/curves/p256\";\nimport { CryptoTypes } from \"@walletconnect/types\";\nimport { decodeJWT } from \"@walletconnect/relay-auth\";\nimport { concat, fromString, toString } from \"uint8arrays\";\n\nexport const BASE10 =