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: add liquidity unbalanced permit2 #1117

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7fa63fe
feat: add permit2 signature step
agualis Sep 20, 2024
7175c7c
fix: types
agualis Sep 23, 2024
7aa9c95
chore: rename includesNativeAsset
agualis Sep 23, 2024
a720db9
feat: avoid buildcall when permit2 is nor signed
agualis Sep 23, 2024
f2c0788
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 24, 2024
863ea60
chore: bump sdk to v0.26.1
agualis Sep 25, 2024
c8aa8a6
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 25, 2024
8c58184
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 25, 2024
d4f70d1
fix: add nonces query
agualis Sep 25, 2024
8666be6
fix: remove old handler
agualis Sep 25, 2024
0125d2d
chore: rename permit2 transfer functions
agualis Sep 25, 2024
61b1563
chore: add token symbols to permit2 transfer label
agualis Sep 26, 2024
aaafbbd
chore: missing parameter
agualis Sep 26, 2024
0cfdcf9
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 26, 2024
49fd31a
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 27, 2024
cbec43a
refactor: extract base abstract class
agualis Sep 27, 2024
bb2285b
refactor: extract signature helpers
agualis Sep 30, 2024
272f36b
Merge branch 'main' into feat/addLiquidityPermit2
agualis Sep 30, 2024
8db0520
chore: delete file
agualis Sep 30, 2024
90ec059
chore: refactor isPermit parameters
agualis Sep 30, 2024
46a2f82
chore: improve comment
agualis Oct 1, 2024
ae4198c
chore: improve naming
agualis Oct 1, 2024
5e1e154
chore: improve permit2 step
agualis Oct 1, 2024
b927f2f
Change promo banners to 'CoW is Live'
uiuxxx Oct 1, 2024
bb9ff72
fix: receipt hook integration tests after foundry update
agualis Oct 1, 2024
ca06eb0
chore: avoid foundry cache
agualis Oct 1, 2024
89b708e
chore: enable foundry cache again
agualis Oct 1, 2024
b20ee98
chore: temporarily disable foundry cache
agualis Oct 1, 2024
3cb8df6
fix: receipt hook integration tests after foundry update
agualis Oct 1, 2024
be9d4b0
Merge branch 'ui/promos' into feat/addLiquidityPermit2
agualis Oct 1, 2024
bebdb88
fix: sign permit2 loading condition
agualis Oct 1, 2024
04bba89
Merge branch 'main' into feat/addLiquidityPermit2
agualis Oct 1, 2024
3b0b1c6
chore: disable cache
agualis Oct 2, 2024
1fd092c
chore: extract token helpers
agualis Oct 2, 2024
eade12c
Merge branch 'main' into feat/addLiquidityPermit2
agualis Oct 3, 2024
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: 3 additions & 0 deletions app/(app)/debug/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ export default function Debug() {
<Link as={NextLink} href="/debug/remove-allowance">
Remove allowance
</Link>
<Link as={NextLink} href="/debug/permit2-allowance">
Permit2 allowance
</Link>
</VStack>
</FadeInOnView>
)
Expand Down
63 changes: 63 additions & 0 deletions app/(app)/debug/permit2-allowance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import { useUserAccount } from '@/lib/modules/web3/UserAccountProvider'
import { Center, Input, Text, VStack } from '@chakra-ui/react'
import { useState } from 'react'
import { Address } from 'viem'
import { sepolia } from 'viem/chains'
import { useReadContract } from 'wagmi'
import { fNum } from '@/lib/shared/utils/numbers'
import { getGqlChain, getNetworkConfig } from '@/lib/config/app.config'
import { permit2Abi } from '@balancer/sdk'

export default function Page() {
const [tokenAddress, setTokenAddress] = useState<Address>('' as Address)

const { chain, userAddress } = useUserAccount()

const chainId = chain?.id || sepolia.id

const { data } = usePermit2Allowance({ chainId, tokenAddress, owner: userAddress })

return (
<Center>
<VStack w="50%">
<Text>
Enter address of token to check permit2 allowance in the current chain:{' '}
{chain ? chain.name : 'None'}
</Text>
<Input type="text" onChange={e => setTokenAddress(e.target.value as Address)} />

{data && (
<div>
<div>Amount: {fNum('integer', data[0])}</div>
<div>Expires: {data[1]}</div>
<div>Nonce: {data[2]}</div>
</div>
)}
</VStack>
</Center>
)
}

type Params = {
chainId: number
tokenAddress: Address
owner: Address
}
function usePermit2Allowance({ chainId, tokenAddress, owner }: Params) {
const permit2Address = '0x000000000022D473030F116dDEE9F6B43aC78BA3'
const balancerRouter = getNetworkConfig(getGqlChain(chainId)).contracts.balancer.router!
const spender = balancerRouter

return useReadContract({
chainId,
address: permit2Address,
abi: permit2Abi,
functionName: 'allowance',
args: [owner, tokenAddress, spender],
query: {
enabled: !!tokenAddress && !!owner,
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { isHash } from 'viem'
import { usePoolRedirect } from '@/lib/modules/pool/pool.hooks'
import { DefaultPageContainer } from '@/lib/shared/components/containers/DefaultPageContainer'
import { AddLiquidityProvider } from '@/lib/modules/pool/actions/add-liquidity/AddLiquidityProvider'
import { Permit2SignatureProvider } from '@/lib/modules/tokens/approvals/permit2/Permit2SignatureProvider'

type Props = PropsWithChildren<{
params: { txHash?: string[] }
Expand Down Expand Up @@ -40,11 +41,13 @@ export default function AddLiquidityLayout({ params: { txHash }, children }: Pro
<DefaultPageContainer>
<TransactionStateProvider>
<RelayerSignatureProvider>
<TokenInputsValidationProvider>
<AddLiquidityProvider urlTxHash={urlTxHash}>
<PriceImpactProvider>{children}</PriceImpactProvider>
</AddLiquidityProvider>
</TokenInputsValidationProvider>
<Permit2SignatureProvider>
<TokenInputsValidationProvider>
<AddLiquidityProvider urlTxHash={urlTxHash}>
<PriceImpactProvider>{children}</PriceImpactProvider>
</AddLiquidityProvider>
</TokenInputsValidationProvider>
</Permit2SignatureProvider>
</RelayerSignatureProvider>
</TransactionStateProvider>
</DefaultPageContainer>
Expand Down
2 changes: 2 additions & 0 deletions lib/config/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export interface ContractsConfig {
vaultV2: Address
// TODO: make it required when v3 is deployed in all networks
vaultV3?: Address
// TODO: make it required when v3 is deployed in all networks
router?: Address
relayerV6: Address
minter: Address
}
Expand Down
1 change: 1 addition & 0 deletions lib/config/networks/sepolia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const networkConfig: NetworkConfig = {
balancer: {
vaultV2: '0xBA12222222228d8Ba445958a75a0704d566BF2C8',
vaultV3: '0x0EF1c156a7986F394d90eD1bEeA6483Cc435F542',
router: '0xB12FcB422aAe6720f882E22C340964a7723f2387',
relayerV6: '0x7852fB9d0895e6e8b3EedA553c03F6e2F9124dF9',
minter: '0x1783Cd84b3d01854A96B4eD5843753C2CcbD574A',
},
Expand Down
6 changes: 6 additions & 0 deletions lib/modules/pool/actions/LiquidityActionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { AddLiquidityNestedQueryOutput, AddLiquidityQueryOutput, TokenAmount } from '@balancer/sdk'
import {
AddLiquidityNestedQueryOutput,
AddLiquidityQueryOutput,
Permit2,
TokenAmount,
} from '@balancer/sdk'
import { Address } from 'viem'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'

Expand All @@ -17,6 +22,7 @@ export interface BuildAddLiquidityInput {
slippagePercent: string
queryOutput: QueryAddLiquidityOutput
relayerApprovalSignature?: Address //only used by Nested Add Liquidity in signRelayer mode
permit2?: Permit2 //only used by v3 add liquidity
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
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 { Address } from 'viem'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { NoncesByTokenAddress } from '@/lib/modules/tokens/approvals/permit2/usePermit2Nonces'

export interface Permit2AddLiquidityInput {
account: Address
slippagePercent: string
sdkQueryOutput: AddLiquidityQueryOutput
}

/**
* AddLiquidityHandler is an interface that defines the methods that must be implemented by a handler.
Expand All @@ -27,4 +36,13 @@ 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
Expand Up @@ -7,6 +7,8 @@ import { UnbalancedAddLiquidityHandler } from './UnbalancedAddLiquidity.handler'
import { selectAddLiquidityHandler } from './selectAddLiquidityHandler'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { Pool } from '../../../PoolProvider'
import { getNetworkConfig } from '@/lib/config/app.config'
import { GqlChain } from '@/lib/shared/services/api/generated/graphql'

function selectUnbalancedHandler() {
return selectAddLiquidityHandler(aWjAuraWethPoolElementMock()) as UnbalancedAddLiquidityHandler
Expand Down Expand Up @@ -110,7 +112,8 @@ describe.skip('When adding unbalanced liquidity for a V3 pool', async () => {
queryOutput,
})

const sepoliaRouter = '0xB12FcB422aAe6720f882E22C340964a7723f2387'
const sepoliaRouter = getNetworkConfig(GqlChain.Sepolia).contracts.balancer.router

expect(result.to).toBe(sepoliaRouter)
expect(result.data).toBeDefined()
})
Expand Down
Copy link
Contributor Author

@agualis agualis Oct 4, 2024

Choose a reason for hiding this comment

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

Should we rename this file/class to UnbalancedAddLiquidityV2.handler.ts to make it more explicit? Note that it also used for V1 CowAMM pools to that would not be precise.

Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,26 @@ 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,
formatBuildCallParams,
areEmptyAmounts,
formatBuildCallParams,
} from '../../LiquidityActionHelpers'
import { AddLiquidityHandler } from './AddLiquidity.handler'
import { SdkBuildAddLiquidityInput, SdkQueryAddLiquidityOutput } from '../add-liquidity.types'
import { AddLiquidityHandler, Permit2AddLiquidityInput } from './AddLiquidity.handler'
import { NoncesByTokenAddress } from '@/lib/modules/tokens/approvals/permit2/usePermit2Nonces'

/**
* UnbalancedAddLiquidityHandler is a handler that implements the
Expand Down Expand Up @@ -61,26 +67,30 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
}

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

const baseBuildCallParams = {
...queryOutput.sdkQueryOutput,
slippage: Slippage.fromPercentage(`${Number(slippagePercent)}`),
wethIsEth: this.helpers.isNativeAssetIn(humanAmountsIn),
}

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

const { callData, to, value } = addLiquidity.buildCall(buildCallParams)
if (this.helpers.isV3Pool() && !permit2) {
throw new Error('Permit2 signature is required for V3 pools')
}

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

return {
account,
Expand All @@ -91,6 +101,24 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
}
}

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
*/
Expand All @@ -106,4 +134,22 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
kind: AddLiquidityKind.Unbalanced,
}
}

public constructBaseBuildCallInput({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will probably try to extract this method to be shared by all the handlers but for now I will keep it here until I implement the rest of the v3 handlers

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(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will delete this later. It is a reminder to try something...

// amountIn => amountIn.amount > 0n
// )
return baseBuildCallParams
}
}
Loading
Loading