Skip to content

Commit

Permalink
Merge pull request #124 from balancer/refactor-join-interface
Browse files Browse the repository at this point in the history
Refactor Join/Exit Interface to Add/Remove Liquidity
  • Loading branch information
brunoguerios authored Nov 14, 2023
2 parents fab87ca + b366164 commit 31d244a
Show file tree
Hide file tree
Showing 49 changed files with 2,130 additions and 1,941 deletions.
3 changes: 2 additions & 1 deletion .changeset/large-socks-exist.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"@balancer/sdk": minor
---

- Add join/exit pool support (non-nested pools)
- Add add/remove liquidity pool support (non-nested pools)
- Weighted pool type
- ComposableStable pool type
- Uses balancerHelpers to query amounts in/out rather than relying on specific pool math and associated data
- Integration tests run against local viem fork
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,28 @@ Testing requires access to an archive node for onchain quote comparisons. This c
The Balancer API Provider is a provider that facilitates
data fetching from the Balancer API,
it can be used for:
- Fetch Pool State for Joins;
- Fetch Pool State for Exits.
- Fetch Pool State for AddLiquidity;
- Fetch Pool State for RemoveLiquidity.

### Usage for Joining Pool
### Usage for adding liquidity to a Pool

```ts
import { BalancerApi, PoolJoin } from "@balancer/sdk";
import { BalancerApi, AddLiquidity } from "@balancer/sdk";
...
const joinInput: ProportionalJoinInput = {
const addLiquidityInput: AddLiquidityProportionalInput = {
bptOut,
chainId,
rpcUrl,
kind: JoinKind.Proportional,
kind: AddLiquidityKind.Proportional,
};

const balancerApi = new BalancerApi('https://backend-v3-canary.beets-ftm-node.com/graphql', 1);
const poolState = await balancerApi.pools.fetchPoolState('0x5f1d6874cb1e7156e79a7563d2b61c6cbce03150000200000000000000000586');
const poolJoin = new PoolJoin();
const queryResult = await poolJoin.query(joinInput, poolState);
const addLiquidity = new AddLiquidity();
const queryOutput = await addLiquidity.query(addLiquidityInput, poolState);
const { call, to, value, maxAmountsIn, minBptOut } =
poolJoin.buildCall({
...queryResult,
addLiquidity.buildCall({
...queryOutput,
slippage,
sender: signerAddress,
recipient: signerAddress,
Expand All @@ -65,27 +65,27 @@ it can be used for:
value,
});
```
Full working join example: [examples/join/weighted.ts](./examples/join/weighted.ts)
Full working add liquidity example: [examples/addLiquidity.ts](./examples/addLiquidity.ts)

### Usage for Exiting Pool
### Usage for removing liquidity from a Pool
```ts
import { BalancerApi, PoolExit } from "@balancer/sdk";
import { BalancerApi, RemoveLiquidity } from "@balancer/sdk";
...
const exitInput: SingleAssetExitInput = {
const removeLiquidityInput: RemoveLiquiditySingleTokenInput = {
chainId,
rpcUrl,
bptIn,
tokenOut,
kind: ExitKind.SingleAsset,
kind: RemoveLiquidityKind.SingleToken,
};

const balancerApi = new BalancerApi('https://backend-v3-canary.beets-ftm-node.com/graphql', 1);
const poolState = await balancerApi.pools.fetchPoolState('0x5f1d6874cb1e7156e79a7563d2b61c6cbce03150000200000000000000000586');
const poolExit = new PoolExit();
const queryResult = await poolExit.query(exitInput, poolState);
const removeLiquidity = new RemoveLiquidity();
const queryOutput = await removeLiquidity.query(removeLiquidityInput, poolState);
const { call, to, value, maxAmountsIn, minBptOut } =
poolExit.buildCall({
...queryResult,
removeLiquidity.buildCall({
...queryOutput,
slippage,
sender: signerAddress,
recipient: signerAddress,
Expand All @@ -102,7 +102,7 @@ await client.sendTransaction({
value,
});
```
Full working exit example: [examples/exit/weighted.ts](./examples/exit/weighted.ts)
Full working remove liquidity example: [examples/removeLiquidity.ts](./examples/removeLiquidity.ts)

## Anvil client
To download and install the anvil client, run the following commands (MacOS):
Expand Down
39 changes: 21 additions & 18 deletions examples/joinPool.ts → examples/addLiquidity.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
/**
* Example showing how to join a pool.
* Example showing how to add liquidity to a pool.
* (Runs against a local Anvil fork)
*
* Run with:
* pnpm example ./examples/joinPool.ts
* pnpm example ./examples/addLiquidity.ts
*/
import { config } from 'dotenv';
config();

import {
BalancerApi,
ChainId,
JoinInput,
JoinKind,
PoolJoin,
AddLiquidityInput,
AddLiquidityKind,
AddLiquidity,
Slippage,
} from '../src';
import { parseUnits } from 'viem';
import { ANVIL_NETWORKS, startFork } from '../test/anvil/anvil-global-setup';
import { makeForkTx } from './utils/makeForkTx';

const join = async () => {
const addLiquidity = async () => {
// User defined
const chainId = ChainId.MAINNET;
const userAccount = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
Expand All @@ -45,28 +45,31 @@ const join = async () => {
address: t.address,
}));

// Construct the JoinInput, in this case an Unbalanced join
const joinInput: JoinInput = {
// Construct the AddLiquidityInput, in this case an AddLiquidityUnbalanced
const addLiquidityInput: AddLiquidityInput = {
amountsIn,
chainId,
rpcUrl,
kind: JoinKind.Unbalanced,
kind: AddLiquidityKind.Unbalanced,
};

// Simulate the join to get the amount of BPT out
const poolJoin = new PoolJoin();
const queryResult = await poolJoin.query(joinInput, poolStateInput);
// Simulate addLiquidity to get the amount of BPT out
const addLiquidity = new AddLiquidity();
const queryOutput = await addLiquidity.query(
addLiquidityInput,
poolStateInput,
);

console.log('\nJoin Query Result:');
console.log('Add Liquidity Query Output:');
console.log('Tokens In:');
queryResult.amountsIn.map((a) =>
queryOutput.amountsIn.map((a) =>
console.log(a.token.address, a.amount.toString()),
);
console.log(`BPT Out: ${queryResult.bptOut.amount.toString()}`);
console.log(`BPT Out: ${queryOutput.bptOut.amount.toString()}`);

// Apply slippage to the BPT amount received from the query and construct the call
const call = poolJoin.buildCall({
...queryResult,
const call = addLiquidity.buildCall({
...queryOutput,
slippage,
sender: userAccount,
recipient: userAccount,
Expand Down Expand Up @@ -97,4 +100,4 @@ const join = async () => {
);
};

join().then(() => {});
addLiquidity().then(() => {});
39 changes: 21 additions & 18 deletions examples/exitPool.ts → examples/removeLiquidity.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
/**
* Example showing how to exit a pool.
* Example showing how to remove liquidity from a pool.
* (Runs against a local Anvil fork)
*
* Run with:
* pnpm example ./examples/exitPool.ts
* pnpm example ./examples/removeLiquidity.ts
*/
import dotenv from 'dotenv';
dotenv.config();

import {
ChainId,
ExitKind,
PoolExit,
RemoveLiquidityKind,
RemoveLiquidity,
PoolStateInput,
Slippage,
InputAmount,
ExitInput,
RemoveLiquidityInput,
BalancerApi,
} from '../src';
import { parseEther } from 'viem';
import { ANVIL_NETWORKS, startFork } from '../test/anvil/anvil-global-setup';
import { makeForkTx } from './utils/makeForkTx';

const exit = async () => {
const removeLiquidity = async () => {
// User defined:
const chainId = ChainId.MAINNET;
const userAccount = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
Expand All @@ -42,33 +42,36 @@ const exit = async () => {
const poolStateInput: PoolStateInput =
await balancerApi.pools.fetchPoolState(poolId);

// Construct the ExitInput, in this case a SingleAsset exit
// Construct the RemoveLiquidityInput, in this case a RemoveLiquiditySingleToken
const bptIn: InputAmount = {
rawAmount: parseEther('1'),
decimals: 18,
address: poolStateInput.address,
};
const exitInput: ExitInput = {
const removeLiquidityInput: RemoveLiquidityInput = {
chainId,
rpcUrl,
bptIn,
tokenOut,
kind: ExitKind.SingleAsset,
kind: RemoveLiquidityKind.SingleToken,
};

// Simulate the exit to get the tokens out
const poolExit = new PoolExit();
const queryResult = await poolExit.query(exitInput, poolStateInput);
// Simulate removing liquidity to get the tokens out
const removeLiquidity = new RemoveLiquidity();
const queryOutput = await removeLiquidity.query(
removeLiquidityInput,
poolStateInput,
);

console.log('\nExit Query Result:');
console.log(`BPT In: ${queryResult.bptIn.amount.toString()}\nTokens Out:`);
queryResult.amountsOut.map((a) =>
console.log('Remove Liquidity Query Output:');
console.log(`BPT In: ${queryOutput.bptIn.amount.toString()}\nTokens Out:`);
queryOutput.amountsOut.map((a) =>
console.log(a.token.address, a.amount.toString()),
);

// Apply slippage to the tokens out received from the query and construct the call
const call = poolExit.buildCall({
...queryResult,
const call = removeLiquidity.buildCall({
...queryOutput,
slippage,
sender: userAccount,
recipient: userAccount,
Expand Down Expand Up @@ -100,4 +103,4 @@ const exit = async () => {
);
};

exit();
removeLiquidity();
62 changes: 62 additions & 0 deletions src/entities/addLiquidity/addLiquidity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
AddLiquidityBase,
AddLiquidityBuildOutput,
AddLiquidityConfig,
AddLiquidityInput,
AddLiquidityQueryOutput,
AddLiquidityCall,
} from './types';
import { AddLiquidityWeighted } from './weighted/addLiquidityWeighted';
import { PoolStateInput } from '../types';
import { validateInputs } from './utils/validateInputs';
import { getSortedTokens } from '../utils/getSortedTokens';
import { AddLiquidityComposableStable } from './composable-stable/addLiquidityComposableStable';

export class AddLiquidity {
private readonly addLiquidityTypes: Record<string, AddLiquidityBase> = {};

constructor(config?: AddLiquidityConfig) {
const { customAddLiquidityTypes } = config || {};
this.addLiquidityTypes = {
//GYRO2, GYRO3, GYROE pool types only support Add Liquidity Proportional (3 - ALL_TOKENS_IN_FOR_BPT_OUT)
GYRO2: new AddLiquidityWeighted(),
GYRO3: new AddLiquidityWeighted(),
GYROE: new AddLiquidityWeighted(),
WEIGHTED: new AddLiquidityWeighted(),
// PHANTOM_STABLE === ComposableStables in API
PHANTOM_STABLE: new AddLiquidityComposableStable(),
// custom add liquidity types take precedence over base types
...customAddLiquidityTypes,
};
}

public getAddLiquidity(poolType: string): AddLiquidityBase {
if (!this.addLiquidityTypes[poolType]) {
throw new Error('Unsupported pool type');
}

return this.addLiquidityTypes[poolType];
}

public async query(
input: AddLiquidityInput,
poolState: PoolStateInput,
): Promise<AddLiquidityQueryOutput> {
validateInputs(input, poolState);

const sortedTokens = getSortedTokens(poolState.tokens, input.chainId);
const mappedPoolState = {
...poolState,
tokens: sortedTokens,
};

return this.getAddLiquidity(poolState.type).query(
input,
mappedPoolState,
);
}

public buildCall(input: AddLiquidityCall): AddLiquidityBuildOutput {
return this.getAddLiquidity(input.poolType).buildCall(input);
}
}
Loading

0 comments on commit 31d244a

Please sign in to comment.