diff --git a/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useHandleApprovalError.ts b/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useHandleApprovalError.ts index 6c4ff54371..e7b2eb36a3 100644 --- a/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useHandleApprovalError.ts +++ b/apps/cowswap-frontend/src/modules/erc20Approve/containers/TradeApproveModal/useHandleApprovalError.ts @@ -28,7 +28,9 @@ export function useHandleApprovalError(symbol: string | undefined): (error: unkn } else { captureError(error, ERROR_TYPES.ON_APPROVE) approvalAnalytics('Error', symbol, extractErrorCode(err)) - updateApproveProgressModalState({ error: error.message }) + // Use shortMessage (viem) to avoid verbose error output with request arguments + const displayMessage = ('shortMessage' in error ? error.shortMessage : error.message) as string + updateApproveProgressModalState({ error: displayMessage }) } }, [updateApproveProgressModalState, t, approvalAnalytics, symbol], diff --git a/apps/cowswap-frontend/src/modules/twap/utils/parseTwapError.ts b/apps/cowswap-frontend/src/modules/twap/utils/parseTwapError.ts index 3030c4cc1f..0a81e3d5b8 100644 --- a/apps/cowswap-frontend/src/modules/twap/utils/parseTwapError.ts +++ b/apps/cowswap-frontend/src/modules/twap/utils/parseTwapError.ts @@ -1,22 +1,7 @@ -import { isRejectRequestProviderError } from '@cowprotocol/common-utils' +import { isRejectRequestProviderError, MAX_NESTED_ERROR_DEPTH } from '@cowprotocol/common-utils' import { t } from '@lingui/core/macro' -const MAX_ERROR_DEPTH = 8 - -function isUserRejectionError(error: unknown, depth = 0): boolean { - if (depth > MAX_ERROR_DEPTH || error === null || error === undefined) { - return false - } - if (isRejectRequestProviderError(error)) { - return true - } - if (error instanceof Error && 'cause' in error && error.cause !== undefined) { - return isUserRejectionError(error.cause, depth + 1) - } - return false -} - function collectErrorMessageFromObject(error: object): string { return 'message' in error ? String((error as { message: unknown }).message) : '' } @@ -25,7 +10,7 @@ function collectErrorMessageFromObject(error: object): string { * Flattens `Error` + nested `cause` messages (viem / WalletConnect often nest "Request expired" here). */ function collectErrorMessages(error: unknown, depth = 0): string { - if (depth > MAX_ERROR_DEPTH) { + if (depth > MAX_NESTED_ERROR_DEPTH) { return '' } if (error === null || error === undefined) { @@ -67,7 +52,7 @@ function getWalletRequestExpiredMessage(flatMessage: string): string | undefined export function getErrorMessage(error: unknown): string { const DEFAULT_ERROR_MESSAGE = t`Something went wrong creating your order` - if (isUserRejectionError(error)) { + if (isRejectRequestProviderError(error)) { return t`User rejected transaction` } diff --git a/libs/common-utils/src/misc.ts b/libs/common-utils/src/misc.ts index c312721d14..1543ce47b2 100644 --- a/libs/common-utils/src/misc.ts +++ b/libs/common-utils/src/misc.ts @@ -9,6 +9,7 @@ interface Market { quoteToken: T } +export const MAX_NESTED_ERROR_DEPTH = 8 const PROVIDER_REJECT_REQUEST_CODES = [4001, -32000] // See https://eips.ethereum.org/EIPS/eip-1193 const PROVIDER_REJECT_REQUEST_ERROR_MESSAGES = [ 'User denied message signature', @@ -173,28 +174,45 @@ export function hashCode(text: string): number { */ // TODO: Add proper return type annotation // TODO: Replace any with proper type definitions -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-explicit-any -export function isRejectRequestProviderError(error: any) { - if (error) { - // Check the error code is the user rejection as described in eip-1193 - if (PROVIDER_REJECT_REQUEST_CODES.includes(error.code)) { - return true - } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isRejectRequestProviderError(error: any, depth = 0): boolean { + if (!error || depth > MAX_NESTED_ERROR_DEPTH) { + return false + } - // Check for some specific messages returned by some wallets when rejecting requests - const message = getProviderErrorMessage(error) - if ( - PROVIDER_REJECT_REQUEST_ERROR_MESSAGES.some( - (rejectMessage) => message && rejectMessage && message.toLowerCase().includes(rejectMessage.toLowerCase()), - ) - ) { - return true - } + if (isRejectionAtCurrentLevel(error)) { + return true + } + + // Recurse into cause chain (viem/WalletConnect nest errors deeply) + const cause = error.cause + if (cause !== undefined && cause !== error) { + return isRejectRequestProviderError(cause, depth + 1) } return false } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isRejectionAtCurrentLevel(error: any): boolean { + // Check the error code is the user rejection as described in eip-1193 + if (PROVIDER_REJECT_REQUEST_CODES.includes(error.code)) { + return true + } + + // Check error message and viem's `details` property for wallet-specific rejection messages + const message = getProviderErrorMessage(error) + const details = typeof error.details === 'string' ? error.details : undefined + + return matchesRejectionMessage(message) || matchesRejectionMessage(details) +} + +function matchesRejectionMessage(message: string | undefined): boolean { + if (!message) return false + const lower = message.toLowerCase() + return PROVIDER_REJECT_REQUEST_ERROR_MESSAGES.some((rejectMessage) => lower.includes(rejectMessage.toLowerCase())) +} + /** * Helper function that transforms a percentage into Basis Points (BPS) * @param percent