-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Withdraw CoW AMM]: Create withdraw pool hook UI #9
Changes from all commits
2aaf4ee
8f8b16e
a5aed00
d96b19b
4b50f33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,11 +3,13 @@ import { useCallback, useState } from 'react' | |
import { ButtonPrimary } from '@cowprotocol/ui' | ||
|
||
import { useGasLimit } from 'modules/hooksStore/hooks/useGasLimitHooks' | ||
import { DropDownMenu } from 'modules/hooksStore/pure/DropDownMenu' | ||
import { ContentWrapper, Wrapper, Row } from 'modules/hooksStore/styled' | ||
import { HookDappProps } from 'modules/hooksStore/types/hooks' | ||
|
||
import { AIRDROP_OPTIONS } from './constants' | ||
import { AIRDROP_PREVIEW_ERRORS, useClaimData } from './hooks/useClaimData' | ||
import { ClaimableAmountContainer, ContentWrapper, DropDownMenu, LabelContainer, Row, Wrapper } from './styled' | ||
import { ClaimableAmountContainer, LabelContainer, } from './styled' | ||
import { AirdropOption, IClaimData } from './types' | ||
|
||
export function AirdropHookApp({ context }: HookDappProps) { | ||
|
@@ -37,7 +39,15 @@ export function AirdropHookApp({ context }: HookDappProps) { | |
<LabelContainer> | ||
<label>Select Airdrop</label> | ||
</LabelContainer> | ||
<DropDownMenu airdropOptions={connectedChainAirdrops} setSelectedAirdrop={setSelectedAirdrop} /> | ||
<DropDownMenu | ||
items={connectedChainAirdrops.map((airdrop) => { | ||
return { value: airdrop.name, id: airdrop.name } | ||
})} | ||
setSelectedItem={({ id }) => | ||
setSelectedAirdrop(connectedChainAirdrops.find((airdrop) => airdrop.name === id)) | ||
} | ||
Comment on lines
+46
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
text={selectedAirdrop?.name || 'Select Airdrop'} | ||
/> | ||
</Row> | ||
<Row> | ||
<ClaimableAmountContainer> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { HookDappInternal, HookDappType } from 'modules/hooksStore/types/hooks' | ||
|
||
import withdrawImage from './icon.png' | ||
|
||
import { WithdrawHookApp } from './index' | ||
|
||
const Description = () => { | ||
return ( | ||
<> | ||
<p> | ||
Reduce or withdraw liquidity from a pool before a token swap integrating the process{' '} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is a CoW AMM pool, not any AMM pool right? would just indicate it in the copy |
||
<b>directly into the transaction flow.</b> By adjusting your liquidity ahead of time, you gain{' '} | ||
<b>more control over your assets</b> without any extra steps. | ||
</p> | ||
<br /> | ||
<p> | ||
Optimize your position in a pool, all in one seamless action —{' '} | ||
<b>no need for multiple transactions or added complexity.</b> | ||
</p> | ||
</> | ||
) | ||
} | ||
|
||
export const PRE_WITHDRAW_COW_AMM: HookDappInternal = { | ||
name: 'Withdraw Liquidity', | ||
description: <Description />, | ||
descriptionShort: 'Exit the pool or decrease liquidity before the swap', | ||
type: HookDappType.INTERNAL, | ||
image: withdrawImage, | ||
component: (props) => <WithdrawHookApp {...props} />, | ||
version: '0.1.0', | ||
website: 'https://balancer.fi/pools/cow', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Token } from '@uniswap/sdk-core' | ||
|
||
import { PoolBalance } from '../types' | ||
|
||
export function usePoolUserBalance(poolId?: string): PoolBalance[] { | ||
if (!poolId) return [] | ||
return [ | ||
{ | ||
token: new Token(1115511, '0x912ce59144191c1204e64559fe8253a0e49e6548', 18, 'ARB', 'Arbitrum'), | ||
fiatAmount: '2765252315984615', | ||
balance: '100000000000000000', | ||
Comment on lines
+9
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 maybe I don't have context here, but I assume we hardcoding this data here for testing purposes, right? Should we add a comment to come back to this in the future and remove it? |
||
}, | ||
{ | ||
token: new Token(111551, '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', 18, 'WETH', 'Wrapped Ether'), | ||
fiatAmount: '2765252315984615', | ||
balance: '100000000000000000', | ||
}, | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { IMinimalPool } from '../types' | ||
|
||
export function useUserPools(user?: string): IMinimalPool[] { | ||
// TODO: replace with real data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
if (!user) return [] | ||
return [ | ||
{ | ||
id: '0x94a5afbf8f987d6eadb1c0fe73366321c5fe950b', | ||
chain: 'ARBITRUM', | ||
symbol: 'ARB-WETH', | ||
|
||
dynamicData: { | ||
totalLiquidity: '552.68', | ||
volume24h: '0.58', | ||
}, | ||
allTokens: [ | ||
{ | ||
address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', | ||
symbol: 'WETH', | ||
decimals: 18, | ||
}, | ||
{ | ||
address: '0x912ce59144191c1204e64559fe8253a0e49e6548', | ||
symbol: 'ARB', | ||
decimals: 18, | ||
}, | ||
], | ||
userBalance: { | ||
totalBalance: '100', | ||
}, | ||
}, | ||
{ | ||
id: 'test', | ||
chain: 'test', | ||
symbol: 'AAA-BBB', | ||
|
||
dynamicData: { | ||
totalLiquidity: '10000.68', | ||
volume24h: '0.58', | ||
}, | ||
allTokens: [ | ||
{ | ||
address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', | ||
symbol: 'BBB', | ||
decimals: 18, | ||
}, | ||
{ | ||
address: '0x912ce59144191c1204e64559fe8253a0e49e6548', | ||
symbol: 'AAA', | ||
decimals: 18, | ||
}, | ||
], | ||
userBalance: { | ||
totalBalance: '2000', | ||
}, | ||
}, | ||
] | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loved this picture 😆 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { useMemo, useState } from 'react' | ||
|
||
import { ButtonPrimary, UI } from '@cowprotocol/ui' | ||
|
||
import { DropDownMenu } from 'modules/hooksStore/pure/DropDownMenu' | ||
import { Slider } from 'modules/hooksStore/pure/Slider' | ||
import { ContentWrapper, Row, Wrapper } from 'modules/hooksStore/styled' | ||
import { HookDappProps } from 'modules/hooksStore/types/hooks' | ||
|
||
import { usePoolUserBalance } from './hooks/usePoolUserBalance' | ||
import { useUserPools } from './hooks/useUserPools' | ||
import { PoolBalancesPreview } from './pure/PoolBalancesPreview' | ||
import { IMinimalPool } from './types' | ||
import { multiplyValueByPct } from './utils' | ||
|
||
export function WithdrawHookApp({ context }: HookDappProps) { | ||
const pools = useUserPools(context.account) | ||
const [selectedPool, setSelectedPool] = useState<IMinimalPool>() | ||
const poolBalances = usePoolUserBalance(selectedPool?.id) | ||
const [withdrawPct, setWithdrawPct] = useState<number>(100) | ||
|
||
const poolBalancesAfterWithdraw = useMemo(() => { | ||
if (!poolBalances) return [] | ||
return poolBalances.map((poolBalance) => ({ | ||
...poolBalance, | ||
balance: multiplyValueByPct(poolBalance.balance, 100 - withdrawPct), | ||
fiatAmount: multiplyValueByPct(poolBalance.fiatAmount, 100 - withdrawPct), | ||
})) | ||
}, [poolBalances, withdrawPct]) | ||
|
||
const buttonProps = useMemo(() => { | ||
if (!context.account) return { disabled: true, message: 'Connect wallet to withdraw' } | ||
if (!pools.length) return { disabled: true, message: 'No pools to withdraw from' } | ||
if (!selectedPool) return { disabled: true, message: 'Choose liquidity pool' } | ||
if (!withdrawPct) return { disabled: true, message: 'Define percentage' } | ||
return { disabled: false, message: 'Add pre-hook' } | ||
}, [context.account, pools, selectedPool, withdrawPct]) | ||
|
||
return ( | ||
<Wrapper> | ||
<ContentWrapper> | ||
<Row> | ||
<DropDownMenu | ||
items={pools.map((pool) => ({ value: pool.symbol, id: pool.id }))} | ||
text={selectedPool?.symbol || 'Choose a pool to remove liquidity from..'} | ||
setSelectedItem={({ id }) => setSelectedPool(pools.find((pool) => pool.id === id))} | ||
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above, would use useMemo/useCb combo here |
||
/> | ||
</Row> | ||
{selectedPool && ( | ||
<> | ||
<Row> | ||
<PoolBalancesPreview label="Current balance" poolBalance={poolBalances} /> | ||
</Row> | ||
<Row> | ||
<Slider value={withdrawPct} setValue={setWithdrawPct} title="Define withdrawal percentage" /> | ||
</Row> | ||
<Row> | ||
<PoolBalancesPreview | ||
label="Balance after withdraw" | ||
poolBalance={poolBalancesAfterWithdraw} | ||
backgroundColor={UI.COLOR_PAPER_DARKER} | ||
/> | ||
</Row> | ||
</> | ||
)} | ||
<ButtonPrimary disabled={buttonProps.disabled}>{buttonProps.message}</ButtonPrimary> | ||
</ContentWrapper> | ||
</Wrapper> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { TokenLogo } from '@cowprotocol/tokens' | ||
import { TokenAmount } from '@cowprotocol/ui' | ||
import { CurrencyAmount, Fraction } from '@uniswap/sdk-core' | ||
|
||
import { Label } from 'modules/hooksStore/styled' | ||
|
||
import { FiatValue } from 'common/pure/FiatValue' | ||
|
||
import * as styledEl from './styled' | ||
|
||
import { PoolBalance } from '../../types' | ||
|
||
export interface PoolBalancesProps { | ||
poolBalance: PoolBalance[] | ||
label: string | ||
backgroundColor?: string | ||
} | ||
|
||
export interface PoolBalancePreviewProps { | ||
id: string | ||
poolBalance: PoolBalance | ||
} | ||
|
||
export function PoolBalancesPreview(props: PoolBalancesProps) { | ||
const { poolBalance, label, backgroundColor } = props | ||
|
||
return ( | ||
<div> | ||
<div> | ||
<Label>{label}</Label> | ||
</div> | ||
<styledEl.Wrapper backgroundColor={backgroundColor}> | ||
{poolBalance.map((poolBalance, index) => ( | ||
<PoolBalancePreview key={index} id={`${label}-${index}`} poolBalance={poolBalance} /> | ||
))} | ||
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you should avoid using the index as key, in many cases it can cause a lot of trouble. |
||
</styledEl.Wrapper> | ||
</div> | ||
) | ||
} | ||
|
||
export function PoolBalancePreview(props: PoolBalancePreviewProps) { | ||
const { id, poolBalance } = props | ||
|
||
return ( | ||
<styledEl.Container id={id}> | ||
<div> | ||
<styledEl.TokenLogoWrapper> | ||
<TokenLogo token={poolBalance.token} size={42} /> | ||
</styledEl.TokenLogoWrapper> | ||
</div> | ||
<styledEl.Amount> | ||
<TokenAmount | ||
className="token-amount-input" | ||
amount={new Fraction(poolBalance.balance, 1)} | ||
tokenSymbol={poolBalance.token} | ||
/> | ||
<FiatValue fiatValue={CurrencyAmount.fromFractionalAmount(poolBalance.token, poolBalance.fiatAmount, 1)} /> | ||
</styledEl.Amount> | ||
</styledEl.Container> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { Media, UI } from '@cowprotocol/ui' | ||
|
||
import styled from 'styled-components/macro' | ||
|
||
export interface WrapperProps { | ||
backgroundColor?: string | ||
} | ||
|
||
export const Wrapper = styled.div<WrapperProps>` | ||
padding: 6px 0px; | ||
width: 100%; | ||
height: 100%; | ||
border-radius: 24px; | ||
border: 1px solid var(${UI.COLOR_BORDER}); | ||
font-size: 14px; | ||
text-align: center; | ||
display: flex; | ||
flex-direction: column; | ||
background-color: ${({ backgroundColor }) => `var(${backgroundColor})` || 'transparent'}; | ||
|
||
${Media.upToSmall()} { | ||
font-size: 13px; | ||
letter-spacing: -0.1px; | ||
} | ||
` | ||
|
||
export const Container = styled.div` | ||
padding: 12px 12px; | ||
width: 100%; | ||
height: 100%; | ||
border-radius: 24px; | ||
font-size: 14px; | ||
text-align: center; | ||
display: flex; | ||
flex-direction: row; | ||
Comment on lines
+33
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a big deal, but I think that flex's direction on React it's row by default. |
||
justify-content: space-between; | ||
gap: 10px; | ||
|
||
${Media.upToSmall()} { | ||
font-size: 13px; | ||
letter-spacing: -0.1px; | ||
} | ||
` | ||
|
||
export const Amount = styled.div` | ||
font-size: 15px; | ||
font-weight: 600; | ||
display: flex; | ||
flex-flow: column wrap; | ||
text-align: right; | ||
gap: 6px; | ||
|
||
// Targets FiatValue | ||
> div { | ||
font-weight: 500; | ||
font-size: 13px; | ||
} | ||
` | ||
|
||
export const TokenLogoWrapper = styled.div` | ||
display: inline-block; | ||
border-radius: 50%; | ||
line-height: 0; | ||
box-shadow: 0 2px 10px 0 ${({ theme }) => (theme.darkMode ? '#496e9f' : '#bfd6f7')}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are colors usually hard coded in this project? |
||
` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import styled from 'styled-components/macro' | ||
|
||
export const PoolTokensWrapper = styled.div` | ||
display: flex; | ||
flex-flow: column wrap; | ||
width: 100%; | ||
|
||
padding-bottom: 1rem; | ||
|
||
flex-grow: 1; | ||
` |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ types file |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Token } from '@uniswap/sdk-core' | ||
|
||
export interface IMinimalPool { | ||
id: string | ||
chain: string | ||
symbol: string | ||
dynamicData: { | ||
totalLiquidity: string | ||
volume24h: string | ||
} | ||
allTokens: { | ||
address: string | ||
symbol: string | ||
decimals: number | ||
}[] | ||
userBalance: { | ||
totalBalance: string | ||
} | ||
} | ||
|
||
export interface PoolBalance { | ||
token: Token | ||
balance: string | ||
fiatAmount: string | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { BigNumber } from 'ethers' | ||
|
||
export function multiplyValueByPct(value: string, pct: number): string { | ||
return BigNumber.from(value).mul(pct).div(100).toString() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#would
wrap this in useMemo