/** * MetaMask / EIP-1193 wallet error helpers. * * Previously the codebase detected user rejections by string-matching the * error message for "reject" or "denied". That breaks when MetaMask's UI * language is set to anything other than English (the message is translated * but our match string isn't). The reliable signal is the EIP-1193 error * code: 4001 = "User rejected the request". * * Wagmi sometimes wraps the underlying provider error inside `.cause`, so we * walk that chain too. */ interface WithCode { code?: number | string; cause?: unknown } export function isUserRejection(err: unknown): boolean { // Walk a small depth of .cause chains since wagmi/viem wrap provider errors let cur: unknown = err for (let i = 0; i < 4 && cur; i++) { const c = (cur as WithCode)?.code if (c === 4001 || c === 'ACTION_REJECTED' || c === 'USER_REJECTED') return true cur = (cur as WithCode)?.cause } // Last-resort string fallback for providers that don't expose a code field. // Kept narrow — "reject" + "denied" + a couple of locale variants we've // actually seen from MetaMask zh-CN ("拒绝") and Rabby ("rejected by user"). const m = err instanceof Error ? err.message : '' return /reject|denied|cancell|拒绝|已取消/i.test(m) } /** * Convenience: return either a friendly cancellation label or a truncated * version of the real error message — never raw user-facing crash text. */ export function walletErrorLabel(err: unknown, cancelledLabel = 'Cancelled', maxLen = 110): string { if (isUserRejection(err)) return cancelledLabel const m = err instanceof Error ? err.message : String(err ?? '') return m.slice(0, maxLen) || 'unknown error' }