Skip to content

Commit

Permalink
feat: add permit2 signature step
Browse files Browse the repository at this point in the history
  • Loading branch information
agualis committed Sep 20, 2024
1 parent 9bb8342 commit 7fa63fe
Show file tree
Hide file tree
Showing 16 changed files with 397 additions and 41 deletions.
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 @@ -39,12 +40,15 @@ export default function AddLiquidityLayout({ params: { txHash }, children }: Pro
return (
<DefaultPageContainer>
<TransactionStateProvider>
{/* // TODO: do we really need a provider */}
<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
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 isNativeAssetIn2(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,6 +1,15 @@
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'
import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types'
import { SdkClient } from '@/lib/modules/web3/useSdkViemClient'
import { AddLiquidityQueryOutput, Permit2 } from '@balancer/sdk'
import { Address } from 'viem'
import { BuildAddLiquidityInput, QueryAddLiquidityOutput } from '../add-liquidity.types'
import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types'

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,9 @@ 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: SdkClient): Promise<Permit2>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@
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 { SdkClient } from '@/lib/modules/web3/useSdkViemClient'
import {
AddLiquidity,
AddLiquidityBaseBuildCallInput,
AddLiquidityBaseQueryOutput,
AddLiquidityKind,
AddLiquidityUnbalancedInput,
Permit2,
Permit2Helper,
PriceImpact,
PriceImpactAmount,
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'

/**
* UnbalancedAddLiquidityHandler is a handler that implements the
Expand Down Expand Up @@ -61,21 +66,17 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
}

public async buildCallData({
humanAmountsIn,
account,
slippagePercent,
queryOutput,
account,
}: 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
)
Expand All @@ -91,6 +92,22 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
}
}

public async signPermit2(
input: Permit2AddLiquidityInput,
sdkClient: SdkClient
): Promise<Permit2> {
const signature = await Permit2Helper.signAddLiquidityApproval({
...this.constructBaseBuildCallInput({
slippagePercent: input.slippagePercent,
sdkQueryOutput: input.sdkQueryOutput as AddLiquidityBaseQueryOutput,
}),
client: sdkClient,
owner: input.account,
})

return signature
}

/**
* PRIVATE METHODS
*/
Expand All @@ -106,4 +123,19 @@ export class UnbalancedAddLiquidityHandler implements AddLiquidityHandler {
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.isNativeAssetIn2(sdkQueryOutput.amountsIn),
}
return baseBuildCallParams
}
}
30 changes: 24 additions & 6 deletions lib/modules/pool/actions/add-liquidity/useAddLiquiditySteps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { useShouldSignRelayerApproval } from '@/lib/modules/relayer/signRelayerA
import { useApproveRelayerStep } from '@/lib/modules/relayer/useApproveRelayerStep'
import { useRelayerMode } from '@/lib/modules/relayer/useRelayerMode'
import { useTokenApprovalSteps } from '@/lib/modules/tokens/approvals/useTokenApprovalSteps'
import { getSpenderForAddLiquidity } from '@/lib/modules/tokens/token.helpers'
import { useSignPermit2Step } from '@/lib/modules/transactions/transaction-steps/useSignPermit2Step'
import { useSignRelayerStep } from '@/lib/modules/transactions/transaction-steps/useSignRelayerStep'
import { useUserSettings } from '@/lib/modules/user/settings/UserSettingsProvider'
import { useUserAccount } from '@/lib/modules/web3/UserAccountProvider'
import { AddLiquidityBaseQueryOutput } from '@balancer/sdk'
import { useMemo } from 'react'
import { usePool } from '../../PoolProvider'
import { requiresPermit2Approval } from '../../pool.helpers'
import { LiquidityActionHelpers } from '../LiquidityActionHelpers'
import { AddLiquidityStepParams, useAddLiquidityStep } from './useAddLiquidityStep'
import { requiresPermit2Approval } from '../../pool.helpers'
import { useSignRelayerStep } from '@/lib/modules/transactions/transaction-steps/useSignRelayerStep'
import { getSpenderForAddLiquidity } from '@/lib/modules/tokens/token.helpers'

type AddLiquidityStepsParams = AddLiquidityStepParams & {
helpers: LiquidityActionHelpers
Expand All @@ -21,6 +25,7 @@ export function useAddLiquiditySteps({
simulationQuery,
}: AddLiquidityStepsParams) {
const { pool, chainId, chain } = usePool()
const { slippage } = useUserSettings()
const relayerMode = useRelayerMode(pool)
const shouldSignRelayerApproval = useShouldSignRelayerApproval(chainId, relayerMode)

Expand All @@ -42,20 +47,33 @@ export function useAddLiquiditySteps({
isPermit2: requiresPermit2Approval(pool),
})

const signPermit2Step = useSignPermit2Step(
{
handler,
slippagePercent: slippage,
queryOutput: simulationQuery.data as AddLiquidityBaseQueryOutput,
},
chainId
)

const addLiquidityStep = useAddLiquidityStep({
handler,
humanAmountsIn,
simulationQuery,
})

const addSteps = requiresPermit2Approval(pool)
? [signPermit2Step, addLiquidityStep]
: [addLiquidityStep]

const steps = useMemo(() => {
if (relayerMode === 'approveRelayer') {
return [approveRelayerStep, ...tokenApprovalSteps, addLiquidityStep]
return [approveRelayerStep, ...tokenApprovalSteps, ...addSteps]
} else if (shouldSignRelayerApproval) {
return [signRelayerStep, ...tokenApprovalSteps, addLiquidityStep]
return [signRelayerStep, ...tokenApprovalSteps, ...addSteps]
}

return [...tokenApprovalSteps, addLiquidityStep]
return [...tokenApprovalSteps, ...addSteps]
}, [
relayerMode,
shouldSignRelayerApproval,
Expand Down
9 changes: 5 additions & 4 deletions lib/modules/relayer/signRelayerApproval.hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { RelayerMode } from './useRelayerMode'
import { SignRelayerState, useRelayerSignature } from './RelayerSignatureProvider'
import { SupportedChainId } from '@/lib/config/config.types'
import { Toast } from '@/lib/shared/components/toasts/Toast'
import { useSdkViemClient } from '../web3/useSdkViemClient'

export function useShouldSignRelayerApproval(chainId: SupportedChainId, relayerMode: RelayerMode) {
const { hasApprovedRelayer } = useHasApprovedRelayer(chainId)
Expand All @@ -23,22 +24,22 @@ export function useSignRelayerApproval(chainId: SupportedChainId) {

const [error, setError] = useState<string | undefined>()

const { data: walletClient } = useWalletClient()
const sdkClient = useSdkViemClient()

useEffect(() => {
if (walletClient === undefined) {
if (sdkClient === undefined) {
setSignRelayerState(SignRelayerState.Preparing)
} else {
setSignRelayerState(SignRelayerState.Ready)
}
}, [setSignRelayerState, walletClient])
}, [setSignRelayerState, sdkClient])

async function signRelayer() {
setSignRelayerState(SignRelayerState.Confirming)
setError(undefined)

try {
const signature = await signRelayerApproval(userAddress, chainId, walletClient)
const signature = await signRelayerApproval(userAddress, chainId, sdkClient)

if (signature) {
setSignRelayerState(SignRelayerState.Completed)
Expand Down
14 changes: 4 additions & 10 deletions lib/modules/relayer/signRelayerApproval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,20 @@ import { getNetworkConfig } from '@/lib/config/app.config'
import { SupportedChainId } from '@/lib/config/config.types'
import { ensureError } from '@/lib/shared/utils/errors'
import { Relayer } from '@balancer/sdk'
import { Address, publicActions, walletActions } from 'viem'
import { GetWalletClientReturnType } from 'wagmi/actions'

// TODO: replace this type with the one exposed by the SDK (WIP)
type SignRelayerApprovalParams = Parameters<typeof Relayer.signRelayerApproval>
type SdkClient = SignRelayerApprovalParams[2]
import { Address } from 'viem'
import { SdkClient } from '../web3/useSdkViemClient'

export async function signRelayerApproval(
userAddress: Address,
chainId: SupportedChainId,
client?: GetWalletClientReturnType
client?: SdkClient
): Promise<Address | undefined> {
if (!client) return undefined

const publicClient = client.extend(publicActions).extend(walletActions) as SdkClient

const relayerV6Address = getNetworkConfig(chainId).contracts.balancer.relayerV6

try {
const signature = await Relayer.signRelayerApproval(relayerV6Address, userAddress, publicClient)
const signature = await Relayer.signRelayerApproval(relayerV6Address, userAddress, client)
return signature
} catch (e: unknown) {
const error = ensureError(e)
Expand Down
22 changes: 22 additions & 0 deletions lib/modules/tokens/approvals/approval-labels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,25 @@ test('Token approval labels for unapprove', () => {
}
`)
})

test('Token approval labels for permit2', () => {
const args: TokenApprovalLabelArgs = {
actionType: 'AddLiquidity',
symbol: 'WETH',
requiredRawAmount: 100000000n,
isPermit2: true,
}
const result = buildTokenApprovalLabels(args)
expect(result).toMatchInlineSnapshot(`
{
"confirmed": "WETH approved!",
"confirming": "Approving WETH...",
"description": "Approval of WETH for adding liquidity.",
"error": "Error approving WETH",
"init": "Approve Permit2: WETH",
"title": "WETH: Approve Permit2",
"tooltip": "You must approve WETH to add liquidity for this token on Balancer.
Approvals are required once per token, per wallet.",
}
`)
})
12 changes: 9 additions & 3 deletions lib/modules/tokens/approvals/approval-labels.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { is } from 'date-fns/locale'
import { BuildTransactionLabels } from '../../web3/contracts/transactionLabels'

export type ApprovalAction =
Expand All @@ -12,15 +13,17 @@ export type TokenApprovalLabelArgs = {
actionType: ApprovalAction
symbol: string
requiredRawAmount: bigint
isPermit2?: boolean
}

export const buildTokenApprovalLabels: BuildTransactionLabels = ({
actionType,
symbol,
isPermit2 = false,
}: TokenApprovalLabelArgs) => {
return {
init: initApprovalLabelFor(actionType, symbol),
title: `Approve ${symbol}`,
init: initApprovalLabelFor(actionType, symbol, isPermit2),
title: isPermit2 ? `${symbol}: Approve Permit2` : `Approve ${symbol}`,
description: descriptionFor(actionType, symbol),
confirming: actionType === 'Unapprove' ? `Unapproving ${symbol}...` : `Approving ${symbol}...`,
confirmed: `${symbol} ${actionType === 'Unapprove' ? 'unapproved' : 'approved!'}`,
Expand All @@ -29,7 +32,10 @@ export const buildTokenApprovalLabels: BuildTransactionLabels = ({
}
}

function initApprovalLabelFor(actionType: ApprovalAction, symbol: string) {
function initApprovalLabelFor(actionType: ApprovalAction, symbol: string, isPermit2: boolean) {
if (isPermit2) {
return `Approve Permit2: ${symbol}`
}
switch (actionType) {
case 'Locking':
return `Approve LP token to lock`
Expand Down
Loading

0 comments on commit 7fa63fe

Please sign in to comment.