diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx
index bb8a288d6b..324295df0d 100644
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx
+++ b/src/components/AccountDrawer/MiniPortfolio/Pools/index.test.tsx
@@ -1,8 +1,5 @@
-import { BigNumber } from '@ethersproject/bignumber'
-import { ChainId, WETH9 } from '@uniswap/sdk-core'
-import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'
-import { USDC_MAINNET } from 'constants/tokens'
import { mocked } from 'test-utils/mocked'
+import { owner, useMultiChainPositionsReturnValue } from 'test-utils/pools/fixtures'
import { render } from 'test-utils/render'
import Pools from '.'
@@ -12,53 +9,6 @@ jest.mock('./useMultiChainPositions')
jest.spyOn(console, 'warn').mockImplementation()
-const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
-
-const pool = new Pool(
- USDC_MAINNET,
- WETH9[ChainId.MAINNET],
- FeeAmount.MEDIUM,
- '1851127709498178402383049949138810',
- '7076437181775065414',
- 201189
-)
-
-const position = new Position({
- pool,
- liquidity: 1341008833950736,
- tickLower: 200040,
- tickUpper: 202560,
-})
-const details = {
- nonce: BigNumber.from('0'),
- tokenId: BigNumber.from('0'),
- operator: '0x0',
- token0: USDC_MAINNET.address,
- token1: WETH9[ChainId.MAINNET].address,
- fee: FeeAmount.MEDIUM,
- tickLower: -100,
- tickUpper: 100,
- liquidity: BigNumber.from('9000'),
- feeGrowthInside0LastX128: BigNumber.from('0'),
- feeGrowthInside1LastX128: BigNumber.from('0'),
- tokensOwed0: BigNumber.from('0'),
- tokensOwed1: BigNumber.from('0'),
-}
-const useMultiChainPositionsReturnValue = {
- positions: [
- {
- owner,
- chainId: ChainId.MAINNET,
- position,
- pool,
- details,
- inRange: true,
- closed: false,
- },
- ],
- loading: false,
-}
-
beforeEach(() => {
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
})
diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx
new file mode 100644
index 0000000000..356a7e75d5
--- /dev/null
+++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx
@@ -0,0 +1,63 @@
+import userEvent from '@testing-library/user-event'
+import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
+import { mocked } from 'test-utils/mocked'
+import { useMultiChainPositionsReturnValue, validPoolToken0, validPoolToken1 } from 'test-utils/pools/fixtures'
+import { act, render, screen } from 'test-utils/render'
+
+import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons'
+
+jest.mock('components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions')
+
+describe('PoolDetailsStatsButton', () => {
+ const mockProps = {
+ chainId: 1,
+ token0: validPoolToken0,
+ token1: validPoolToken1,
+ feeTier: 500,
+ }
+
+ const mockPropsTokensReversed = {
+ ...mockProps,
+ token0: validPoolToken1,
+ token1: validPoolToken0,
+ }
+
+ beforeEach(() => {
+ mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
+ })
+
+ it('renders both buttons correctly', () => {
+ const { asFragment } = render()
+ expect(asFragment()).toMatchSnapshot()
+
+ expect(screen.getByTestId('pool-details-add-liquidity-button')).toBeVisible()
+ expect(screen.getByTestId('pool-details-swap-button')).toBeVisible()
+ })
+
+ it('clicking swap goes to correct url', async () => {
+ render()
+
+ await act(() => userEvent.click(screen.getByTestId('pool-details-swap-button')))
+ expect(global.window.location.href).toContain(
+ '/swap?inputCurrency=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&outputCurrency=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
+ )
+ })
+
+ it('clicking swap goes to correct url with tokens reversed', async () => {
+ render()
+
+ await act(() => userEvent.click(screen.getByTestId('pool-details-swap-button')))
+ expect(global.window.location.href).toContain(
+ '/swap?inputCurrency=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2&outputCurrency=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
+ )
+ })
+
+ it('clicking add liquidity goes to correct url', async () => {
+ render()
+
+ await act(() => userEvent.click(screen.getByTestId('pool-details-add-liquidity-button')))
+ expect(global.window.location.href).toContain(
+ '/increase/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/500'
+ )
+ })
+})
diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx
new file mode 100644
index 0000000000..497b202725
--- /dev/null
+++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx
@@ -0,0 +1,89 @@
+import { Trans } from '@lingui/macro'
+import { useWeb3React } from '@web3-react/core'
+import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache'
+import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions'
+import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
+import Row from 'components/Row'
+import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
+import { useCurrency } from 'hooks/Tokens'
+import { useSwitchChain } from 'hooks/useSwitchChain'
+import { useNavigate } from 'react-router-dom'
+import styled from 'styled-components'
+import { BREAKPOINTS } from 'theme'
+import { currencyId } from 'utils/currencyId'
+
+const PoolDetailsStatsButtonsRow = styled(Row)`
+ gap: 12px;
+
+ @media (max-width: ${BREAKPOINTS.lg - 1}px) {
+ display: none;
+ }
+`
+
+const PoolButton = styled(ThemeButton)`
+ padding: 12px 16px 12px 12px;
+ border-radius: 900px;
+ width: 50%;
+`
+
+interface PoolDetailsStatsButtonsProps {
+ chainId?: number
+ token0?: Token
+ token1?: Token
+ feeTier?: number
+}
+
+function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) {
+ return positions?.find(
+ (position) =>
+ (position?.details.token0.toLowerCase() === token0?.id ||
+ position?.details.token0.toLowerCase() === token1?.id) &&
+ (position?.details.token1.toLowerCase() === token0?.id ||
+ position?.details.token1.toLowerCase() === token1?.id) &&
+ position?.details.fee == feeTier &&
+ !position.closed
+ )
+}
+
+export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) {
+ const { chainId: walletChainId, connector, account } = useWeb3React()
+ const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined)
+ const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier)
+ const tokenId = position?.details.tokenId
+ const switchChain = useSwitchChain()
+ const navigate = useNavigate()
+ const currency0 = useCurrency(token0?.id, chainId)
+ const currency1 = useCurrency(token1?.id, chainId)
+ const handleOnClick = async (toSwap: boolean) => {
+ if (currency0 && currency1) {
+ if (walletChainId !== chainId && chainId) await switchChain(connector, chainId)
+ navigate(
+ toSwap
+ ? `/swap?inputCurrency=${currencyId(currency0)}&outputCurrency=${currencyId(currency1)}`
+ : `/increase/${currencyId(currency0)}/${currencyId(currency1)}/${feeTier}${tokenId ? `/${tokenId}` : ''}`
+ )
+ }
+ }
+ if (!currency0 || !currency1) return null
+ return (
+
+ handleOnClick(false)}
+ data-testid="pool-details-add-liquidity-button"
+ >
+ Add liquidity
+
+
+ handleOnClick(true)}
+ data-testid="pool-details-swap-button"
+ >
+ Swap
+
+
+ )
+}
diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap
new file mode 100644
index 0000000000..2b8fcc5fb7
--- /dev/null
+++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap
@@ -0,0 +1,141 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PoolDetailsStatsButton renders both buttons correctly 1`] = `
+
+ .c0 {
+ box-sizing: border-box;
+ margin: 0;
+ min-width: 0;
+}
+
+.c1 {
+ width: 100%;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ padding: 0;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: start;
+ -webkit-justify-content: flex-start;
+ -ms-flex-pack: start;
+ justify-content: flex-start;
+}
+
+.c6 {
+ background-color: transparent;
+ bottom: 0;
+ border-radius: inherit;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ -webkit-transition: 150ms ease background-color;
+ transition: 150ms ease background-color;
+ width: 100%;
+}
+
+.c3 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: #FFEFFF;
+ border-radius: 16px;
+ border: 0;
+ color: #FC72FF;
+ cursor: pointer;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ font-size: 16px;
+ font-weight: 535;
+ gap: 12px;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ line-height: 20px;
+ padding: 10px 12px;
+ position: relative;
+ -webkit-transition: 150ms ease opacity;
+ transition: 150ms ease opacity;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.c3:active .c5 {
+ background-color: #B8C0DC3d;
+}
+
+.c3:focus .c5 {
+ background-color: #B8C0DC3d;
+}
+
+.c3:hover .c5 {
+ background-color: #98A1C014;
+}
+
+.c3:disabled {
+ cursor: default;
+ opacity: 0.6;
+}
+
+.c3:disabled:active .c5,
+.c3:disabled:focus .c5,
+.c3:disabled:hover .c5 {
+ background-color: transparent;
+}
+
+.c2 {
+ gap: 12px;
+}
+
+.c4 {
+ padding: 12px 16px 12px 12px;
+ border-radius: 900px;
+ width: 50%;
+}
+
+@media (max-width:1023px) {
+ .c2 {
+ display: none;
+ }
+}
+
+
+
+
+
+
+`;
diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap
index 84c96e5367..fde90bd8fb 100644
--- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap
+++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap
@@ -17,7 +17,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
width: max-content;
}
-.c26 {
+.c31 {
box-sizing: border-box;
margin: 0;
min-width: 0;
@@ -82,7 +82,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
gap: 8px;
}
-.c27 {
+.c32 {
width: -webkit-max-content;
width: -moz-max-content;
width: max-content;
@@ -124,6 +124,78 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
justify-content: flex-start;
}
+.c21 {
+ background-color: transparent;
+ bottom: 0;
+ border-radius: inherit;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ -webkit-transition: 150ms ease background-color;
+ transition: 150ms ease background-color;
+ width: 100%;
+}
+
+.c18 {
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ background-color: #FFEFFF;
+ border-radius: 16px;
+ border: 0;
+ color: #FC72FF;
+ cursor: pointer;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ flex-direction: row;
+ font-size: 16px;
+ font-weight: 535;
+ gap: 12px;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ line-height: 20px;
+ padding: 10px 12px;
+ position: relative;
+ -webkit-transition: 150ms ease opacity;
+ transition: 150ms ease opacity;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.c18:active .c20 {
+ background-color: #B8C0DC3d;
+}
+
+.c18:focus .c20 {
+ background-color: #B8C0DC3d;
+}
+
+.c18:hover .c20 {
+ background-color: #98A1C014;
+}
+
+.c18:disabled {
+ cursor: default;
+ opacity: 0.6;
+}
+
+.c18:disabled:active .c20,
+.c18:disabled:focus .c20,
+.c18:disabled:hover .c20 {
+ background-color: transparent;
+}
+
.c4 {
gap: 36px;
}
@@ -207,17 +279,17 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
border-radius: 50%;
}
-.c28 {
+.c33 {
color: #FF5F52;
}
-.c18 {
+.c23 {
font-weight: 485;
font-size: 24px;
line-height: 36px;
}
-.c17 {
+.c22 {
gap: 24px;
padding: 20px;
border-radius: 20px;
@@ -225,7 +297,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
width: 100%;
}
-.c19 {
+.c24 {
gap: 8px;
-webkit-flex: 1;
-ms-flex: 1;
@@ -233,14 +305,14 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
min-width: 180px;
}
-.c20 {
+.c25 {
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
-.c21 {
+.c26 {
font-weight: 485;
font-size: 18px;
line-height: 24px;
@@ -249,7 +321,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
width: max-content;
}
-.c22 {
+.c27 {
height: 8px;
width: 40.698463777008904%;
background: #2172E5;
@@ -258,7 +330,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
border-right: 1px solid #F9F9F9;
}
-.c23 {
+.c28 {
height: 8px;
width: 59.3015362229911%;
background: #2172E5;
@@ -267,7 +339,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
border-left: 1px solid #F9F9F9;
}
-.c24 {
+.c29 {
gap: 4px;
width: 100%;
-webkit-align-items: flex-end;
@@ -276,13 +348,23 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
align-items: flex-end;
}
-.c25 {
+.c30 {
color: #222222;
font-size: 36px;
font-weight: 485;
line-height: 44px;
}
+.c17 {
+ gap: 12px;
+}
+
+.c19 {
+ padding: 12px 16px 12px 12px;
+ border-radius: 900px;
+ width: 50%;
+}
+
.c2 {
padding: 48px;
width: 100%;
@@ -300,13 +382,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
}
@media (max-width:1023px) {
- .c18 {
+ .c23 {
width: 100%;
}
}
@media (max-width:1023px) {
- .c17 {
+ .c22 {
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
@@ -323,13 +405,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
}
@media (max-width:640px) {
- .c19 {
+ .c24 {
min-width: 150px;
}
}
@media (max-width:1023px) {
- .c20 {
+ .c25 {
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
@@ -337,7 +419,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
}
@media (max-width:1023px) {
- .c21 {
+ .c26 {
font-size: 20px;
line-height: 28px;
width: 100%;
@@ -345,7 +427,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
}
@media (max-width:1023px) {
- .c24 {
+ .c29 {
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
@@ -358,12 +440,18 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
}
@media (max-width:1023px) {
- .c25 {
+ .c30 {
font-size: 20px;
line-height: 28px;
}
}
+@media (max-width:1023px) {
+ .c17 {
+ display: none;
+ }
+}
+
@media (max-width:1023px) {
.c2 {
-webkit-flex-direction: column;
@@ -485,15 +573,39 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
class="c3 c16"
>
+
+
+
+
Stats
90.93M USDC
82,526.49 WETH
@@ -519,15 +631,15 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
data-testid="pool-balance-chart"
>
$223.2M
$233.4M
$116.7K
diff --git a/src/pages/PoolDetails/index.tsx b/src/pages/PoolDetails/index.tsx
index 4c4cca6d02..427a63e1a2 100644
--- a/src/pages/PoolDetails/index.tsx
+++ b/src/pages/PoolDetails/index.tsx
@@ -11,6 +11,7 @@ import { isAddress } from 'utils'
import { PoolDetailsHeader } from './PoolDetailsHeader'
import { PoolDetailsStats } from './PoolDetailsStats'
+import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons'
const PageWrapper = styled(Row)`
padding: 48px;
@@ -67,6 +68,7 @@ export default function PoolDetailsPage() {
toggleReversed={toggleReversed}
/>
+
{poolData && }
diff --git a/src/test-utils/pools/fixtures.ts b/src/test-utils/pools/fixtures.ts
index d4815873dc..d070cd15b4 100644
--- a/src/test-utils/pools/fixtures.ts
+++ b/src/test-utils/pools/fixtures.ts
@@ -1,6 +1,76 @@
+import { BigNumber } from '@ethersproject/bignumber'
+import { ChainId, WETH9 } from '@uniswap/sdk-core'
+import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'
+import { USDC_MAINNET } from 'constants/tokens'
import { Token } from 'graphql/thegraph/__generated__/types-and-hooks'
export const validParams = { poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', chainName: 'ethereum' }
+
+export const validPoolToken0 = {
+ id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
+ symbol: 'USDC',
+ name: 'USD Coin',
+ decimals: '6',
+ derivedETH: '0.0006240873011635544626425964678706127',
+ __typename: 'Token',
+} as Token
+
+export const validPoolToken1 = {
+ id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
+ symbol: 'WETH',
+ name: 'Wrapped Ether',
+ decimals: '18',
+ derivedETH: '1',
+ __typename: 'Token',
+} as Token
+
+export const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
+
+const pool = new Pool(
+ USDC_MAINNET,
+ WETH9[ChainId.MAINNET],
+ FeeAmount.MEDIUM,
+ '1851127709498178402383049949138810',
+ '7076437181775065414',
+ 201189
+)
+
+const position = new Position({
+ pool,
+ liquidity: 1341008833950736,
+ tickLower: 200040,
+ tickUpper: 202560,
+})
+const details = {
+ nonce: BigNumber.from('0'),
+ tokenId: BigNumber.from('0'),
+ operator: '0x0',
+ token0: USDC_MAINNET.address,
+ token1: WETH9[ChainId.MAINNET].address,
+ fee: FeeAmount.MEDIUM,
+ tickLower: -100,
+ tickUpper: 100,
+ liquidity: BigNumber.from('9000'),
+ feeGrowthInside0LastX128: BigNumber.from('0'),
+ feeGrowthInside1LastX128: BigNumber.from('0'),
+ tokensOwed0: BigNumber.from('0'),
+ tokensOwed1: BigNumber.from('0'),
+}
+export const useMultiChainPositionsReturnValue = {
+ positions: [
+ {
+ owner,
+ chainId: ChainId.MAINNET,
+ position,
+ pool,
+ details,
+ inRange: true,
+ closed: false,
+ },
+ ],
+ loading: false,
+}
+
export const validPoolDataResponse = {
data: {
id: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640',
@@ -8,22 +78,8 @@ export const validPoolDataResponse = {
liquidity: parseFloat('26414803986874770777'),
sqrtPrice: parseFloat('1977320351696380862605029898750440'),
tick: 202508,
- token0: {
- id: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
- symbol: 'USDC',
- name: 'USD Coin',
- decimals: '6',
- derivedETH: '0.0006240873011635544626425964678706127',
- __typename: 'Token',
- } as Token,
- token1: {
- id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
- symbol: 'WETH',
- name: 'Wrapped Ether',
- decimals: '18',
- derivedETH: '1',
- __typename: 'Token',
- } as Token,
+ token0: validPoolToken0,
+ token1: validPoolToken1,
token0Price: 1605.481,
token1Price: 0.000622,
volumeUSD: 233379442.64648438,