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

[WIP] V3 nested pool support #442

Merged
merged 11 commits into from
Oct 25, 2024
Merged
5 changes: 5 additions & 0 deletions .changeset/chilly-donuts-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@balancer/sdk": minor
---

Add add/remove support for V3 nested pools.
4 changes: 2 additions & 2 deletions examples/addLiquidity/addLiquidityNested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {
} from 'viem';
import {
Address,
AddLiquidityNested,
AddLiquidityNestedInput,
BALANCER_RELAYER,
BalancerApi,
API_ENDPOINT,
Expand All @@ -29,6 +27,8 @@ import {
import { ANVIL_NETWORKS, startFork } from '../../test/anvil/anvil-global-setup';
import { makeForkTx } from 'examples/lib/makeForkTx';
import { getSlot } from 'examples/lib/getSlot';
import { AddLiquidityNestedInput } from '@/entities/addLiquidityNested/addLiquidityNestedV2/types';
import { AddLiquidityNested } from '@/entities/addLiquidityNested';

async function runAgainstFork() {
// User defined inputs
Expand Down
4 changes: 2 additions & 2 deletions examples/removeLiquidity/removeLiquidityNested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
replaceWrapped,
Slippage,
RemoveLiquidityNested,
RemoveLiquidityNestedSingleTokenInput,
RemoveLiquidityNestedSingleTokenInputV2,
} from '../../src';
import { ANVIL_NETWORKS, startFork } from '../../test/anvil/anvil-global-setup';
import { makeForkTx } from 'examples/lib/makeForkTx';
Expand Down Expand Up @@ -103,7 +103,7 @@ const removeLiquidityNested = async ({
// setup remove liquidity helper
const removeLiquidityNested = new RemoveLiquidityNested();

const removeLiquidityInput: RemoveLiquidityNestedSingleTokenInput = {
const removeLiquidityInput: RemoveLiquidityNestedSingleTokenInputV2 = {
bptAmountIn,
chainId,
rpcUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import {
decodeFunctionResult,
http,
} from 'viem';
import { Hex } from '../../types';
import { BALANCER_RELAYER, CHAINS, ChainId, EMPTY_SENDER } from '../../utils';
import { Hex } from '../../../types';
import {
BALANCER_RELAYER,
CHAINS,
ChainId,
EMPTY_SENDER,
} from '../../../utils';
import {
balancerRelayerAbi,
permit2Abi,
vaultExtensionAbi_V3,
vaultV3Abi,
} from '../../abi';
} from '../../../abi';

export const doAddLiquidityNestedQuery = async (
chainId: ChainId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Hex, PoolType } from '../../types';
import { WeightedEncoder } from '../encoders';
import { ComposableStableEncoder } from '../encoders/composableStable';
import { ComposableStableEncoder } from '../../encoders/composableStable';
import { batchRelayerLibraryAbi } from '../../../abi';
import { encodeFunctionData, Hex } from 'viem';
import { TokenAmount } from '../../tokenAmount';
import { getValue } from '../../utils/getValue';
import { replaceWrapped } from '@/entities/utils';
import { AddLiquidityNestedCallAttributes } from './types';
import { replaceWrapped } from '../utils/replaceWrapped';
import { batchRelayerLibraryAbi } from '../../abi';
import { encodeFunctionData } from 'viem';
import { TokenAmount } from '../tokenAmount';
import { getValue } from '../utils/getValue';
import { WeightedEncoder } from '@/entities/encoders';
import { PoolType } from '@/types';

export const encodeCalls = (
callsAttributes: AddLiquidityNestedCallAttributes[],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { Token } from '../token';
import { Address, PoolType } from '@/types';
import { Token } from '@/entities/token';
import {
BALANCER_RELAYER,
ChainId,
ZERO_ADDRESS,
BALANCER_RELAYER,
getPoolAddress,
} from '../../utils';
ChainId,
} from '@/utils';
import {
AddLiquidityNestedInput,
AddLiquidityNestedCallAttributes,
AddLiquidityNestedInputV2,
} from './types';
import { NestedPool, PoolKind } from '../types';
import { Address, PoolType } from '../../types';
import { Relayer } from '../relayer';
import { NestedPool, PoolKind } from '@/entities/types';
import { Relayer } from '@/entities/relayer';

export const getQueryCallsAttributes = (
{ amountsIn, chainId, fromInternalBalance }: AddLiquidityNestedInput,
{ amountsIn, chainId, fromInternalBalance }: AddLiquidityNestedInputV2,
pools: NestedPool[],
): AddLiquidityNestedCallAttributes[] => {
/**
Expand Down
123 changes: 123 additions & 0 deletions src/entities/addLiquidityNested/addLiquidityNestedV2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { encodeFunctionData } from 'viem';
import { Token } from '../../token';
import { BALANCER_RELAYER, ZERO_ADDRESS } from '../../../utils';
import { Relayer } from '../../relayer';
import { encodeCalls } from './encodeCalls';
import { TokenAmount } from '../../tokenAmount';
import { balancerRelayerAbi } from '../../../abi';
import { doAddLiquidityNestedQuery } from './doAddLiquidityNestedQuery';
import { getQueryCallsAttributes } from './getQueryCallsAttributes';
import { validateBuildCallInput, validateQueryInput } from './validateInputs';
import { NestedPoolState } from '../../types';
import {
AddLiquidityNestedBuildCallOutput,
AddLiquidityNestedInput,
} from '../types';
import {
AddLiquidityNestedCallInputV2,
AddLiquidityNestedQueryOutputV2,
} from './types';

export class AddLiquidityNestedV2 {
async query(
input: AddLiquidityNestedInput,
nestedPoolState: NestedPoolState,
): Promise<AddLiquidityNestedQueryOutputV2> {
const amountsIn = validateQueryInput(input, nestedPoolState);

const callsAttributes = getQueryCallsAttributes(
input,
nestedPoolState.pools,
);

const { encodedCalls } = encodeCalls(callsAttributes);

// append peek call to get bptOut
const peekCall = Relayer.encodePeekChainedReferenceValue(
callsAttributes[callsAttributes.length - 1].outputReference,
);
encodedCalls.push(peekCall);

const encodedMulticall = encodeFunctionData({
abi: balancerRelayerAbi,
functionName: 'vaultActionsQueryMulticall',
args: [encodedCalls],
});

const peekedValue = await doAddLiquidityNestedQuery(
input.chainId,
input.rpcUrl,
encodedMulticall,
);

const tokenOut = new Token(
input.chainId,
callsAttributes[callsAttributes.length - 1].poolAddress,
18,
);
const bptOut = TokenAmount.fromRawAmount(tokenOut, peekedValue);

return { callsAttributes, amountsIn, bptOut, protocolVersion: 2 };
}

buildCall(
input: AddLiquidityNestedCallInputV2,
): AddLiquidityNestedBuildCallOutput {
validateBuildCallInput(input);
// apply slippage to bptOut
const minBptOut = input.slippage.applyTo(input.bptOut.amount, -1);

// update last call with minBptOut limit in place
input.callsAttributes[input.callsAttributes.length - 1] = {
...input.callsAttributes[input.callsAttributes.length - 1],
minBptOut,
};

// update wethIsEth flag + sender and recipient placeholders
input.callsAttributes = input.callsAttributes.map((call) => {
return {
...call,
sender:
call.sender === ZERO_ADDRESS
? input.accountAddress
: call.sender,
recipient:
call.recipient === ZERO_ADDRESS
? input.accountAddress
: call.recipient,
wethIsEth: input.wethIsEth,
};
});

const { encodedCalls, values } = encodeCalls(input.callsAttributes);

// prepend relayer approval if provided
if (input.relayerApprovalSignature !== undefined) {
encodedCalls.unshift(
Relayer.encodeSetRelayerApproval(
BALANCER_RELAYER[input.callsAttributes[0].chainId],
true,
input.relayerApprovalSignature,
),
);
}

const callData = encodeFunctionData({
abi: balancerRelayerAbi,
functionName: 'multicall',
args: [encodedCalls],
});

// aggregate values from all calls
const accumulatedValue = values.reduce((acc, value) => {
return acc + value;
}, 0n);

return {
callData,
to: BALANCER_RELAYER[input.callsAttributes[0].chainId],
value: accumulatedValue,
minBptOut,
};
}
}
47 changes: 47 additions & 0 deletions src/entities/addLiquidityNested/addLiquidityNestedV2/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Address, Hex } from 'viem';
import { InputAmount, PoolType } from '../../../types';
import { ChainId } from '../../../utils';
import { Token } from '../../token';
import { TokenAmount } from '../../tokenAmount';
import { PoolKind } from '../../types';
import { Slippage } from '@/entities/slippage';

export type AddLiquidityNestedInputV2 = {
amountsIn: InputAmount[];
chainId: ChainId;
rpcUrl: string;
fromInternalBalance?: boolean;
};

export type AddLiquidityNestedCallAttributes = {
chainId: ChainId;
wethIsEth?: boolean;
sortedTokens: Token[];
poolId: Hex;
poolAddress: Address;
poolType: PoolType;
kind: PoolKind;
sender: Address;
recipient: Address;
maxAmountsIn: {
amount: bigint;
isRef: boolean;
}[];
minBptOut: bigint;
fromInternalBalance: boolean;
outputReference: bigint;
};

export type AddLiquidityNestedQueryOutputV2 = {
callsAttributes: AddLiquidityNestedCallAttributes[];
amountsIn: TokenAmount[];
bptOut: TokenAmount;
protocolVersion: 2;
};

export type AddLiquidityNestedCallInputV2 = AddLiquidityNestedQueryOutputV2 & {
slippage: Slippage;
accountAddress: Address;
relayerApprovalSignature?: Hex;
wethIsEth?: boolean;
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { NATIVE_ASSETS } from '../../utils';
import { Token } from '../token';
import { TokenAmount } from '../tokenAmount';
import { NestedPoolState } from '../types';
import { AddLiquidityNestedCallInput, AddLiquidityNestedInput } from './types';
import { NATIVE_ASSETS } from '../../../utils';
import { Token } from '../../token';
import { TokenAmount } from '../../tokenAmount';
import { NestedPoolState } from '../../types';
import { AddLiquidityNestedInput } from '../types';
import { AddLiquidityNestedCallInputV2 } from './types';

export const validateQueryInput = (
input: AddLiquidityNestedInput,
Expand All @@ -26,7 +27,7 @@ export const validateQueryInput = (
};

export const validateBuildCallInput = (
input: AddLiquidityNestedCallInput,
input: AddLiquidityNestedCallInputV2,
): void => {
const chainId = input.callsAttributes[0].chainId;
if (input.wethIsEth) {
Expand Down
Loading
Loading