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..3460745cf6 --- /dev/null +++ b/src/utils/sentryFilters.ts @@ -0,0 +1,97 @@ +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, + + // 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, + /websocket connection failed for host: wss:\/\/relay\.walletconnect\.org/i, + /no matching key\. session topic doesn't exist/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 (wallet-specific patterns) + /userrejectedrequesterror/i, + /user rejected the request/i, + /user rejected transaction/i, + /user denied transaction signature/i, + /user denied message signature/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 + /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 ?? []; + 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; + + const isFromInjectedScript = + (culprit != null && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(culprit))) || + (topFilename != null && IGNORED_CULPRIT_PATTERNS.some((p) => p.test(topFilename))); + + // Drop any error whose stack originates from an injected script + if (isFromInjectedScript) return true; + + return false; +}