From a85b6c5086369c02f0b2c65f32587c907a04e3d4 Mon Sep 17 00:00:00 2001 From: Martin Grabina Date: Thu, 9 Apr 2026 12:27:13 -0300 Subject: [PATCH 1/2] chore: filter noisy wallet/extension errors in Sentry beforeSend Drops errors from browser extensions, wallet injectors, WalletConnect noise, user rejections, and RPC errors that aren't actionable. Filters by both error message patterns and stack-frame origin (culprit-based) to catch generic errors from injected scripts. --- sentry.client.config.ts | 7 +++ src/utils/sentryFilters.ts | 95 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/utils/sentryFilters.ts diff --git a/sentry.client.config.ts b/sentry.client.config.ts index 11df4f9a19..33725d92f3 100644 --- a/sentry.client.config.ts +++ b/sentry.client.config.ts @@ -5,6 +5,8 @@ import * as Sentry from '@sentry/nextjs'; +import { shouldIgnoreError } from './src/utils/sentryFilters'; + Sentry.init({ dsn: 'https://f4f62da759bfe365562d0dfe080a255e@o4508407151525888.ingest.de.sentry.io/4510516896530512', @@ -14,4 +16,9 @@ Sentry.init({ // Enable sending user PII (Personally Identifiable Information) // https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii sendDefaultPii: false, + + beforeSend(event) { + if (shouldIgnoreError(event)) return null; + return event; + }, }); diff --git a/src/utils/sentryFilters.ts b/src/utils/sentryFilters.ts new file mode 100644 index 0000000000..fe4e33f6df --- /dev/null +++ b/src/utils/sentryFilters.ts @@ -0,0 +1,95 @@ +import type { Event } from '@sentry/types'; + +// Error message patterns to drop. Each regex is tested against +// the first exception value (or the top-level message). +const IGNORED_ERROR_PATTERNS: RegExp[] = [ + // Browser-extension subscription/port noise + /connection interrupted while trying to subscribe/i, + /attempting to use a disconnected port object/i, + /the source .+ has not been authorized yet/i, + /origin not allowed/i, + + // MetaMask / wallet connect failures + /failed to connect to metamask/i, + /chrome\.runtime\.sendmessage\(\) called from a webpage must specify an extension id/i, + /cannot read properties of undefined \(reading 'sendmessage'\)/i, + /cannot read properties of undefined \(reading 'networkversion'\)/i, + /cannot read properties of undefined \(reading 'removelistener'\)/i, + /cannot read properties of undefined \(reading 'ton'\)/i, + + // Wallet injector conflicts (window.ethereum property fights) + /invalid property descriptor\. cannot both specify accessors and a value or writable attribute/i, + /cannot set property ethereum of # which has only a getter/i, + + // Specific wallet extensions + /talisman extension has not been configured yet/i, + /'set' on proxy: trap returned falsish for property 'tronlinkparams'/i, + /bitvisionweb is not defined/i, + /shouldsetpelagusforcurrentprovider is not a function/i, + /the request by this web3 provider is timeout/i, + + // wagmi / RainbowKit connector noise + /providernotfounderror: provider not found/i, + /connectornotconnectederror: connector not connected/i, + + // Cross-origin frame (wallet iframes) + /blocked a frame with origin .+ from accessing a cross-origin frame/i, + + // WalletConnect + /websocket connection closed abnormally with code: 3000/i, + /jwt validation error/i, + /websocket connection failed for host: wss:\/\/relay\.walletconnect\.org/i, + /no matching key\. session topic doesn't exist/i, + /proposal expired/i, + /request expired\. please try again/i, + /failed to execute 'transaction' on 'idbdatabase': the database connection is closing/i, + + // User rejections + /userrejectedrequesterror/i, + /user rejected the request/i, + /user denied/i, + + // RPC noise from viem buildRequest + /unknownrpcerror: an unknown rpc error occurred/i, + /internalrpcerror: an internal error was received/i, + + // Network / browser noise + /aborterror: signal is aborted without reason/i, + /can't find variable: eip155/i, +]; + +// Culprit / stack-frame patterns. These catch errors that have generic +// messages (e.g. "Failed to fetch") but originate from injected scripts. +const IGNORED_CULPRIT_PATTERNS: RegExp[] = [ + /injectLeap/i, + /inject\.chrome/i, + /extensionServiceWorker/i, + /injectedScript\.bundle/i, + /window-provider/i, + /injected\/injected/i, + /frame_ant\/frame_ant/i, + /\/inpage$/i, + /\/inject$/i, + /\/injector$/i, + /\/btc$/i, + /\/sui$/i, + /\/solana$/i, +]; + +export function shouldIgnoreError(event: Event): boolean { + const message = event.exception?.values?.[0]?.value ?? event.message ?? ''; + const culprit = (event as Record).culprit as string | undefined; + const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? []; + + if (IGNORED_ERROR_PATTERNS.some((p) => p.test(message))) return true; + + if (culprit && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(culprit))) return true; + + // Check if the top stack frame comes from an injected script + const topFrame = frames[frames.length - 1]; + if (topFrame?.filename && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(topFrame.filename!))) { + return true; + } + + return false; +} From 7e317d1a4039d3a8cf06113595420208e602af70 Mon Sep 17 00:00:00 2001 From: Martin Grabina Date: Thu, 9 Apr 2026 12:35:00 -0300 Subject: [PATCH 2/2] fix: tighten Sentry filters to avoid suppressing real errors - Remove overly broad patterns: /origin not allowed/, /jwt validation error/, /user denied/, /unknownrpcerror/, /internalrpcerror/, /aborterror: signal is aborted/ - Scope "proposal expired" to WalletConnect context - Scope "user denied" to wallet-specific rejection messages - Add missing patterns: "missing revert data in call exception", "non-error promise rejection captured with value: null" --- src/utils/sentryFilters.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/utils/sentryFilters.ts b/src/utils/sentryFilters.ts index fe4e33f6df..3460745cf6 100644 --- a/src/utils/sentryFilters.ts +++ b/src/utils/sentryFilters.ts @@ -7,7 +7,6 @@ const IGNORED_ERROR_PATTERNS: RegExp[] = [ /connection interrupted while trying to subscribe/i, /attempting to use a disconnected port object/i, /the source .+ has not been authorized yet/i, - /origin not allowed/i, // MetaMask / wallet connect failures /failed to connect to metamask/i, @@ -37,24 +36,26 @@ const IGNORED_ERROR_PATTERNS: RegExp[] = [ // WalletConnect /websocket connection closed abnormally with code: 3000/i, - /jwt validation error/i, /websocket connection failed for host: wss:\/\/relay\.walletconnect\.org/i, /no matching key\. session topic doesn't exist/i, - /proposal expired/i, + /walletconnect.+proposal expired/i, /request expired\. please try again/i, /failed to execute 'transaction' on 'idbdatabase': the database connection is closing/i, - // User rejections + // User rejections (wallet-specific patterns) /userrejectedrequesterror/i, /user rejected the request/i, - /user denied/i, + /user rejected transaction/i, + /user denied transaction signature/i, + /user denied message signature/i, - // RPC noise from viem buildRequest - /unknownrpcerror: an unknown rpc error occurred/i, - /internalrpcerror: an internal error was received/i, + // Contract revert with no reason string (not actionable from frontend) + /missing revert data in call exception/i, + + // Non-Error null rejections (wallet/provider teardown) + /non-error promise rejection captured with value: null/i, // Network / browser noise - /aborterror: signal is aborted without reason/i, /can't find variable: eip155/i, ]; @@ -80,16 +81,17 @@ export function shouldIgnoreError(event: Event): boolean { const message = event.exception?.values?.[0]?.value ?? event.message ?? ''; const culprit = (event as Record).culprit as string | undefined; const frames = event.exception?.values?.[0]?.stacktrace?.frames ?? []; + const topFilename = frames.length > 0 ? frames[frames.length - 1]?.filename : undefined; + // Unconditional message-based filters (safe regardless of source) if (IGNORED_ERROR_PATTERNS.some((p) => p.test(message))) return true; - if (culprit && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(culprit))) return true; + const isFromInjectedScript = + (culprit != null && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(culprit))) || + (topFilename != null && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(topFilename))); - // Check if the top stack frame comes from an injected script - const topFrame = frames[frames.length - 1]; - if (topFrame?.filename && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(topFrame.filename!))) { - return true; - } + // Drop any error whose stack originates from an injected script + if (isFromInjectedScript) return true; return false; }