Skip to content
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

Closed
Show file tree
Hide file tree
Changes from all 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
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 }
})}
Comment on lines +43 to +45

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

setSelectedItem={({ id }) =>
setSelectedAirdrop(connectedChainAirdrops.find((airdrop) => airdrop.name === id))
}
Comment on lines +46 to +48

Choose a reason for hiding this comment

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

#would wrap this in useCallback

text={selectedAirdrop?.name || 'Select Airdrop'}
/>
</Row>
<Row>
<ClaimableAmountContainer>
Expand Down
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{' '}

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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',
},
},
]
}

Choose a reason for hiding this comment

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

Loved this picture 😆

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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.
https://robinpokorny.com/blog/index-as-a-key-is-an-anti-pattern/

</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

Choose a reason for hiding this comment

The 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')};

Choose a reason for hiding this comment

The 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;
`

Choose a reason for hiding this comment

The 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()
}
Loading