export function serializeWalletError(err: unknown, method: string | null): WalletErrorPayload { const e = err as any const message = ((e && (e.message ?? String(err))) || 'Something went wrong.').trim() const codeRaw = e && (e.code ?? e.errorCode) const code = codeRaw === undefined || codeRaw === null ? undefined : String(codeRaw) const data = safeStringify(e && e.data, 500) const causes: string[] = [] let cur = e && e.cause for (let i = 0; i < 5 && cur; i++) { const cm = ((cur && (cur.message ?? String(cur))) || '').trim() if (cm) causes.push(cm) cur = cur && cur.cause } const out: WalletErrorPayload = { message } if (code) out.code = code if (data) out.data = data if (causes.length) out.causes = causes if (method) out.method = method if (config.purpose) out.purpose = config.purpose if (config.chainIdHex) out.chainIdHex = config.chainIdHex return out } export function walletErrorHtml(payload: WalletErrorPayload): string { const msg = payload.message || 'Something went wrong.' const isNoWallet = /no wallet|window\.ethereum|metamask|rabby|brave|extension/i.test(msg) const isUserReject = /user rejected|user denied|cancelled|canceled/i.test(msg) const isWrongChain = /chain|network/i.test(msg) && !isNoWallet const isExecutionRevert = /execution reverted|revert/i.test(msg) const isOwnerWalletRequired = /owner wallet required/i.test(msg) const isOperatorWalletRequired = /operator wallet required/i.test(msg) let title = 'Wallet Error' let body = msg let hint = '' const codeClass = classifyByCode(payload.code) if (codeClass) { title = codeClass.title hint = codeClass.hint } else if (isOwnerWalletRequired) { title = 'Owner Wallet Required' body = msg.replace(/^owner wallet required:\s*/i, '') body = body ? body.charAt(0).toUpperCase() + body.slice(1) : 'Switch to the owner wallet.' hint = 'Switch to the owner wallet, then retry.' } else if (isOperatorWalletRequired) { title = 'Operator Wallet Required' body = msg.replace(/^operator wallet required:\s*/i, '') body = body ? body.charAt(0).toUpperCase() + body.slice(1) : 'Switch to the operator wallet.' hint = 'Switch to the operator wallet, then retry.' } else if (isNoWallet) { title = 'No Wallet' body = 'Install a wallet.' hint = 'Install a wallet, then retry.' } else if (isUserReject) { title = 'Rejected' body = 'Request declined in wallet.' hint = '' } else if (isWrongChain) { title = 'Wrong Network' hint = `Switch to ${escapeHtml(chainLabel(config.chainIdHex))}, then retry.` } else if (isExecutionRevert) { title = 'Transaction Reverted' hint = 'Use the expected wallet and check ENS ownership, then retry.' } if (body) body = body.charAt(0).toUpperCase() + body.slice(1) payload.title = title const action = actionContextFor(payload) let html = `

${escapeHtml(title)}

` + `

${escapeHtml(body)}

` if (action) html += `

${escapeHtml(action)}

` if (payload.causes && payload.causes.length) { for (const cause of payload.causes) { html += `

Caused by: ${escapeHtml(cause)}

` } } if (hint) html += `

${hint}

` return html } function safeStringify(v: unknown, max: number): string | undefined { if (v === undefined || v === null) return undefined let s: string try { s = typeof v === 'string' ? v : JSON.stringify(v) } catch (_) { s = String(v) } if (!s) return undefined return s.length > max ? s.slice(0, max) + '...' : s } function actionContextFor(payload: WalletErrorPayload): string { const purpose = payload.purpose || config.purpose if (purpose) { const copy = (PURPOSE_COPY as any)[purpose] as PurposeCopyEntry | undefined const ctx = copy && (copy as any).errorContext if (typeof ctx === 'string' && ctx) return ctx } if (payload.method) return `during ${payload.method}` return '' } function classifyByCode(code: string | undefined): { title: string; hint: string } | null { if (!code) return null switch (code) { case '4001': return { title: 'Rejected', hint: '' } case '4100': return { title: 'Wallet Not Authorized', hint: 'Connect this site in your wallet, then retry.' } case '4200': return { title: 'Method Not Supported by Wallet', hint: 'Use a wallet that supports this transaction type, then retry.' } case '4900': return { title: 'Wallet Disconnected', hint: 'Reconnect your wallet, then retry.' } case '4901': return { title: 'Wrong Network', hint: `Switch to ${escapeHtml(chainLabel(config.chainIdHex))}, then retry.` } case '-32603': return { title: 'Internal Wallet RPC Error', hint: "The wallet's connected RPC failed. Try again, or switch RPC in your wallet settings." } case '-32602': return { title: 'Invalid Request Parameters', hint: '' } case '-32000': case '-32001': case '-32002': case '-32003': case '-32004': case '-32005': case '-32006': case '-32007': case '-32008': case '-32009': return { title: 'Wallet RPC Error', hint: 'The wallet RPC reported a server error. Try again or switch RPC in your wallet.' } default: return null } }