Skip to content

Commit

Permalink
refactor: extract base abstract class
Browse files Browse the repository at this point in the history
  • Loading branch information
agualis committed Sep 27, 2024
1 parent 49fd31a commit cbec43a
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 189 deletions.
11 changes: 1 addition & 10 deletions lib/modules/pool/actions/LiquidityActionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,6 @@ export class LiquidityActionHelpers {
return humanAmountsIn.some(amountIn => isSameAddress(amountIn.tokenAddress, nativeAssetAddress))
}

public includesNativeAsset(amountsIn: TokenAmount[]): boolean {
const nativeAssetAddress = this.networkConfig.tokens.nativeAsset.address

return amountsIn.some(amountIn => isSameAddress(amountIn.token.address, nativeAssetAddress))
}

public isNativeAsset(tokenAddress: Address): boolean {
const nativeAssetAddress = this.networkConfig.tokens.nativeAsset.address

Expand Down Expand Up @@ -319,10 +313,7 @@ export function hasNoLiquidity(pool: Pool): boolean {
}

// When the pool has version < v3, it adds extra buildCall params (sender and recipient) that must be present only in V1/V2
export function formatBuildCallParams<T>(buildCallParams: T, isV3Pool: boolean, account: Address) {
// sender must be undefined for v3 pools
if (isV3Pool) return buildCallParams

export function formatBuildCallParams<T>(buildCallParams: T, account: Address) {
// sender and recipient must be defined only for v1 and v2 pools
return { ...buildCallParams, sender: account, recipient: account }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { AddLiquidityQueryOutput, Permit2, PublicWalletClient } from '@balancer/sdk'
import { AddLiquidityQueryOutput } from '@balancer/sdk'
import { Address } from 'viem'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { NoncesByTokenAddress } from '@/lib/modules/tokens/approvals/permit2/usePermit2Nonces'

export interface Permit2AddLiquidityInput {
account: Address
Expand Down Expand Up @@ -36,13 +35,4 @@ export interface AddLiquidityHandler {
It is responsibility of the UI to avoid calling buildAddLiquidityCallData before the last queryAddLiquidity was finished
*/
buildCallData(inputs: BuildAddLiquidityInput): Promise<TransactionConfig>

/* Sign permit2 for adding liquidity (for now only used by v3 pools)
TODO: generalize for other handlers using permit2
*/
signPermit2?(
input: Permit2AddLiquidityInput,
walletClient: PublicWalletClient,
nonces?: NoncesByTokenAddress
): Promise<Permit2>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { getRpcUrl } from '@/lib/modules/web3/transports'
import {
AddLiquidity,
AddLiquidityKind,
AddLiquidityUnbalancedInput,
PriceImpact,
PriceImpactAmount,
} from '@balancer/sdk'
import { Pool } from '../../../PoolProvider'
import { LiquidityActionHelpers, areEmptyAmounts } from '../../LiquidityActionHelpers'
import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types'
import { AddLiquidityHandler } from './AddLiquidity.handler'

/**
* UnbalancedAddLiquidityHandler is a handler that implements the
* AddLiquidityHandler interface for unbalanced adds, e.g. where the user
* specifies the token amounts in. It uses the Balancer SDK to implement it's
* methods. It also handles the case where one of the input tokens is the native
* asset instead of the wrapped native asset.
*/
export abstract class BaseUnbalancedAddLiquidityHandler implements AddLiquidityHandler {
protected helpers: LiquidityActionHelpers

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

public async simulate(
humanAmountsIn: HumanTokenAmountWithAddress[]
): Promise<SdkQueryAddLiquidityOutput> {
const addLiquidity = new AddLiquidity()
const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const sdkQueryOutput = await addLiquidity.query(addLiquidityInput, this.helpers.poolState)

return { bptOut: sdkQueryOutput.bptOut, sdkQueryOutput }
}

public async getPriceImpact(humanAmountsIn: HumanTokenAmountWithAddress[]): Promise<number> {
if (areEmptyAmounts(humanAmountsIn)) {
// Avoid price impact calculation when there are no amounts in
return 0
}

const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const priceImpactABA: PriceImpactAmount = await PriceImpact.addLiquidityUnbalanced(
addLiquidityInput,
this.helpers.poolState
)

return priceImpactABA.decimal
}

public abstract buildCallData(input: SdkBuildAddLiquidityInput): Promise<TransactionConfig>

/**
* PRIVATE METHODS
*/
protected constructSdkInput(
humanAmountsIn: HumanTokenAmountWithAddress[]
): AddLiquidityUnbalancedInput {
const amountsIn = this.helpers.toSdkInputAmounts(humanAmountsIn)

return {
chainId: this.helpers.chainId,
rpcUrl: getRpcUrl(this.helpers.chainId),
amountsIn,
kind: AddLiquidityKind.Unbalanced,
}
}
}
Original file line number Diff line number Diff line change
@@ -1,96 +1,40 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { getRpcUrl } from '@/lib/modules/web3/transports'
import {
AddLiquidity,
AddLiquidityBaseBuildCallInput,
AddLiquidityBaseQueryOutput,
AddLiquidityKind,
AddLiquidityUnbalancedInput,
Permit2,
Permit2Helper,
PriceImpact,
PriceImpactAmount,
PublicWalletClient,
Slippage,
} from '@balancer/sdk'
import { Pool } from '../../../PoolProvider'
import {
LiquidityActionHelpers,
areEmptyAmounts,
formatBuildCallParams,
} from '../../LiquidityActionHelpers'
import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types'
import { AddLiquidityHandler, Permit2AddLiquidityInput } from './AddLiquidity.handler'
import { NoncesByTokenAddress } from '@/lib/modules/tokens/approvals/permit2/usePermit2Nonces'
import { AddLiquidity } from '@balancer/sdk'
import { formatBuildCallParams } from '../../LiquidityActionHelpers'
import { SdkBuildAddLiquidityInput } from '../add-liquidity.types'
import { BaseUnbalancedAddLiquidityHandler } from './BaseUnbalancedAddLiquidity.handler'
import { constructBaseBuildCallInput } from './v3Helpers'

/**
* UnbalancedAddLiquidityHandler is a handler that implements the
* AddLiquidityHandler interface for unbalanced adds, e.g. where the user
* AddLiquidityHandler interface for unbalanced adds in v1(cowAMM) and v2 pools, e.g. where the user
* specifies the token amounts in. It uses the Balancer SDK to implement it's
* methods. It also handles the case where one of the input tokens is the native
* asset instead of the wrapped native asset.
*/
export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
helpers: LiquidityActionHelpers

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

public async simulate(
humanAmountsIn: HumanTokenAmountWithAddress[]
): Promise<SdkQueryAddLiquidityOutput> {
const addLiquidity = new AddLiquidity()
const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const sdkQueryOutput = await addLiquidity.query(addLiquidityInput, this.helpers.poolState)

return { bptOut: sdkQueryOutput.bptOut, sdkQueryOutput }
}

public async getPriceImpact(humanAmountsIn: HumanTokenAmountWithAddress[]): Promise<number> {
if (areEmptyAmounts(humanAmountsIn)) {
// Avoid price impact calculation when there are no amounts in
return 0
}

const addLiquidityInput = this.constructSdkInput(humanAmountsIn)

const priceImpactABA: PriceImpactAmount = await PriceImpact.addLiquidityUnbalanced(
addLiquidityInput,
this.helpers.poolState
)

return priceImpactABA.decimal
}

export class UnbalancedAddLiquidityHandler extends BaseUnbalancedAddLiquidityHandler {
public async buildCallData({
humanAmountsIn,
slippagePercent,
queryOutput,
account,
permit2,
}: SdkBuildAddLiquidityInput): Promise<TransactionConfig> {
const addLiquidity = new AddLiquidity()

const buildCallParams = formatBuildCallParams(
this.constructBaseBuildCallInput({
constructBaseBuildCallInput({
humanAmountsIn,
sdkQueryOutput: queryOutput.sdkQueryOutput,
slippagePercent: slippagePercent,
pool: this.helpers.pool,
}),
this.helpers.isV3Pool(),
account
)

if (this.helpers.isV3Pool() && !permit2) {
throw new Error('Permit2 signature is required for V3 pools')
}
console.log({ buildCallParams })

const { callData, to, value } =
this.helpers.isV3Pool() && permit2
? addLiquidity.buildCallWithPermit2(buildCallParams, permit2)
: addLiquidity.buildCall(buildCallParams)
const { callData, to, value } = addLiquidity.buildCall(buildCallParams)

return {
account,
Expand All @@ -100,56 +44,4 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
value,
}
}

public async signPermit2(
input: Permit2AddLiquidityInput,
sdkClient: PublicWalletClient,
nonces: NoncesByTokenAddress
): Promise<Permit2> {
const baseInput = this.constructBaseBuildCallInput({
slippagePercent: input.slippagePercent,
sdkQueryOutput: input.sdkQueryOutput as AddLiquidityBaseQueryOutput,
})
const signature = await Permit2Helper.signAddLiquidityApproval({
...baseInput,
client: sdkClient,
owner: input.account,
nonces: baseInput.amountsIn.map(a => nonces[a.token.address]),
})
return signature
}

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

return {
chainId: this.helpers.chainId,
rpcUrl: getRpcUrl(this.helpers.chainId),
amountsIn,
kind: AddLiquidityKind.Unbalanced,
}
}

public constructBaseBuildCallInput({
slippagePercent,
sdkQueryOutput,
}: {
slippagePercent: string
sdkQueryOutput: AddLiquidityBaseQueryOutput
}): AddLiquidityBaseBuildCallInput {
const baseBuildCallParams = {
...(sdkQueryOutput as AddLiquidityBaseQueryOutput),
slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`),
wethIsEth: this.helpers.includesNativeAsset(sdkQueryOutput.amountsIn),
}
// baseBuildCallParams.amountsIn = baseBuildCallParams.amountsIn.filter(
// amountIn => amountIn.amount > 0n
// )
return baseBuildCallParams
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { AddLiquidity } from '@balancer/sdk'
import { SdkBuildAddLiquidityInput } from '../add-liquidity.types'
import { BaseUnbalancedAddLiquidityHandler } from './BaseUnbalancedAddLiquidity.handler'
import { constructBaseBuildCallInput } from './v3Helpers'

/**
* UnbalancedAddLiquidityHandlerV3 is a handler that implements the
* AddLiquidityHandler interface for unbalanced V3 adds, e.g. where the user
* specifies the token amounts in. It uses the Balancer SDK to implement it's
* methods. It also handles the case where one of the input tokens is the native
* asset instead of the wrapped native asset.
*/
export class UnbalancedAddLiquidityHandlerV3 extends BaseUnbalancedAddLiquidityHandler {
public async buildCallData({
humanAmountsIn,
slippagePercent,
queryOutput,
account,
permit2,
}: SdkBuildAddLiquidityInput): Promise<TransactionConfig> {
const addLiquidity = new AddLiquidity()

const buildCallParams = constructBaseBuildCallInput({
humanAmountsIn,
sdkQueryOutput: queryOutput.sdkQueryOutput,
slippagePercent: slippagePercent,
pool: this.helpers.pool,
})

if (!permit2) {
throw new Error('Permit2 signature is required for V3 pools')
}

const { callData, to, value } = addLiquidity.buildCallWithPermit2(buildCallParams, permit2)

return {
account,
chainId: this.helpers.chainId,
data: callData,
to,
value,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { AddLiquidityHandler } from './AddLiquidity.handler'
import { NestedAddLiquidityHandler } from './NestedAddLiquidity.handler'
import { requiresProportionalInput, supportsNestedActions } from '../../LiquidityActionHelpers'
import { ProportionalAddLiquidityHandler } from './ProportionalAddLiquidity.handler'
import { isV3Pool } from '../../../pool.helpers'
import { UnbalancedAddLiquidityHandlerV3 } from './UnbalancedAddLiquidityV3.handler'

export function selectAddLiquidityHandler(pool: Pool): AddLiquidityHandler {
// This is just an example to illustrate how edge-case handlers would receive different inputs but return a common contract
Expand All @@ -22,5 +24,6 @@ export function selectAddLiquidityHandler(pool: Pool): AddLiquidityHandler {
return new NestedAddLiquidityHandler(pool)
}

if (isV3Pool(pool)) return new UnbalancedAddLiquidityHandlerV3(pool)
return new UnbalancedAddLiquidityHandler(pool)
}
37 changes: 37 additions & 0 deletions lib/modules/pool/actions/add-liquidity/handlers/v3Helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//TODO: move all this file logic to a better place

import {
AddLiquidityBaseBuildCallInput,
AddLiquidityBaseQueryOutput,
Slippage,
} from '@balancer/sdk'
import { Pool } from '../../../PoolProvider'
import { LiquidityActionHelpers } from '../../LiquidityActionHelpers'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'

// For now only valid for unbalanced adds
export function constructBaseBuildCallInput({
humanAmountsIn,
slippagePercent,
sdkQueryOutput,
pool,
}: {
humanAmountsIn: HumanTokenAmountWithAddress[]
slippagePercent: string
sdkQueryOutput: AddLiquidityBaseQueryOutput
pool: Pool
}): AddLiquidityBaseBuildCallInput {
const helpers = new LiquidityActionHelpers(pool)

console.log({ includesNativeAsset: helpers.isNativeAssetIn(humanAmountsIn) })

const baseBuildCallParams = {
...(sdkQueryOutput as AddLiquidityBaseQueryOutput),
slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`),
wethIsEth: helpers.isNativeAssetIn(humanAmountsIn),
}
// baseBuildCallParams.amountsIn = baseBuildCallParams.amountsIn.filter(
// amountIn => amountIn.amount > 0n
// )
return baseBuildCallParams
}
Loading

0 comments on commit cbec43a

Please sign in to comment.