Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react'

import { HookDappType, HookToDappMatch, CowHookDetails } from '@cowprotocol/hook-dapp-lib'

import { i18n } from '@lingui/core'
import { I18nProvider } from '@lingui/react'
import { fireEvent, render, screen } from '@testing-library/react'
import { ThemeProvider as StyledComponentsThemeProvider } from 'styled-components/macro'
import { getCowswapTheme } from 'theme'

import { useSimulationData } from 'modules/tenderly/hooks/useSimulationData'

import { HookItem } from './index'

jest.mock('modules/tenderly/hooks/useSimulationData', () => ({
useSimulationData: jest.fn(),
}))

const mockUseSimulationData = useSimulationData as jest.MockedFunction<typeof useSimulationData>

i18n.load('en-US', {})
i18n.activate('en-US')

const details: CowHookDetails = {
uuid: 'hook-1',
hook: {
target: '0x1111111111111111111111111111111111111111',
callData: '0xdeadbeef',
gasLimit: '100000',
dappId: 'test-hook',
},
}

const item: HookToDappMatch = {
dapp: {
id: 'test-hook',
name: 'Test hook',
descriptionShort: 'Test description',
type: HookDappType.IFRAME,
version: '1.0.0',
website: 'https://cow.fi',
image: 'https://cow.fi/icon.png',
},
hook: details.hook,
}

function renderComponent(): ReturnType<typeof render> {
return render(
<I18nProvider i18n={i18n}>
<StyledComponentsThemeProvider theme={getCowswapTheme(false)}>
<HookItem details={details} item={item} index={0} />
</StyledComponentsThemeProvider>
</I18nProvider>,
)
}

describe('HookItem', () => {
beforeEach(() => {
mockUseSimulationData.mockReturnValue({
id: 'simulation-1',
link: 'javascript:alert(1)',
status: false,
cumulativeBalancesDiff: {},
stateDiff: [],
gasUsed: '0',
})
})

it('renders an invalid simulation link as inert text', () => {
renderComponent()

fireEvent.click(screen.getByText('Test hook'))

expect(screen.queryByRole('link', { name: 'Simulation failed' })).toBeNull()
expect(screen.getByText('Simulation failed')).not.toBeNull()
})
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-restricted-imports */ // TODO: Don't use 'modules' import
import { ReactNode, useState } from 'react'

import { getSafeAbsoluteUrl } from '@cowprotocol/common-utils'
import { CowHookDetails, HookToDappMatch } from '@cowprotocol/hook-dapp-lib'

import { t } from '@lingui/core/macro'
Expand Down Expand Up @@ -28,6 +29,9 @@ export function HookItem({
const simulationData = useSimulationData(details?.uuid)

const dappName = item.dapp?.name || t`Unknown Hook`
const safeWebsiteUrl = item.dapp ? getSafeAbsoluteUrl(item.dapp.website) : null
const websiteHostname = safeWebsiteUrl ? new URL(safeWebsiteUrl).hostname : null
const safeSimulationUrl = simulationData ? getSafeAbsoluteUrl(simulationData.link) : null

return (
<styledEl.HookItemWrapper as="li">
Expand Down Expand Up @@ -66,18 +70,24 @@ export function HookItem({
<Trans>Simulation:</Trans>
</b>
<styledEl.SimulationLink status={simulationData.status}>
<a
href={simulationData.link}
target="_blank"
rel="noopener noreferrer"
data-click-event={toCowSwapGtmEvent({
category: CowSwapAnalyticsCategory.HOOKS,
action: 'Click Simulation',
label: `${dappName} - ${simulationData.status ? 'Success' : 'Failed'}`,
})}
>
{simulationData.status ? <Trans>Simulation successful</Trans> : <Trans>Simulation failed</Trans>}
</a>
{safeSimulationUrl ? (
<a
href={safeSimulationUrl}
target="_blank"
rel="noopener noreferrer"
data-click-event={toCowSwapGtmEvent({
category: CowSwapAnalyticsCategory.HOOKS,
action: 'Click Simulation',
label: `${dappName} - ${simulationData.status ? 'Success' : 'Failed'}`,
})}
>
{simulationData.status ? <Trans>Simulation successful</Trans> : <Trans>Simulation failed</Trans>}
</a>
) : (
<span>
{simulationData.status ? <Trans>Simulation successful</Trans> : <Trans>Simulation failed</Trans>}
</span>
)}
</styledEl.SimulationLink>
</p>
)}
Expand All @@ -97,18 +107,22 @@ export function HookItem({
<b>
<Trans>Website</Trans>:
</b>{' '}
<a
href={item.dapp.website}
target="_blank"
rel="noopener noreferrer"
data-click-event={toCowSwapGtmEvent({
category: CowSwapAnalyticsCategory.HOOKS,
action: 'Click Website',
label: `${dappName} - ${new URL(item.dapp.website).hostname}`,
})}
>
{item.dapp.website}
</a>
{safeWebsiteUrl && websiteHostname ? (
<a
href={safeWebsiteUrl}
target="_blank"
rel="noopener noreferrer"
data-click-event={toCowSwapGtmEvent({
category: CowSwapAnalyticsCategory.HOOKS,
action: 'Click Website',
label: `${dappName} - ${websiteHostname}`,
})}
>
{item.dapp.website}
</a>
) : (
item.dapp.website
)}
</p>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { PINATA_API_KEY, PINATA_SECRET_API_KEY } from '@cowprotocol/common-const'
import { PINATA_API_KEY } from '@cowprotocol/common-const'
import { isLocal } from '@cowprotocol/common-utils'

let warningMsg

if ((!PINATA_SECRET_API_KEY || !PINATA_API_KEY) && isLocal) {
if (!PINATA_API_KEY && isLocal) {
warningMsg =
"Pinata env vars not set. Order appData upload won't work! " +
'Set REACT_APP_PINATA_API_KEY and REACT_APP_PINATA_SECRET_API_KEY'
"Pinata public env var not set. Order appData upload won't work! " +
'Set REACT_APP_PINATA_API_KEY and configure the required server-side Pinata credentials separately.'
}

export const localWarning = warningMsg
Comment on lines 4 to 12

@coderabbitai coderabbitai Bot Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify TypeScript strictness and check implicit-any patterns around this file.
fd -i 'tsconfig.*json' -x sh -c 'echo "--- $1"; jq ".compilerOptions | {strict, noImplicitAny}" "$1"' sh {}
rg -n -C2 'let\s+warningMsg\s*$|let\s+\w+\s*$' apps/cowswap-frontend/src/legacy/state/application/localWarning.ts

Repository: cowprotocol/cowswap

Length of output: 7535


Avoid implicit any for warningMsg (apps/cowswap-frontend/src/legacy/state/application/localWarning.ts:4)

let warningMsg has no initializer or type annotation, so with noImplicitAny: true it becomes implicitly any. Type it as string | undefined.

Proposed fix
-let warningMsg
+let warningMsg: string | undefined
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cowswap-frontend/src/legacy/state/application/localWarning.ts` around
lines 4 - 12, The variable warningMsg is implicitly any; update its declaration
to have an explicit type (string | undefined) and optionally initialize to
undefined so it respects noImplicitAny, and ensure the exported localWarning has
the matching type (string | undefined) — locate the warningMsg declaration and
the export of localWarning and add the type annotation to both places (or
initialize warningMsg to undefined) to fix the type error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not applying this one.

let warningMsg here is not a demonstrated noImplicitAny failure in TypeScript; this pattern is accepted as an evolving type in this control flow. Adding : string | undefined would be harmless cleanup, but I am keeping this PR scoped to the actual lint blockers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fairlighteth, understood — TypeScript's control flow analysis infers the evolving string | undefined type correctly here without an explicit annotation, so the existing pattern is fine. Happy to leave it as-is.

🐇 (mild acceptance)


✏️ Learnings added
Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 7601
File: apps/cowswap-frontend/src/legacy/state/application/localWarning.ts:4-12
Timestamp: 2026-06-05T11:04:07.197Z
Learning: In `apps/cowswap-frontend/src/legacy/state/application/localWarning.ts`, the `let warningMsg` declaration without an explicit type annotation is intentional. TypeScript's control flow analysis correctly infers the evolving `string | undefined` type, so adding `: string | undefined` is purely cosmetic cleanup and should not be flagged as a required fix or a `noImplicitAny` violation in this file.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: lgahdl
Repo: cowprotocol/cowswap PR: 7394
File: .env.local.example:3-5
Timestamp: 2026-04-24T20:02:41.035Z
Learning: In `cowprotocol/cowswap`, the `.env.local.example` comment for `REACT_APP_SAFE_API_AUTH_TOKEN` intentionally says "required" (not "optional") even though `getSafeApiHeaders()` treats it as optional at runtime. The rationale is that unauthenticated calls to `api.safe.global` still work but are subject to monthly quota limits (429 Too Many Requests), so the strong wording is meant to proactively encourage developers to obtain and set a token. Do not flag this comment wording as misleading.

Learnt from: limitofzero
Repo: cowprotocol/cowswap PR: 7504
File: .github/workflows/vercel.yml:86-86
Timestamp: 2026-05-13T18:35:50.478Z
Learning: In the cowprotocol/cowswap frontend (pure SPA), do not treat `REACT_APP_*` environment variables (e.g., `REACT_APP_SAFE_API_AUTH_TOKEN`) as a security vulnerability by itself just because they are included in the built frontend bundle. In CRA-style setups, `REACT_APP_*` values are injected at build time into the client bundle, and this repository has no server-side execution layer that could safely keep these values secret. Only flag an issue if there is additional evidence of unintended privilege exposure or missing authorization/validation beyond the expected build-time injection of `REACT_APP_*` tokens.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-05-22T12:57:32.776Z
Learning: Applies to apps/cowswap-frontend/**/*.{ts,tsx} : Use explicit return types where they improve safety/readability. Keep local trivial internals inferred; keep exported APIs aligned with root `AGENTS.md` requirements

Learnt from: crutch12
Repo: cowprotocol/cowswap PR: 6969
File: apps/cowswap-frontend/src/lib/localeMessages.ts:12-20
Timestamp: 2026-02-04T18:23:26.408Z
Learning: In apps/cowswap-frontend/src/lib/localeMessages.ts, the loadActiveLocaleMessages() function runs before initial render (in initApp()), at which point feature flags like isInternationalizationEnabled are not yet available. This early locale loading is intentional to avoid white screens and extra rerenders.

Learnt from: fairlighteth
Repo: cowprotocol/cowswap PR: 7044
File: apps/cowswap-frontend/src/modules/wallet/utils/getWalletConnectionErrorMessage.ts:25-37
Timestamp: 2026-04-07T15:30:41.352Z
Learning: In `apps/cowswap-frontend/src/modules/wallet/utils/getWalletConnectionErrorMessage.ts`, the `JSON.stringify` fallback inside `getNormalizedWalletConnectionErrorMessage` is intentional. The function is only invoked when `getProviderErrorMessage()` does not return a string, so structured/non-string message payloads are already handled upstream. The `JSON.stringify` call is kept to preserve diagnostic detail for truly unknown error objects, rather than collapsing them to `[object Object]` or a generic string. Do not flag this as a leakage/noise risk.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-05-22T12:57:32.776Z
Learning: Applies to apps/cowswap-frontend/**/modules/**/index.ts : Public module API must be re-exported via the module `index.ts`

Learnt from: cowdan
Repo: cowprotocol/cowswap PR: 6009
File: apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx:23-33
Timestamp: 2025-07-24T16:42:53.154Z
Learning: In apps/cowswap-frontend/src/modules/tradeWidgetAddons/containers/HighFeeWarning/HighFeeWarningTooltipContent.tsx, the use of toFixed(2) for percentage formatting in tooltip content is intentional and differs from the banner message formatting that uses toSignificant(2, undefined, Rounding.ROUND_DOWN). This formatting difference serves different UX purposes and should not be flagged as inconsistent.

Learnt from: alfetopito
Repo: cowprotocol/cowswap PR: 7216
File: libs/common-utils/src/environments.ts:45-45
Timestamp: 2026-03-24T15:01:01.995Z
Learning: In `libs/common-utils/src/environments.ts`, the `forceProdApi` and `forceStagingApi` localStorage keys are intentionally unversioned. They are internal developer/hack overrides (not user-facing keys) and are exempt from the `camelCaseBase:v{number}` versioning convention that applies to regular user-facing or data-shape-sensitive localStorage keys.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-05-22T12:57:32.776Z
Learning: Applies to apps/cowswap-frontend/**/*.{ts,tsx} : Prefer `unknown` to `any`

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-26T08:28:03.489Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : MUST NOT use `any` or non-null assertions (`!`) in production code

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-05-22T12:57:32.776Z
Learning: Applies to apps/cowswap-frontend/**/*.{ts,tsx} : Prefer `as const`, `satisfies`, and `as const satisfies` where they improve literal safety

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: apps/cowswap-frontend/AGENTS.md:0-0
Timestamp: 2026-05-22T12:57:32.776Z
Learning: Applies to apps/cowswap-frontend/**/*.{ts,tsx} : Avoid generic type names like `Props`/`Options`; use specific names (for example, `TradeWidgetContainerProps`)

Learnt from: kernelwhisperer
Repo: cowprotocol/cowswap PR: 7228
File: libs/widget-lib/src/types.ts:447-448
Timestamp: 2026-04-01T21:32:33.975Z
Learning: In `libs/widget-lib/src/types.ts` (cowprotocol/cowswap PR `#7228`), `WidgetHookPayload<T>` intentionally remains a generic interface rather than a discriminated union over `WidgetHookEvents`. The team considered converting it to a distributive conditional type but found it too complex; correctness of event/payload pairings is enforced via unit tests instead of TypeScript. Do not flag `WidgetHookPayload<WidgetHookEvents>` or the `[WidgetMethodsEmit.PROCESS_HOOK]` mapping as a type-safety issue in this repository.

Learnt from: Danziger
Repo: cowprotocol/cowswap PR: 7153
File: apps/cowswap-frontend/src/modules/advancedOrders/pure/Settings/AdvancedOrdersSettings.tsx:25-25
Timestamp: 2026-03-16T16:33:33.440Z
Learning: In `apps/cowswap-frontend/src/modules/advancedOrders/pure/Settings/AdvancedOrdersSettings.tsx`, the comment `// TODO: we should use limit orders settings in Advanced Orders!` (line 25) is a pre-existing TODO that predates PR `#7153`. It is intentionally deferred and should not be flagged as a blocker in reviews of that PR or subsequent PRs unless there is a dedicated effort to address it.

Learnt from: CR
Repo: cowprotocol/cowswap PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-26T08:28:03.489Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid introducing new `common/** -> modules/**` imports; treat existing cases as legacy debt and track cleanup in `docs/QUALITY.md`

Learnt from: Danziger
Repo: cowprotocol/cowswap PR: 6997
File: apps/cowswap-frontend/src/common/state/fetchTokens.utils.ts:37-49
Timestamp: 2026-03-20T18:17:17.482Z
Learning: In `apps/cowswap-frontend/src/common/state/fetchTokens.utils.ts`, the use of `Promise.all` (instead of `Promise.allSettled`) for batching `fetchTokenFromBlockchain` calls in `fetchTokens` is intentional. The app cannot function correctly when any tokens are missing, so failing the entire batch on a single rejection is the desired fail-fast behavior. Do not flag this as a missing error-handling issue.

Learnt from: shoom3301
Repo: cowprotocol/cowswap PR: 7581
File: libs/wallet/src/wagmi/Web3Provider.tsx:160-174
Timestamp: 2026-06-04T07:39:09.775Z
Learning: In cowprotocol/cowswap (`libs/wallet/src/wagmi`), `SafeConnectionHandler` contains an internal early-return guard: if the currently connected connector is `COW_WIDGET_CONNECTOR_ID` (the widget connector), `SafeConnectionHandler` skips its Safe auto-connection/reconnection logic entirely. Therefore, `Web3Provider` does not need to gate `SafeConnectionHandler` on `standaloneMode` — even in dappMode, where the widget connector is active, `SafeConnectionHandler` will not override it. Do not flag the unused `standaloneMode` prop in `Web3ProviderProps` as a bug; the defence is implemented inside `SafeConnectionHandler`.

6 changes: 6 additions & 0 deletions apps/cowswap-frontend/src/locales/en-US.po
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ msgstr "Swap anyway"
msgid "The hash for this Safe transaction."
msgstr "The hash for this Safe transaction."

#: apps/cowswap-frontend/src/common/containers/OrderHooksDetails/HookItem/index.tsx
#: apps/cowswap-frontend/src/common/containers/OrderHooksDetails/HookItem/index.tsx
#: apps/cowswap-frontend/src/common/containers/OrderHooksDetails/index.tsx
#: apps/cowswap-frontend/src/modules/hooksStore/pure/AppliedHookItem/index.tsx
Expand Down Expand Up @@ -5623,6 +5624,10 @@ msgstr "N/A"
msgid "This transaction can be simulated before execution to ensure that it will be succeed, generating a detailed report of the transaction execution."
msgstr "This transaction can be simulated before execution to ensure that it will be succeed, generating a detailed report of the transaction execution."

#: apps/cowswap-frontend/src/modules/hooksStore/pure/AddCustomHookForm/constants.tsx
msgid "Invalid website URL in manifest. Only http(s) URLs are allowed, with http limited to local development."
msgstr "Invalid website URL in manifest. Only http(s) URLs are allowed, with http limited to local development."

#: apps/cowswap-frontend/src/modules/tradeFormValidation/pure/TradeFormButtons/tradeButtonsMap.tsx
msgid "Sell amount too small to bridge"
msgstr "Sell amount too small to bridge"
Expand Down Expand Up @@ -6702,6 +6707,7 @@ msgstr "Network fees and costs"
msgid "NEW"
msgstr "NEW"

#: apps/cowswap-frontend/src/common/containers/OrderHooksDetails/HookItem/index.tsx
#: apps/cowswap-frontend/src/common/containers/OrderHooksDetails/HookItem/index.tsx
#: apps/cowswap-frontend/src/modules/hooksStore/pure/AppliedHookItem/index.tsx
msgid "Simulation successful"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactNode, useCallback, useState } from 'react'

import { useUpdateTokenBalance } from '@cowprotocol/balances-and-allowances'
import { useComponentDestroyedRef } from '@cowprotocol/common-hooks'
import { getIsNativeToken, isFractionFalsy } from '@cowprotocol/common-utils'
import { getIsNativeToken, isAddress, isFractionFalsy } from '@cowprotocol/common-utils'
import { areAddressesEqual } from '@cowprotocol/cow-sdk'
import { TokenLogo } from '@cowprotocol/tokens'
import { ButtonSize, CenteredDots, FiatAmount, Loader, TokenSymbol } from '@cowprotocol/ui'
Expand Down Expand Up @@ -37,19 +37,22 @@ export function AccountProxyRecoverPage(): ReactNode {
const [txInProgress, setTxInProgress] = useState(false)

const navigateBack = useNavigateBack()
const { balance, usdValue } = useTokenBalanceAndUsdValue(tokenAddress)
const proxies = useAccountProxies()
const ownedProxy = proxies?.find((p) => proxyAddress && areAddressesEqual(p.account, proxyAddress))
const validProxyAddress = ownedProxy?.account
const validTokenAddress = tokenAddress && isAddress(tokenAddress) ? tokenAddress : undefined
const { balance, usdValue } = useTokenBalanceAndUsdValue(validTokenAddress)
const destroyedRef = useComponentDestroyedRef()

const proxies = useAccountProxies()
const updateTokenBalance = useUpdateTokenBalance()
const proxyVersion = proxies?.find((p) => areAddressesEqual(p.account, proxyAddress))?.version
const proxyVersion = ownedProxy?.version

const recoverFundsContext = useRecoverFundsFromProxy(
proxyAddress,
validProxyAddress,
proxyVersion,
tokenAddress,
validTokenAddress,
balance,
!!tokenAddress && getIsNativeToken(chainId, tokenAddress),
!!validTokenAddress && getIsNativeToken(chainId, validTokenAddress),
)
const { txSigningStep } = recoverFundsContext

Expand All @@ -70,11 +73,11 @@ export function AccountProxyRecoverPage(): ReactNode {
// When tx is successfully mined
() => {
navigateBack()
tokenAddress && updateTokenBalance(tokenAddress, 0n)
validTokenAddress && updateTokenBalance(validTokenAddress, 0n)
},
)
})
}, [recoverCallback, navigateBack, updateTokenBalance, tokenAddress, destroyedRef])
}, [recoverCallback, navigateBack, updateTokenBalance, validTokenAddress, destroyedRef])

return (
<Wrapper>
Expand Down Expand Up @@ -104,7 +107,7 @@ export function AccountProxyRecoverPage(): ReactNode {
</TokenWrapper>

<ButtonPrimaryStyled
disabled={isFractionFalsy(balance) || !!txSigningStep || txInProgress}
disabled={!validProxyAddress || !validTokenAddress || isFractionFalsy(balance) || !!txSigningStep || txInProgress}
buttonSize={ButtonSize.BIG}
onClick={onRecover}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'

import { getSafeSameOriginOrAbsoluteUrl } from '@cowprotocol/common-utils'
import type { NotificationModel } from '@cowprotocol/core'
import { UI } from '@cowprotocol/ui'

Expand Down Expand Up @@ -58,16 +59,18 @@ export function CowSpeechBubbleNotificationBanner({
}

const { title, description, url, id } = currentNotification
const linkTarget = url && isInternal(url) ? '_parent' : '_blank'
const safeLink =
typeof window !== 'undefined' && url ? getSafeSameOriginOrAbsoluteUrl(url, window.location.origin) : null
const linkTarget = safeLink?.isExternal ? '_blank' : '_parent'

return (
<CowSpeechBubble variant="notification" onClose={onClose} closeButtonAriaLabel={t`Dismiss notification`}>
<NotificationText>
<strong>{title}</strong>
<NotificationDescription>{description}</NotificationDescription>
{url && (
{safeLink && (
<NotificationLink
href={url}
href={safeLink.href}
target={linkTarget}
rel={linkTarget === '_blank' ? 'noopener noreferrer' : undefined}
data-click-event={toCowSwapGtmEvent({
Expand All @@ -85,13 +88,3 @@ export function CowSpeechBubbleNotificationBanner({
</CowSpeechBubble>
)
}

function isInternal(href: string): boolean {
if (href.startsWith('/')) return true

try {
return new URL(href).hostname === window.location.hostname
} catch {
return false
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ReactNode } from 'react'

import iconReceiptSrc from '@cowprotocol/assets/cow-swap/icon-receipt.svg'
import { getSafeAbsoluteUrl } from '@cowprotocol/common-utils'
import { ExternalLink } from '@cowprotocol/ui'

import { ConfirmDetailsItem } from 'modules/trade'
Expand All @@ -14,6 +15,12 @@ interface TransactionLinkDisplayProps {
}

export function TransactionLinkDisplay({ link, label, linkText }: TransactionLinkDisplayProps): ReactNode {
const safeLink = getSafeAbsoluteUrl(link)

if (!safeLink) {
return null
}

return (
<ConfirmDetailsItem
label={
Expand All @@ -25,7 +32,7 @@ export function TransactionLinkDisplay({ link, label, linkText }: TransactionLin
</>
}
>
<ExternalLink href={link}>{linkText}</ExternalLink>
<ExternalLink href={safeLink}>{linkText}</ExternalLink>
</ConfirmDetailsItem>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from 'react'

import { isFractionFalsy } from '@cowprotocol/common-utils'
import { getSafeAbsoluteUrl, isFractionFalsy } from '@cowprotocol/common-utils'
import { BridgeStatusResult } from '@cowprotocol/sdk-bridging'

import { FailedBridgingContent } from './FailedBridgingContent'
Expand Down Expand Up @@ -32,6 +32,7 @@ export function BridgingProgressContent(props: BridgingContentProps): ReactNode
statusResult,
explorerUrl,
} = props
const safeExplorerUrl = getSafeAbsoluteUrl(explorerUrl) || undefined

return (
<QuoteBridgeContent {...props} isFinished={!isFractionFalsy(receivedAmount)}>
Expand All @@ -42,14 +43,18 @@ export function BridgingProgressContent(props: BridgingContentProps): ReactNode
destinationChainId={destinationChainId}
receivedAmount={receivedAmount}
receivedAmountUsd={receivedAmountUsd}
explorerUrl={explorerUrl}
explorerUrl={safeExplorerUrl}
/>
) : isRefunded ? (
<RefundedBridgingContent account={account} bridgeSendCurrencyAmount={quoteContext.sellAmount} />
) : isFailed ? (
<FailedBridgingContent />
) : (
<PendingBridgingContent sourceChainId={sourceChainId} statusResult={statusResult} explorerUrl={explorerUrl} />
<PendingBridgingContent
sourceChainId={sourceChainId}
statusResult={statusResult}
explorerUrl={safeExplorerUrl}
/>
)}
</QuoteBridgeContent>
)
Expand Down
Loading
Loading