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

Feat/remove liquidity flow #185

Closed
wants to merge 67 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
808ddc3
first setup
groninge01 Dec 4, 2023
8dc1d6f
Merge branch 'main' into feat/remove-liquidity
groninge01 Dec 8, 2023
4cef6a2
rename
groninge01 Dec 8, 2023
c476d00
add component
groninge01 Dec 8, 2023
37aa4ed
update form
groninge01 Dec 8, 2023
eaa96d4
add proportional vs single
groninge01 Dec 8, 2023
188442d
format integer percentage
groninge01 Dec 11, 2023
210fe36
update component
groninge01 Dec 11, 2023
3c5866d
update remove-liquidity
groninge01 Dec 11, 2023
2ac3f57
update default values
groninge01 Dec 12, 2023
7ed09b2
switch on type
groninge01 Dec 12, 2023
08cb494
Merge branch 'main' into feat/remove-liquidity
groninge01 Dec 12, 2023
88c68c5
add bptPrice
groninge01 Dec 13, 2023
756395c
add row for bpt
groninge01 Dec 13, 2023
b8034e0
set correct remove liquidity type
groninge01 Dec 13, 2023
81d99d5
use row for bpt
groninge01 Dec 13, 2023
73dea31
Create remove liquidity core acchitecture
agualis Dec 14, 2023
1ac7213
Merge branch 'main' into feat/remove-liquidity-architecture
agualis Dec 14, 2023
1af39f5
Fix typecheck
agualis Dec 14, 2023
66b3a3b
Implement priceImpact query for remove liquidity
agualis Dec 14, 2023
27fe7d0
Merge branch 'main' into feat/remove-liquidity-architecture
agualis Dec 14, 2023
88d6ac5
Add liquidity removal step
agualis Dec 15, 2023
9cbddd2
Fix import
agualis Dec 15, 2023
ec4866a
Merge branch 'main' into feat/remove-liquidity-architecture
agualis Dec 15, 2023
4feba5e
Rename query
agualis Dec 15, 2023
8a2aff3
fix typecheck
agualis Dec 15, 2023
e82eebe
Refactor enabled check
agualis Dec 17, 2023
b5a81b6
reduce padding
groninge01 Dec 18, 2023
b67c042
increase spacing between input & tokenrow
groninge01 Dec 18, 2023
44f984d
remove gradient
groninge01 Dec 18, 2023
eef89c1
Refactor provider-handler interaction in Removal step
agualis Dec 18, 2023
e3fd87e
Refactor provider-handler interaction in Add liquidity step
agualis Dec 18, 2023
06f90d0
add isSelected
groninge01 Dec 18, 2023
4d3b208
set isSelected
groninge01 Dec 18, 2023
af3f805
add parse & format
groninge01 Dec 18, 2023
92872fe
parse & format input w/ slider
groninge01 Dec 18, 2023
d21ce12
Save query execution inside handler
agualis Dec 18, 2023
0f889da
add TODO
groninge01 Dec 18, 2023
cc79514
Merge branch 'main' into feat/remove-liquidity
groninge01 Dec 18, 2023
c2a7dc6
fix build error
groninge01 Dec 18, 2023
522e319
Refactor tests after outdated block
agualis Dec 18, 2023
d451d71
Fix unit test
agualis Dec 18, 2023
899aeda
Fix integration test
agualis Dec 18, 2023
a753e47
Fix integration test2
agualis Dec 18, 2023
bf012b6
Merge branch 'feat/remove-liquidity' into feat/remove-liquidity-flow
agualis Dec 19, 2023
8a9ccf5
Merge branch 'main' into feat/remove-liquidity-flow
agualis Dec 20, 2023
977670e
Merge branch 'main' into feat/remove-liquidity-flow
agualis Dec 20, 2023
03e8efb
Merge branch 'main' into feat/remove-liquidity-flow
agualis Dec 20, 2023
aafd30f
Merge branch 'main' into feat/remove-liquidity-flow
agualis Dec 21, 2023
c8e0c27
Refactor handlers and queries
agualis Dec 21, 2023
0582adf
Fix integration test
agualis Dec 21, 2023
254d17f
Remove approvals check in remove flow
agualis Dec 21, 2023
15b70bc
Extract defaultDebounceMillis
agualis Dec 21, 2023
e9af8a4
Fix useDebounce usage
agualis Dec 21, 2023
bc4b424
Add pool contants file
agualis Dec 21, 2023
5e6aca0
Improve comments
agualis Dec 21, 2023
d8554e9
Add safeTokenFormat
agualis Dec 21, 2023
192083d
Hook up useQueries in useRemoveLiquidity
agualis Dec 21, 2023
ee5f3e6
chore: Fix BPT values in preview modal. (#183)
garethfuller Dec 21, 2023
5d42f41
chore: Fix token order in modal (#184)
garethfuller Dec 21, 2023
bb030a4
Fix debounce calls
agualis Dec 21, 2023
8a8a6d1
Refactor query keys
agualis Dec 21, 2023
f32ab86
Merge branch 'main' into feat/remove-liquidity-architecture
agualis Dec 22, 2023
6f82a08
Fix typo
agualis Dec 22, 2023
cd6d941
Refactor token handling
agualis Dec 22, 2023
642ec67
Remove helpers from removal provider
agualis Dec 22, 2023
bb9b318
Fix single token removal types
agualis Dec 22, 2023
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
3 changes: 2 additions & 1 deletion lib/modules/pool/PoolList/PoolListSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useEffect } from 'react'
import { useDebounce } from '@/lib/shared/hooks/useDebounce'
import { usePoolList } from './usePoolList'
import { useBreakpoints } from '@/lib/shared/hooks/useBreakpoints'
import { defaultDebounceMs } from '@/lib/shared/utils/queries'

const SEARCH = 'search'

Expand All @@ -20,7 +21,7 @@ export function PoolListSearch() {
setSearch(event.target.value)
}

const debouncedChangeHandler = useDebounce(changeHandler, 300)
const debouncedChangeHandler = useDebounce(changeHandler, defaultDebounceMs)

useEffect(() => {
reset({
Expand Down
23 changes: 23 additions & 0 deletions lib/modules/pool/actions/LiquidityActionHelpers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { hasValidHumanAmounts } from './LiquidityActionHelpers'
import { HumanAmountIn } from './liquidity-types'

describe('hasValidHumanAmounts', () => {
test('when all humanAmounts are empty', () => {
const humanAmountsIn: HumanAmountIn[] = [
{ tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', humanAmount: '' },
{ tokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', humanAmount: '' },
]
expect(hasValidHumanAmounts(humanAmountsIn)).toBeFalsy()
})
test('when all humanAmounts are zero', () => {
const humanAmountsIn: HumanAmountIn[] = [
{ tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', humanAmount: '0' },
{ tokenAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', humanAmount: '0' },
]
expect(hasValidHumanAmounts(humanAmountsIn)).toBeFalsy()
})
test('when humanAmounts is an empty array', () => {
const humanAmountsIn: HumanAmountIn[] = []
expect(hasValidHumanAmounts(humanAmountsIn)).toBeFalsy()
})
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { getChainId, getNetworkConfig } from '@/lib/config/app.config'
import { TokenAmountToApprove } from '@/lib/modules/tokens/approvals/approval-rules'
import { nullAddress } from '@/lib/modules/web3/contracts/wagmi-helpers'
import { PoolStateInput } from '@balancer/sdk'
import { keyBy } from 'lodash'
import { isSameAddress } from '@/lib/shared/utils/addresses'
import { HumanAmount, PoolStateInput } from '@balancer/sdk'
import { Dictionary, keyBy } from 'lodash'
import { parseUnits } from 'viem'
import { Address } from 'wagmi'
import { toPoolStateInput } from '../../pool.helpers'
import { Pool } from '../../usePool'
import { HumanAmountInWithTokenInfo } from './AddLiquidityFlowButton'
import { HumanAmountIn } from './add-liquidity.types'
import { isSameAddress } from '@/lib/shared/utils/addresses'
import { toPoolStateInput } from '../pool.helpers'
import { Pool } from '../usePool'
import { HumanAmountIn } from './liquidity-types'
import { GqlToken } from '@/lib/shared/services/api/generated/graphql'

// TODO: this should be imported from the SDK
export type InputAmount = {
Expand All @@ -27,10 +27,10 @@ const NullPool: Pool = {
} as unknown as Pool

/*
AddLiquidityHelpers provides helper methods to traverse the pool state and prepare data structures needed by add liquidity handlers
to implement the AddLiquidityHandler interface
This class provides helper methods to traverse the pool state and prepare data structures needed by add/remove liquidity handlers
to implement the Add/RemoveLiquidityHandler interface
*/
export class AddLiquidityHelpers {
export class LiquidityActionHelpers {
constructor(public pool: Pool = NullPool) {}

public get poolStateInput(): PoolStateInput {
Expand All @@ -50,15 +50,16 @@ export class AddLiquidityHelpers {
}

public getAmountsToApprove(
humanAmountsInWithTokenInfo: HumanAmountInWithTokenInfo[]
humanAmountsIn: HumanAmountIn[],
tokensByAddress: Dictionary<GqlToken>
): TokenAmountToApprove[] {
return this.toInputAmounts(humanAmountsInWithTokenInfo).map(({ address, rawAmount }, index) => {
const humanAmountWithInfo = humanAmountsInWithTokenInfo[index]
return this.toInputAmounts(humanAmountsIn).map(({ address, rawAmount }, index) => {
const humanAmountIn = humanAmountsIn[index]
return {
tokenAddress: address,
humanAmount: humanAmountWithInfo.humanAmount || '0',
humanAmount: humanAmountIn.humanAmount || '0',
rawAmount,
tokenSymbol: humanAmountWithInfo.symbol,
tokenSymbol: tokensByAddress[humanAmountIn.tokenAddress].symbol,
}
})
}
Expand Down Expand Up @@ -97,3 +98,14 @@ export class AddLiquidityHelpers {
return humanAmountsIn.some(amountIn => isSameAddress(amountIn.tokenAddress, nativeAssetAddress))
}
}

export const isEmptyAmount = (amountIn: HumanAmountIn) => isEmptyHumanAmount(amountIn.humanAmount)

export const isEmptyHumanAmount = (humanAmount: HumanAmount | '') =>
!humanAmount || humanAmount === '0'

export const areEmptyAmounts = (humanAmountsIn: HumanAmountIn[]) =>
!humanAmountsIn || humanAmountsIn.length === 0 || humanAmountsIn.every(isEmptyAmount)

export const hasValidHumanAmounts = (humanAmountsIn: HumanAmountIn[]) =>
humanAmountsIn.some(a => a.humanAmount && a.humanAmount !== '0')
26 changes: 15 additions & 11 deletions lib/modules/pool/actions/add-liquidity/AddLiquidityFlowButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@ import { useNextTokenApprovalStep } from '@/lib/modules/tokens/approvals/useNext
import TransactionFlow from '@/lib/shared/components/btns/transaction-steps/TransactionFlow'
import { GqlToken } from '@/lib/shared/services/api/generated/graphql'
import { Text, VStack } from '@chakra-ui/react'
import { HumanAmountIn } from './add-liquidity.types'
import { useConstructAddLiquidityStep } from './useConstructAddLiquidityStep'
import { useAddLiquidity } from './useAddLiquidity'

export type HumanAmountInWithTokenInfo = HumanAmountIn & GqlToken
import { HumanAmountIn } from '../liquidity-types'
import { useTokens } from '@/lib/modules/tokens/useTokens'
import { zipObject } from 'lodash'
import { Pool } from '../../usePool'

type Props = {
humanAmountsInWithTokenInfo: HumanAmountInWithTokenInfo[]
poolId: string
humanAmountsIn: HumanAmountIn[]
pool: Pool
}
export function AddLiquidityFlowButton({ humanAmountsInWithTokenInfo, poolId }: Props) {
export function AddLiquidityFlowButton({ humanAmountsIn, pool }: Props) {
const { helpers } = useAddLiquidity()
const { getToken } = useTokens()

const tokenAddresses = humanAmountsIn.map(h => h.tokenAddress)
const tokens = humanAmountsIn.map(h => getToken(h.tokenAddress, pool.chain))
const tokensByAddress = zipObject(tokenAddresses, tokens as GqlToken[])

const { tokenApprovalStep, initialAmountsToApprove } = useNextTokenApprovalStep(
helpers.getAmountsToApprove(humanAmountsInWithTokenInfo)
helpers.getAmountsToApprove(humanAmountsIn, tokensByAddress)
)

const { step: addLiquidityStep } = useConstructAddLiquidityStep(
humanAmountsInWithTokenInfo,
poolId
)
const { step: addLiquidityStep } = useConstructAddLiquidityStep(pool.id)
const steps = [tokenApprovalStep, addLiquidityStep]

function handleJoinCompleted() {
Expand Down
17 changes: 11 additions & 6 deletions lib/modules/pool/actions/add-liquidity/AddLiquidityForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,20 @@ import { useRef } from 'react'
import { Address } from 'wagmi'
import { AddLiquidityModal } from './AddLiquidityModal'
import { useAddLiquidity } from './useAddLiquidity'
import { fNum, safeTokenFormat } from '@/lib/shared/utils/numbers'
import { BPT_DECIMALS } from '../../pool.constants'

export function AddLiquidityForm() {
const {
amountsIn,
humanAmountsIn: amountsIn,
totalUSDValue,
setAmountIn,
setHumanAmountIn: setAmountIn,
tokens,
validTokens,
formattedPriceImpact,
priceImpact,
isPriceImpactLoading,
bptOutUnits,
isBptOutQueryLoading,
bptOut,
isPreviewQueryLoading,
isDisabled,
disabledReason,
} = useAddLiquidity()
Expand All @@ -48,6 +50,9 @@ export function AddLiquidityForm() {
return amountIn ? amountIn.humanAmount : ''
}

const bptOutLabel = safeTokenFormat(bptOut?.amount, BPT_DECIMALS)
const formattedPriceImpact = priceImpact ? fNum('priceImpact', priceImpact) : '-'

return (
<TokenBalancesProvider tokens={validTokens}>
<Center h="full" w="full" maxW="lg" mx="auto">
Expand Down Expand Up @@ -100,7 +105,7 @@ export function AddLiquidityForm() {
<Text color="GrayText">Bpt out</Text>
<HStack>
<NumberText color="GrayText">
{isBptOutQueryLoading ? <Skeleton w="12" h="full" /> : bptOutUnits}
{isPreviewQueryLoading ? <Skeleton w="12" h="full" /> : bptOutLabel}
</NumberText>
<Tooltip label="Bpt out" fontSize="sm">
<InfoOutlineIcon color="GrayText" />
Expand Down
48 changes: 31 additions & 17 deletions lib/modules/pool/actions/add-liquidity/AddLiquidityModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TokenAllowancesProvider } from '@/lib/modules/web3/useTokenAllowances'
import { useUserAccount } from '@/lib/modules/web3/useUserAccount'
import { NumberText } from '@/lib/shared/components/typography/NumberText'
import { useCurrency } from '@/lib/shared/hooks/useCurrency'
import { isSameAddress } from '@/lib/shared/utils/addresses'
import { fNum } from '@/lib/shared/utils/numbers'
import { HumanAmount } from '@balancer/sdk'
import { InfoOutlineIcon } from '@chakra-ui/icons'
Expand All @@ -27,9 +28,13 @@ import {
VStack,
} from '@chakra-ui/react'
import { RefObject, useRef } from 'react'
import { formatUnits } from 'viem'
import { Address } from 'wagmi'
import { BPT_DECIMALS } from '../../pool.constants'
import { bptUsdValue } from '../../pool.helpers'
import { usePool } from '../../usePool'
import { AddLiquidityFlowButton, HumanAmountInWithTokenInfo } from './AddLiquidityFlowButton'
import { HumanAmountIn } from '../liquidity-types'
import { AddLiquidityFlowButton } from './AddLiquidityFlowButton'
import { useAddLiquidity } from './useAddLiquidity'

type Props = {
Expand All @@ -43,17 +48,24 @@ function TokenAmountRow({
tokenAddress,
humanAmount,
symbol,
isBpt,
}: {
tokenAddress: Address
humanAmount: HumanAmount | ''
symbol?: string
isBpt?: boolean
}) {
const { pool } = usePool()
const { getToken, usdValueForToken } = useTokens()
const { toCurrency } = useCurrency()

const token = getToken(tokenAddress, pool.chain)
const usdValue = token ? usdValueForToken(token, humanAmount) : undefined
let usdValue: string | undefined
if (isBpt) {
usdValue = bptUsdValue(pool, humanAmount)
} else {
usdValue = token ? usdValueForToken(token, humanAmount) : undefined
}

return (
<HStack w="full" justify="space-between">
Expand All @@ -79,19 +91,15 @@ export function AddLiquidityModal({
...rest
}: Props & Omit<ModalProps, 'children'>) {
const initialFocusRef = useRef(null)
const { amountsIn, totalUSDValue, helpers, formattedPriceImpact, bptOutUnits } = useAddLiquidity()
const { humanAmountsIn, totalUSDValue, helpers, bptOut, priceImpact, tokens } = useAddLiquidity()
const { toCurrency } = useCurrency()
const { pool } = usePool()
// TODO: move userAddress up
const spenderAddress = useContractAddress('balancer.vaultV2')
const { userAddress } = useUserAccount()
const { getToken } = useTokens()
const humanAmountsInWithTokenInfo: HumanAmountInWithTokenInfo[] = amountsIn.map(humanAmountIn => {
return {
...humanAmountIn,
...getToken(humanAmountIn.tokenAddress, pool.chain),
} as HumanAmountInWithTokenInfo
})

const bptOutLabel = bptOut ? formatUnits(bptOut.amount, BPT_DECIMALS) : '0'
const formattedPriceImpact = priceImpact ? fNum('priceImpact', priceImpact) : '-'

return (
<Modal
Expand All @@ -118,22 +126,28 @@ export function AddLiquidityModal({
<Text color="GrayText">{"You're adding"}</Text>
<NumberText fontSize="lg">{toCurrency(totalUSDValue)}</NumberText>
</HStack>
{amountsIn.map(amountIn => (
<TokenAmountRow key={amountIn.tokenAddress} {...amountIn} />
))}
{tokens.map(token => {
if (!token) return <div>Missing token</div>

const amountIn = humanAmountsIn.find(amountIn =>
isSameAddress(amountIn.tokenAddress, token?.address)
) as HumanAmountIn

return <TokenAmountRow key={token.address} {...amountIn} />
})}
</VStack>
</Card>

<Card variant="level0" p="md" shadow="sm" w="full">
<VStack align="start" spacing="md">
<HStack justify="space-between" w="full">
<Text color="GrayText">{"You'll get (if no slippage)"}</Text>
<Text color="GrayText">{pool.symbol}</Text>
</HStack>
<TokenAmountRow
tokenAddress={pool.address as Address}
humanAmount={bptOutUnits as HumanAmount}
humanAmount={bptOutLabel as HumanAmount}
symbol="LP Token"
isBpt
/>
</VStack>
</Card>
Expand All @@ -160,8 +174,8 @@ export function AddLiquidityModal({
tokenAddresses={helpers.poolTokenAddresses}
>
<AddLiquidityFlowButton
humanAmountsInWithTokenInfo={humanAmountsInWithTokenInfo}
poolId={pool.id}
humanAmountsIn={humanAmountsIn}
pool={pool}
></AddLiquidityFlowButton>
</TokenAllowancesProvider>
</ModalFooter>
Expand Down

This file was deleted.

11 changes: 2 additions & 9 deletions lib/modules/pool/actions/add-liquidity/add-liquidity.types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { AddLiquidityQueryOutput, HumanAmount, PriceImpact, TokenAmount } from '@balancer/sdk'
import { AddLiquidityQueryOutput, PriceImpact, TokenAmount } from '@balancer/sdk'
import { Address } from 'wagmi'
import { HumanAmountIn } from '../liquidity-types'

// TODO: this type should be exposed by the SDK
export type PriceImpactAmount = Awaited<ReturnType<typeof PriceImpact.addLiquidityUnbalanced>>

export type HumanAmountIn = {
humanAmount: HumanAmount | ''
tokenAddress: Address
}

export type AddLiquidityInputs = {
humanAmountsIn: HumanAmountIn[]
account?: Address
Expand All @@ -22,9 +18,6 @@ export type AddLiquidityOutputs = {
sdkQueryOutput?: AddLiquidityQueryOutput
}

// sdkQueryOutput is optional because it will be only used in cases where we use the SDK to query/build the transaction
// We will probably need a more abstract interface to be used by edge cases
export type BuildLiquidityInputs = {
inputs: AddLiquidityInputs
sdkQueryOutput?: AddLiquidityQueryOutput
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {

/**
* AddLiquidityHandler is an interface that defines the methods that must be implemented by a handler.
* They take standard inputs from the UI and return frontend standardised
* outputs. The outputs should not be return types from the SDK. This is to
* They take standard inputs from the UI and return frontend standardised outputs.
* The outputs should not be return types from the SDK. This is to
* allow handlers to be developed in the future that may not use the SDK.
*/
export interface AddLiquidityHandler {
Expand Down
Loading