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

Spike [handlers]: Merge preview and build queries into one #206

Closed
wants to merge 2 commits into from
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
4 changes: 2 additions & 2 deletions lib/modules/pool/actions/add-liquidity/AddLiquidityForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function AddLiquidityForm() {
priceImpact,
isPriceImpactLoading,
bptOut,
isPreviewQueryLoading,
isMixedQueryLoading,
isDisabled,
disabledReason,
stopRefetchCountdown,
Expand Down Expand Up @@ -113,7 +113,7 @@ export function AddLiquidityForm() {
<Text color="GrayText">Bpt out</Text>
<HStack>
<NumberText color="GrayText">
{isPreviewQueryLoading ? <Skeleton w="12" h="full" /> : bptOutLabel}
{isMixedQueryLoading ? <Skeleton w="12" h="full" /> : bptOutLabel}
</NumberText>
<Tooltip label="Bpt out" fontSize="sm">
<InfoOutlineIcon color="GrayText" />
Expand Down
6 changes: 6 additions & 0 deletions lib/modules/pool/actions/add-liquidity/add-liquidity.types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { TokenAmount } from '@balancer/sdk'
import { Address } from 'wagmi'

export type QueryAddLiquidityOutput = {
bptOut: TokenAmount
}

export type MixedAddLiquidityOutput = {
bptOut: TokenAmount
transactionConfig?: TransactionConfig
}

export type BuildAddLiquidityInput = {
account: Address
slippagePercent: string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { Address } from 'viem'
import { HumanAmountIn } from '../../liquidity-types'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { MixedAddLiquidityOutput } from '../add-liquidity.types'

/**
* AddLiquidityHandler is an interface that defines the methods that must be implemented by a handler.
Expand All @@ -18,12 +18,19 @@ import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidit
export interface AddLiquidityHandler {
// Query the expected output of adding liquidity and store it inside the handler instance
// Also returns bptOut to be used by the UI
queryAddLiquidity(humanAmountsIn: HumanAmountIn[]): Promise<QueryAddLiquidityOutput>
// queryAddLiquidity(humanAmountsIn: HumanAmountIn[]): Promise<QueryAddLiquidityOutput>
// Calculate the price impact of adding liquidity
calculatePriceImpact(humanAmountsIn: HumanAmountIn[]): Promise<number>
/*
Build tx callData payload for adding liquidity
It is responsibility of the UI to avoid calling buildAddLiquidityCallData before the last queryAddLiquidity was finished
*/
buildAddLiquidityCallData(inputs: BuildAddLiquidityInput): Promise<TransactionConfig>
// buildAddLiquidityCallData(inputs: BuildAddLiquidityInput): Promise<TransactionConfig>

mixed(
humanAmountsIn: HumanAmountIn[],
account: Address,
slippagePercent: string,
isBuildCallReady: boolean
): Promise<MixedAddLiquidityOutput>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ import {
AddLiquidityKind,
AddLiquidityQueryOutput,
AddLiquidityUnbalancedInput,
Address,
PriceImpact,
PriceImpactAmount,
Slippage,
} from '@balancer/sdk'
import { Pool } from '../../../usePool'
import {
LiquidityActionHelpers,
areEmptyAmounts,
ensureLastQueryResponse,
} from '../../LiquidityActionHelpers'
import { LiquidityActionHelpers, areEmptyAmounts } from '../../LiquidityActionHelpers'
import { HumanAmountIn } from '../../liquidity-types'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { MixedAddLiquidityOutput } from '../add-liquidity.types'
import { AddLiquidityHandler } from './AddLiquidity.handler'

/**
Expand All @@ -29,25 +26,11 @@ import { AddLiquidityHandler } from './AddLiquidity.handler'
*/
export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
helpers: LiquidityActionHelpers
queryResponse?: AddLiquidityQueryOutput

constructor(pool: Pool) {
this.helpers = new LiquidityActionHelpers(pool)
}

public async queryAddLiquidity(
humanAmountsIn: HumanAmountIn[]
): Promise<QueryAddLiquidityOutput> {
// Deletes the previous queryResponse to enforce that we don't build callData with an outdated queryResponse (while a new one is loading)
this.queryResponse = undefined
const addLiquidity = new AddLiquidity()
const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

this.queryResponse = await addLiquidity.query(addLiquidityInput, this.helpers.poolStateInput)

return { bptOut: this.queryResponse.bptOut }
}

public async calculatePriceImpact(humanAmountsIn: HumanAmountIn[]): Promise<number> {
if (areEmptyAmounts(humanAmountsIn)) {
// Avoid price impact calculation when there are no amounts in
Expand All @@ -64,16 +47,52 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
return priceImpactABA.decimal
}

public async buildAddLiquidityCallData({
account,
slippagePercent,
}: BuildAddLiquidityInput): Promise<TransactionConfig> {
this.queryResponse = ensureLastQueryResponse('Unbalanced add liquidity', this.queryResponse)
public async mixed(
humanAmountsIn: HumanAmountIn[],
account: Address,
slippagePercent: string,
isBuildCallReady: boolean // This is true when the user isconnected and the flow is in the "build call data step" (for example, after the approval steps are finished)
): Promise<MixedAddLiquidityOutput> {
const queryResponse = await this.queryAddLiquidity(humanAmountsIn)

if (!isBuildCallReady) {
return {
bptOut: queryResponse.bptOut,
}
}

const buildCallDataResponse = await this.buildAddLiquidityCallData(
account,
slippagePercent,
queryResponse
)
return {
bptOut: queryResponse.bptOut,
transactionConfig: buildCallDataResponse,
}
}

/**
* PRIVATE METHODS
*/

private async queryAddLiquidity(humanAmountsIn: HumanAmountIn[]) {
// Deletes the previous queryResponse to enforce that we don't build callData with an outdated queryResponse (while a new one is loading)
const addLiquidity = new AddLiquidity()
const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

return await addLiquidity.query(addLiquidityInput, this.helpers.poolStateInput)
}

private async buildAddLiquidityCallData(
account: Address,
slippagePercent: string,
queryResponse: AddLiquidityQueryOutput
): Promise<TransactionConfig> {
const addLiquidity = new AddLiquidity()

const { call, to, value } = addLiquidity.buildCall({
...this.queryResponse,
...queryResponse,
slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`),
sender: account,
recipient: account,
Expand All @@ -88,9 +107,6 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
}
}

/**
* PRIVATE METHODS
*/
private constructSdkInput(humanAmountsIn: HumanAmountIn[]): AddLiquidityUnbalancedInput {
const amountsIn = this.helpers.toInputAmounts(humanAmountsIn)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ type LiquidityParams = {
poolId: string
slippage: string
humanAmountsIn: HumanAmountIn[]
isBuildCallReady: boolean
}
function liquidityParams({ userAddress, poolId, slippage, humanAmountsIn }: LiquidityParams) {
return `${userAddress}:${poolId}:${slippage}:${JSON.stringify(humanAmountsIn)}`
function liquidityParams({
userAddress,
poolId,
slippage,
humanAmountsIn,
isBuildCallReady = true,
}: LiquidityParams) {
return `${userAddress}:${poolId}:${slippage}:${isBuildCallReady}:${JSON.stringify(
humanAmountsIn
)}`
}
export const addLiquidityKeys = {
priceImpact: (params: LiquidityParams) =>
[addLiquidity, 'price-impact', liquidityParams(params)] as const,
preview: (params: LiquidityParams) => [addLiquidity, 'preview', liquidityParams(params)] as const,
buildCallData: (params: LiquidityParams) =>
[addLiquidity, 'buildCallData', liquidityParams(params)] as const,
mixed: (params: LiquidityParams) => [addLiquidity, 'mixed', liquidityParams(params)] as const,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client'

import { useUserSettings } from '@/lib/modules/user/settings/useUserSettings'
import { useUserAccount } from '@/lib/modules/web3/useUserAccount'
import { defaultDebounceMs } from '@/lib/shared/utils/queries'
import { useDebounce } from 'use-debounce'
import { useQuery } from 'wagmi'
import { hasValidHumanAmounts } from '../../LiquidityActionHelpers'
import { HumanAmountIn } from '../../liquidity-types'
import { AddLiquidityHandler } from '../handlers/AddLiquidity.handler'
import { addLiquidityKeys } from './add-liquidity-keys'

export type AddLiquidityPreviewQueryResult = ReturnType<typeof useAddLiquidityMixedQuery>

export function useAddLiquidityMixedQuery(
handler: AddLiquidityHandler,
humanAmountsIn: HumanAmountIn[],
poolId: string,
isBuildCallReady: boolean,
startRefetchCountdown: () => void
) {
const { userAddress, isConnected } = useUserAccount()
const { slippage } = useUserSettings()
const debouncedHumanAmountsIn = useDebounce(humanAmountsIn, defaultDebounceMs)[0]

const query = useQuery(
addLiquidityKeys.mixed({
userAddress,
slippage,
poolId,
humanAmountsIn: debouncedHumanAmountsIn,
isBuildCallReady,
}),
async () => {
if (isBuildCallReady) startRefetchCountdown()

return await handler.mixed(humanAmountsIn, userAddress, slippage, isBuildCallReady)
},
{
// enabled: isConnected && hasValidHumanAmounts(debouncedHumanAmountsIn),
enabled: hasValidHumanAmounts(debouncedHumanAmountsIn),
cacheTime: 0,
}
)

return {
bptOut: query.data?.bptOut,
isMixedQueryLoading: query.isLoading,
refetchMixedQuery: query.refetch,
transactionConfig: query.data?.transactionConfig,
mixedQueryError: query.error,
}
}
47 changes: 19 additions & 28 deletions lib/modules/pool/actions/add-liquidity/useAddLiquidity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
'use client'

import { useTokens } from '@/lib/modules/tokens/useTokens'
import { useUserAccount } from '@/lib/modules/web3/useUserAccount'
import { useRefetchCountdown } from '@/lib/shared/hooks/transaction-flows/useRefetchCountdown'
import { LABELS } from '@/lib/shared/labels'
import { GqlToken } from '@/lib/shared/services/api/generated/graphql'
import { isSameAddress } from '@/lib/shared/utils/addresses'
import { useMandatoryContext } from '@/lib/shared/utils/contexts'
import { isDisabledWithReason } from '@/lib/shared/utils/functions/isDisabledWithReason'
import { safeSum } from '@/lib/shared/utils/numbers'
import { sleep } from '@/lib/shared/utils/time'
import { makeVar, useReactiveVar } from '@apollo/client'
import { HumanAmount } from '@balancer/sdk'
import { PropsWithChildren, createContext, useEffect, useMemo } from 'react'
import { PropsWithChildren, createContext, useEffect, useMemo, useState } from 'react'
import { Address } from 'viem'
import { usePool } from '../../usePool'
import { useAddLiquidityPreviewQuery } from './queries/useAddLiquidityPreviewQuery'
import { useAddLiquidityPriceImpactQuery } from './queries/useAddLiquidityPriceImpactQuery'
import { HumanAmountIn } from '../liquidity-types'
import { LiquidityActionHelpers, areEmptyAmounts } from '../LiquidityActionHelpers'
import { useAddLiquidityBuildCallDataQuery } from './queries/useAddLiquidityBuildCallDataQuery'
import { isDisabledWithReason } from '@/lib/shared/utils/functions/isDisabledWithReason'
import { useUserAccount } from '@/lib/modules/web3/useUserAccount'
import { LABELS } from '@/lib/shared/labels'
import { HumanAmountIn } from '../liquidity-types'
import { selectAddLiquidityHandler } from './handlers/selectAddLiquidityHandler'
import { useRefetchCountdown } from '@/lib/shared/hooks/transaction-flows/useRefetchCountdown'
import { sleep } from '@/lib/shared/utils/time'
import { useAddLiquidityMixedQuery } from './queries/useAddLiquidityMixedQuery'
import { useAddLiquidityPriceImpactQuery } from './queries/useAddLiquidityPriceImpactQuery'

export type UseAddLiquidityResponse = ReturnType<typeof _useAddLiquidity>
export const AddLiquidityContext = createContext<UseAddLiquidityResponse | null>(null)

export const humanAmountsInVar = makeVar<HumanAmountIn[]>([])

export function _useAddLiquidity() {
const [isBuildCallReady, setBuildCallReady] = useState(false)
const humanAmountsIn = useReactiveVar(humanAmountsInVar)

const { pool, poolStateInput } = usePool()
Expand Down Expand Up @@ -92,35 +92,24 @@ export function _useAddLiquidity() {
pool.id
)

const { isPreviewQueryLoading, bptOut, refetchPreviewQuery } = useAddLiquidityPreviewQuery(
handler,
humanAmountsIn,
pool.id
)

const { secondsToRefetch, startRefetchCountdown, stopRefetchCountdown } = useRefetchCountdown()

let refetchBuildQuery: () => Promise<object>
function useBuildCallData(isActiveStep: boolean) {
const buildQuery = useAddLiquidityBuildCallDataQuery(
const { bptOut, isMixedQueryLoading, refetchMixedQuery, transactionConfig, mixedQueryError } =
useAddLiquidityMixedQuery(
handler,
humanAmountsIn,
isActiveStep,
pool,
pool.id,
isBuildCallReady,
startRefetchCountdown
)
refetchBuildQuery = buildQuery.refetch
return buildQuery
}

useEffect(() => {
const refetchQueries = async () => {
// TODO: remove after manual feature tests
console.log('Refetching preview, priceImpact and build queries')
stopRefetchCountdown()
await sleep(1000) // TODO: Show some kind of UI feedback during this artificial delay
await Promise.all([refetchPreviewQuery(), refetchPriceImpact()])
await refetchBuildQuery()
await Promise.all([refetchMixedQuery(), refetchPriceImpact()])
startRefetchCountdown()
}
if (secondsToRefetch === 0) {
Expand All @@ -143,15 +132,17 @@ export function _useAddLiquidity() {
isPriceImpactLoading,
priceImpact,
bptOut,
isPreviewQueryLoading,
transactionConfig,
mixedQueryError,
isMixedQueryLoading,
setHumanAmountIn,
useBuildCallData,
isDisabled,
disabledReason,
helpers,
poolStateInput,
secondsToRefetch,
stopRefetchCountdown,
setBuildCallReady,
}
}

Expand Down
Loading
Loading