Skip to content

Commit 8fcbfa9

Browse files
authored
Merge pull request #5913 from cowprotocol/release/2025-07-02
chore(release): 2025-07-02
2 parents 0bbed1e + f3d721e commit 8fcbfa9

File tree

350 files changed

+7393
-3585
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

350 files changed

+7393
-3585
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,7 @@ apps/cowswap-frontend/public/robots.txt
6363

6464
# Claude context files (local only)
6565
CLAUDE.md
66-
.claude/
66+
.claude/
67+
68+
# Serena project files
69+
.serena/

apps/cow-fi/components/NetworkItem/index.tsx

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
import { JSX } from 'react'
2+
3+
import Image from 'next/image'
4+
15
import { CopyToClipboard } from '@/components/CopyToClipboard'
6+
import { Network, NETWORK_IMAGE_MAP, NETWORK_MAP, NETWORK_URL_MAP } from '@/const/networkMap'
7+
28
import { ItemWrapper } from './styles'
39

410
type PlatformData = {
@@ -9,41 +15,28 @@ type PlatformData = {
915
}
1016

1117
type Props = {
12-
network: string
18+
network: Network
1319
platformData: PlatformData
1420
}
1521

16-
export function NetworkItem(props: Props) {
22+
export function NetworkItem(props: Props): JSX.Element {
1723
const { network, platformData } = props
1824
const { address, symbol, name } = platformData
1925

26+
const networkUrl = `${NETWORK_URL_MAP[network]}/${address}`
27+
const networkImage = NETWORK_IMAGE_MAP[network]
28+
const networkName = NETWORK_MAP[network]
29+
2030
return (
2131
<ItemWrapper>
22-
<a
23-
href={
24-
network === 'xdai' ? `https://gnosisscan.io/address/${address}` : `https://etherscan.io/address/${address}`
25-
}
26-
title={`${name} (${symbol}) on ${network === 'xdai' ? 'Gnosis Chain' : 'Ethereum'}`}
27-
target="_blank"
28-
rel="noreferrer nofollow"
29-
>
30-
<img
31-
src={`/images/${network === 'xdai' ? 'gnosis-chain' : network}.svg`}
32-
alt={network === 'xdai' ? 'Gnosis Chain' : 'Ethereum'}
33-
/>
34-
{network === 'xdai' ? 'Gnosis Chain' : network.charAt(0).toUpperCase() + network.slice(1)}
32+
<a href={networkUrl} title={`${name} (${symbol}) on ${networkName}`} target="_blank" rel="noreferrer nofollow">
33+
<Image src={networkImage} alt={networkName} width={16} height={16} />
34+
{networkName}
3535
</a>
36-
<a
37-
href={
38-
network === 'xdai' ? `https://gnosisscan.io/address/${address}` : `https://etherscan.io/address/${address}`
39-
}
40-
title={`${name} (${symbol}) on ${network === 'xdai' ? 'Gnosis Chain' : 'Ethereum'}`}
41-
target="_blank"
42-
rel="noreferrer nofollow"
43-
>
36+
<a href={networkUrl} title={`${name} (${symbol}) on ${networkName}`} target="_blank" rel="noreferrer nofollow">
4437
<div>
4538
<i>{address}</i>
46-
<img src="/images/external-arrow.svg" alt="Go to explorer" />
39+
<Image src="/images/external-arrow.svg" alt="Go to explorer" width={16} height={16} />
4740
</div>
4841
</a>
4942
<span>
Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
1+
import { JSX } from 'react'
2+
3+
import Image from 'next/image'
4+
15
import { SwapCard } from '@/components/TokenDetails/index.styles'
6+
import { Network, NETWORK_IMAGE_MAP, NETWORK_MAP } from '@/const/networkMap'
27

38
type SwapLinkCardProps = {
49
contractAddress: string
510
networkId: number
6-
networkName: string
11+
network: Network
712
tokenSymbol: string
813
}
914

10-
export const SwapLinkCard = ({ contractAddress, networkId, networkName, tokenSymbol }: SwapLinkCardProps) => {
11-
return (
12-
contractAddress && (
13-
<SwapCard>
14-
<a
15-
href={`https://swap.cow.fi/#/${networkId}/swap/${
16-
networkId === 100 ? 'WXDAI' : 'WETH'
17-
}/${contractAddress}?sellAmount=1`}
18-
target="_blank"
19-
rel="nofollow noreferrer"
20-
>
21-
<img
22-
src={`/images/${(networkName === 'Gnosis Chain' ? 'gnosis-chain' : networkName).toLowerCase()}.svg`}
23-
alt={networkName}
24-
/>
25-
<b>
26-
Swap {tokenSymbol} <br /> on {networkName}
27-
</b>
28-
<img src="/images/external-arrow.svg" alt="Go to CoW Swap" />
29-
</a>
30-
</SwapCard>
31-
)
32-
)
15+
export const SwapLinkCard = ({
16+
contractAddress,
17+
networkId,
18+
network,
19+
tokenSymbol,
20+
}: SwapLinkCardProps): JSX.Element | null => {
21+
const networkImage = NETWORK_IMAGE_MAP[network]
22+
23+
return contractAddress ? (
24+
<SwapCard>
25+
<a
26+
href={`https://swap.cow.fi/#/${networkId}/swap/${
27+
networkId === 100 ? 'WXDAI' : 'WETH'
28+
}/${contractAddress}?sellAmount=1`}
29+
target="_blank"
30+
rel="nofollow noreferrer"
31+
>
32+
<Image src={networkImage} alt={NETWORK_MAP[network]} width={16} height={16} />
33+
<b>
34+
Swap {tokenSymbol} <br /> on {NETWORK_MAP[network]}
35+
</b>
36+
<Image src="/images/external-arrow.svg" alt="Go to CoW Swap" width={16} height={16} />
37+
</a>
38+
</SwapCard>
39+
) : null
3340
}

apps/cow-fi/components/SwapWidget.tsx

Lines changed: 108 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
import React, { useState, useEffect, ChangeEvent } from 'react'
1+
import { useState, useEffect, ChangeEvent, JSX } from 'react'
22

33
import { Color } from '@cowprotocol/ui'
44

5+
import Image from 'next/image'
56
import { transparentize } from 'polished'
67
import styled from 'styled-components/macro'
78

89
import { LinkWithUtmComponent } from 'modules/utm/components'
910

1011
import { Button } from '@/components/Button'
1112
import { CONFIG } from '@/const/meta'
13+
import {
14+
Network,
15+
NETWORK_DEFAULT_BUY_TOKEN_MAP,
16+
NETWORK_DEFAULT_SELL_TOKEN_MAP,
17+
NETWORK_ID_MAP,
18+
NETWORK_IMAGE_MAP,
19+
NETWORK_MAP,
20+
} from '@/const/networkMap'
1221

1322
type TabProps = {
1423
active: boolean
@@ -193,128 +202,151 @@ type SwapWidgetProps = {
193202
platforms: Platforms
194203
}
195204

196-
enum Networks {
197-
ETHEREUM = 'ethereum',
198-
XDAI = 'xdai',
205+
type Tab = 'Buy' | 'Sell'
206+
const DEFAULT_TAB: Tab = 'Buy'
207+
const DEFAULT_NETWORK: Network = 'ethereum'
208+
209+
const getBuyAndSellTokens = (
210+
activeTab: Tab,
211+
network: Network,
212+
contractAddress: string,
213+
): { sellToken: string; buyToken: string } => {
214+
if (activeTab === 'Buy') {
215+
return {
216+
sellToken: NETWORK_DEFAULT_SELL_TOKEN_MAP[network],
217+
buyToken: contractAddress,
218+
}
219+
}
220+
221+
return {
222+
sellToken: contractAddress,
223+
buyToken: NETWORK_DEFAULT_BUY_TOKEN_MAP[network],
224+
}
199225
}
200226

201-
const NETWORK_MAP: { [key: string]: string } = {
202-
[Networks.ETHEREUM]: 'Ethereum',
203-
[Networks.XDAI]: 'Gnosis Chain',
227+
const Tabs = ({ activeTab, setActiveTab }: { activeTab: Tab; setActiveTab: (tab: Tab) => void }): JSX.Element => {
228+
return (
229+
<TabContainer>
230+
<Tab onClick={() => setActiveTab('Buy')} active={activeTab === 'Buy'}>
231+
Buy
232+
</Tab>
233+
<Tab onClick={() => setActiveTab('Sell')} active={activeTab === 'Sell'}>
234+
Sell
235+
</Tab>
236+
</TabContainer>
237+
)
204238
}
205239

206-
const WXDAI = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'
207-
const WETH = ['0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1']
240+
function DropdownNetworkOption({
241+
network,
242+
handleSelect,
243+
}: {
244+
network: Network
245+
handleSelect: (network: Network) => void
246+
}): JSX.Element {
247+
const width = 20
248+
const height = 20
208249

209-
export const SwapWidget = ({ tokenId, tokenSymbol, tokenImage, platforms }: SwapWidgetProps) => {
210-
const [activeTab, setActiveTab] = useState('Buy')
211-
const [network, setNetwork] = useState<string | null>(null)
250+
return (
251+
<DropdownOption onClick={() => handleSelect(network)}>
252+
<Image src={NETWORK_IMAGE_MAP[network]} alt={NETWORK_MAP[network]} width={width} height={height} />
253+
{NETWORK_MAP[network]}
254+
</DropdownOption>
255+
)
256+
}
257+
258+
const getDropdownBody = (platforms: Platforms, handleSelect: (network: Network) => void): JSX.Element => {
259+
const { ethereum, xdai, base, 'arbitrum-one': arbitrum, avalanche, 'polygon-pos': polygon } = platforms
260+
261+
return (
262+
<DropdownBody>
263+
{ethereum?.contractAddress && <DropdownNetworkOption network="ethereum" handleSelect={handleSelect} />}
264+
{base?.contractAddress && <DropdownNetworkOption network="base" handleSelect={handleSelect} />}
265+
{arbitrum?.contractAddress && <DropdownNetworkOption network="arbitrum-one" handleSelect={handleSelect} />}
266+
{polygon?.contractAddress && <DropdownNetworkOption network="polygon-pos" handleSelect={handleSelect} />}
267+
{avalanche?.contractAddress && <DropdownNetworkOption network="avalanche" handleSelect={handleSelect} />}
268+
{xdai?.contractAddress && <DropdownNetworkOption network="xdai" handleSelect={handleSelect} />}
269+
</DropdownBody>
270+
)
271+
}
272+
273+
function getNetworkFromPlatforms(platforms: Platforms): Network {
274+
const { ethereum, xdai, base, 'arbitrum-one': arbitrum, avalanche, 'polygon-pos': polygon } = platforms
275+
276+
if (ethereum?.contractAddress) return 'ethereum'
277+
if (base?.contractAddress) return 'base'
278+
if (arbitrum?.contractAddress) return 'arbitrum-one'
279+
if (polygon?.contractAddress) return 'polygon-pos'
280+
if (avalanche?.contractAddress) return 'avalanche'
281+
if (xdai?.contractAddress) return 'xdai'
282+
283+
return 'ethereum'
284+
}
285+
286+
export const SwapWidget = ({ tokenId, tokenSymbol, tokenImage, platforms }: SwapWidgetProps): JSX.Element => {
287+
const [activeTab, setActiveTab] = useState<Tab>(DEFAULT_TAB)
288+
const [network, setNetwork] = useState<Network>(DEFAULT_NETWORK)
212289
const [amount, setAmount] = useState(0)
213290

214291
const [isOpen, setIsOpen] = useState(false)
215-
const handleSelect = (network: string | null) => {
292+
293+
const handleSelect = (network: Network): void => {
216294
setNetwork(network)
217295
setIsOpen(false)
218296
}
219297

298+
// set initial network based on the available platforms
220299
useEffect(() => {
221-
// set initial network based on the available platforms
222-
if (platforms.ethereum.contractAddress) setNetwork(Networks.ETHEREUM)
223-
else if (platforms.xdai.contractAddress) setNetwork(Networks.XDAI)
300+
setNetwork(getNetworkFromPlatforms(platforms))
224301
}, [platforms])
225302

226-
const handleInputChange = (event: ChangeEvent<any>) => {
303+
const handleInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
227304
let value = event.target.value
228305

229306
// Remove leading minus sign if present
230307
if (value.startsWith('-')) {
231308
value = value.slice(1)
232309
}
233310

234-
if (value === '' || (parseFloat(value) >= 0 && !isNaN(value))) {
235-
setAmount(value)
311+
if (value === '' || (parseFloat(value) >= 0 && !isNaN(parseFloat(value)))) {
312+
setAmount(parseFloat(value))
236313
}
237314
}
238315

239-
const onSwap = () => {
316+
const getSwapUrl = (): string => {
240317
if (network && platforms[network]) {
241-
const networkId = network === 'xdai' ? 100 : 1
318+
const networkId = NETWORK_ID_MAP[network as Network]
242319
const contractAddress = platforms[network].contractAddress
243320

244-
let sellToken, buyToken
245-
if (activeTab === 'Buy') {
246-
sellToken = networkId === 100 ? 'WXDAI' : 'WETH'
247-
buyToken = contractAddress
248-
249-
if (contractAddress === WXDAI) {
250-
sellToken = 'XDAI'
251-
}
252-
253-
if (WETH.includes(contractAddress)) {
254-
sellToken = networkId === 100 ? 'WXDAI' : 'ETH'
255-
}
256-
} else {
257-
sellToken = contractAddress
258-
buyToken = networkId === 100 ? 'WXDAI' : 'WETH'
259-
260-
if (contractAddress === WXDAI) {
261-
buyToken = 'XDAI'
262-
}
263-
264-
if (WETH.includes(contractAddress)) {
265-
buyToken = networkId === 100 ? 'WXDAI' : 'ETH'
266-
}
267-
}
321+
const { sellToken, buyToken } = getBuyAndSellTokens(activeTab, network as Network, contractAddress)
268322

269323
return `https://swap.cow.fi/#/${networkId}/swap/${sellToken}/${buyToken}?${activeTab.toLowerCase()}Amount=${amount}`
270324
} else {
271325
return '#'
272326
}
273327
}
274328

329+
const networkImage = NETWORK_IMAGE_MAP[network as Network]
330+
const networkName = NETWORK_MAP[network as Network]
331+
275332
return (
276333
<Wrapper>
277-
<TabContainer>
278-
<Tab onClick={() => setActiveTab('Buy')} active={activeTab === 'Buy'}>
279-
Buy
280-
</Tab>
281-
<Tab onClick={() => setActiveTab('Sell')} active={activeTab === 'Sell'}>
282-
Sell
283-
</Tab>
284-
</TabContainer>
334+
<Tabs activeTab={activeTab} setActiveTab={setActiveTab} />
285335

286336
<DropdownContainer>
287337
<DropdownHeader onClick={() => setIsOpen(!isOpen)}>
288-
<img
289-
src={`/images/${network === Networks.ETHEREUM ? 'ethereum' : 'gnosis-chain'}.svg`}
290-
alt={network ? NETWORK_MAP[network] : ''}
291-
/>
292-
<b>{network ? NETWORK_MAP[network] : ''}</b>
338+
<Image src={networkImage} alt={networkName} width={16} height={16} />
339+
<b>{networkName}</b>
293340
</DropdownHeader>
294-
{isOpen && (
295-
<DropdownBody>
296-
{platforms?.ethereum?.contractAddress && (
297-
<DropdownOption onClick={() => handleSelect('ethereum')}>
298-
<img src="/images/ethereum.svg" alt="Ethereum" />
299-
Ethereum
300-
</DropdownOption>
301-
)}
302-
{platforms?.xdai?.contractAddress && (
303-
<DropdownOption onClick={() => handleSelect('xdai')}>
304-
<img src="/images/gnosis-chain.svg" alt="Gnosis Chain" />
305-
Gnosis Chain
306-
</DropdownOption>
307-
)}
308-
</DropdownBody>
309-
)}
341+
{isOpen && getDropdownBody(platforms, handleSelect)}
310342
</DropdownContainer>
311343

312344
<InputLabel>
313345
{activeTab === 'Buy' ? 'Receive' : 'Send'}
314346

315347
<div>
316348
<TokenLabel>
317-
<img src={tokenImage} alt={tokenSymbol} />
349+
<Image src={tokenImage} alt={tokenSymbol} width={20} height={20} />
318350
<span>{tokenSymbol}</span>
319351
</TokenLabel>
320352
<Input min={0} value={amount} type="text" onChange={handleInputChange} placeholder="0" />
@@ -326,7 +358,7 @@ export const SwapWidget = ({ tokenId, tokenSymbol, tokenImage, platforms }: Swap
326358
...CONFIG.utm,
327359
utmContent: 'utm_content=swap-widget-token__' + encodeURI(tokenId),
328360
}}
329-
href={onSwap()}
361+
href={getSwapUrl()}
330362
passHref
331363
>
332364
<Button label={`Swap ${tokenSymbol}`} fontSize={1.6} minHeight={4.2} />

0 commit comments

Comments
 (0)