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

Add Permit and Permit2 signature docs #498

Merged
merged 12 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
124 changes: 121 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Builds the addLiquidity transaction using user defined slippage.

```typescript
buildCall(input: AddLiquidityBuildCallInput): AddLiquidityBuildCallOutput
````
```

**Parameters**

Expand All @@ -117,6 +117,32 @@ AddLiquidityBuildCallOutput
[AddLiquidityBuildCallOutput](./src/entities/addLiquidity/types.ts#L75) - Encoded call data for addLiquidity that user can submit.
___

### buildCallWithPermit2

Builds the addLiquidity transaction and approves amounts through a Permit2 signature.

```typescript
buildCallWithPermit2(input: AddLiquidityBuildCallInput, permit2: Permit2): AddLiquidityBuildCallOutput
```

**Parameters**

| Name | Type | Description |
| ------------- | ------------- | ------------ |
| input | [AddLiquidityBuildCallInput](./src/entities/addLiquidity/types.ts#L63) | Parameters required to build the call including user defined slippage |
| permit2 | [Permit2](./src/entities/permit2Helper/index.ts#L35) | Permit2 signature |
*Note: refer to [Permit2Helper](#calculateproportionalamounts) for a Permit2 helper function*

**Returns**

```typescript
AddLiquidityBuildCallOutput
```

[AddLiquidityBuildCallOutput](./src/entities/addLiquidity/types.ts#L75) - Encoded call data for addLiquidity that user can submit.
___


## RemoveLiquidity

This class provides functionality to:
Expand Down Expand Up @@ -661,9 +687,11 @@ Helper functions.

Given pool balances (including BPT) and a reference token amount, it calculates all other amounts proportional to the reference amount.

### Example
**Example**

See the [price impact example](/examples/priceImpact/addLiquidity.ts).
See [calculateProportionalAmounts example](/examples/utils/calculateProportionalAmounts.ts).

**Function**

```typescript
calculateProportionalAmounts(
Expand Down Expand Up @@ -700,4 +728,94 @@ calculateProportionalAmounts(
```

Amounts proportional to the reference amount.
___

### Permit2 Helper

Balancer v3 handles token approval through Pemit2 and this helper facilitates Permit2 signature generation.

Each operation (e.g. addLiquidity, swap, ...) has its own method that leverages the same input type of the operation itself in order to simplify signature generation.

**Example**

See [addLiquidityWithPermit2Signature example](/examples/addLiquidity/addLiquidityWithPermit2Signature.ts).

**Function**

Helper function to create a Permit2 signature for an addLiquidity operation:

```typescript
static async signAddLiquidityApproval(
input: AddLiquidityBaseBuildCallInput & {
client: PublicWalletClient;
owner: Address;
nonces?: number[];
expirations?: number[];
},
): Promise<Permit2>
```

**Parameters**

| Name | Type | Description |
| ------------- | ------------- | ------------ |
| input | [AddLiquidityBaseBuildCallInput](./src/entities/addLiquidity/types.ts#L62) | Add Liquidity Input |
| client | [PublicWalletClient](./src/utils/types.ts#L3) | Viem's wallet client with public actions |
| owner | Address | User address |
| nonces (optional) | number[] | Nonces for each token |
| expirations (optional) | number[] | Expirations for each token |

**Returns**

```typescript
Promise<Permit2>;
```

[Permit2](./src/entities/permit2Helper/index.ts#L35) - Permit2 object with metadata and encoded signature
MattPereira marked this conversation as resolved.
Show resolved Hide resolved

___

### Permit Helper

Balancer v3 conforms with EIP-2612 and this helper facilitates Permit signature generation.

Each operation (e.g. removeLiquidity, removeLiquidityNested and removeLiquidityBoosted) has its own method that leverages the same input type of the operation itself in order to simplify signature generation.

**Example**

See [removeLiquidityWithPermitSignature example](/examples/removeLiquidity/removeLiquidityWithPermitSignature.ts).

**Function**

Helper function to create a Permit signature for a removeLiquidity operation:

```typescript
static signRemoveLiquidityApproval = async (
input: RemoveLiquidityBaseBuildCallInput & {
client: PublicWalletClient;
owner: Hex;
nonce?: bigint;
deadline?: bigint;
},
): Promise<Permit>
```

**Parameters**

| Name | Type | Description |
| ------------- | ------------- | ------------ |
| input | [RemoveLiquidityBaseBuildCallInput](./src/entities/removeLiquidity/types.ts#L81) | Remove Liquidity Input |
| client | [PublicWalletClient](./src/utils/types.ts#L3) | Viem's wallet client with public actions |
| owner | Address | User address |
| nonces (optional) | number[] | Nonces for each token |
| expirations (optional) | number[] | Expirations for each token |

**Returns**

```typescript
Promise<Permit>;
```

[Permit](./src/entities/permitHelper/index.ts#L30) - Permit object with metadata and encoded signature

___
10 changes: 5 additions & 5 deletions examples/addLiquidity/addLiquidityUnbalanced.ts
Copy link
Member

Choose a reason for hiding this comment

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

Attempt to run this script seems to result in the following error:

[ b-sdk ] $ pnpm example ./examples/addLiquidity/addLiquidityUnbalanced.ts

> @balancer/[email protected] example /home/matthu/Desktop/Balancer/b-sdk
> npx tsx ./examples/lib/executeExample.ts "./examples/addLiquidity/addLiquidityUnbalanced.ts"

checking rpcUrl 8945 {}
🛠️  Starting anvil { port: 8945, forkBlockNumber: 7010800n }
/home/matthu/Desktop/Balancer/b-sdk/src/data/providers/balancer-api/modules/pool-state/index.ts:54
            ...data.poolGetPool,
                    ^


TypeError: Cannot read properties of null (reading 'poolGetPool')
    at Pools.fetchPoolState (/home/matthu/Desktop/Balancer/b-sdk/src/data/providers/balancer-api/modules/pool-state/index.ts:54:21)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async addLiquidityExample (/home/matthu/Desktop/Balancer/b-sdk/examples/addLiquidity/addLiquidityUnbalanced.ts:82:23)
    at async runAgainstFork (/home/matthu/Desktop/Balancer/b-sdk/examples/addLiquidity/addLiquidityUnbalanced.ts:47:18)
    at async executeExample (/home/matthu/Desktop/Balancer/b-sdk/examples/lib/executeExample.ts:6:5)

Node.js v20.17.0
 ELIFECYCLE  Command failed with exit code 1
 ```

Copy link
Member

@MattPereira MattPereira Dec 3, 2024

Choose a reason for hiding this comment

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

Tried moving the forkBlockNumber but still fails with same poolGetPool error

Copy link
Member

@MattPereira MattPereira Dec 3, 2024

Choose a reason for hiding this comment

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

Recreated the query and looks like the response from API is coming up dry

image
image

Copy link
Member

Choose a reason for hiding this comment

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

Maybe the API is on deploy11 but the pool is from deploy10?

Heres a tenderly simulation at deploy11 vault explorer that shows pool is not registered

Copy link
Member

@MattPereira MattPereira Dec 3, 2024

Choose a reason for hiding this comment

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

Looks like this PR is on deploy 10 and API is on deploy 11

Examples run fine after merging main into this PR and updating with a deploy 11 pool address

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for checking that!
You're right - sorry - I had this open before updating to deploy11.
I'll update the PR over here.

Copy link
Member Author

Choose a reason for hiding this comment

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

done ✅

Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ async function runAgainstFork() {
const { rpcUrl } = await startFork(ANVIL_NETWORKS.SEPOLIA);
const chainId = ChainId.SEPOLIA;
const userAccount = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';
// 80BAL-20WETH
// 50BAL-50WETH
const pool = {
id: '0x03Bf996C7BD45B3386cb41875761d45e27EaB284',
address: '0x03Bf996C7BD45B3386cb41875761d45e27EaB284' as Address,
id: '0x5e54dad6c2504d63473f95d8025b763fd5b893c6',
address: '0x5e54dad6c2504d63473f95d8025b763fd5b893c6' as Address,
};
const amountsIn = [
{
Expand All @@ -44,7 +44,7 @@ async function runAgainstFork() {
];
const slippage = Slippage.fromPercentage('1'); // 1%

const call = await addLiquidity({
const call = await addLiquidityExample({
rpcUrl,
chainId,
amountsIn,
Expand All @@ -70,7 +70,7 @@ async function runAgainstFork() {
);
}

const addLiquidity = async ({
export const addLiquidityExample = async ({
rpcUrl,
chainId,
poolId,
Expand Down
172 changes: 172 additions & 0 deletions examples/addLiquidity/addLiquidityWithPermit2Signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/**
* Example showing how to add liquidity to a pool while approving tokens through Permit2 signature.
* (Runs against a local Anvil fork)
*
* Run with:
* pnpm example ./examples/addLiquidity/addLiquidityWithPermit2Signature.ts
*/
import {
Address,
createTestClient,
http,
parseEther,
publicActions,
walletActions,
} from 'viem';
import {
AddLiquidityInput,
AddLiquidityKind,
AddLiquidity,
BalancerApi,
ChainId,
PriceImpact,
Slippage,
TEST_API_ENDPOINT,
CHAINS,
Permit2Helper,
} from '../../src';
import { ANVIL_NETWORKS, startFork } from '../../test/anvil/anvil-global-setup';
import { getSlot } from 'examples/lib/getSlot';
import { makeForkTx } from 'examples/lib/makeForkTx';

async function runAgainstFork() {
// User defined inputs
const { rpcUrl } = await startFork(ANVIL_NETWORKS.SEPOLIA);
const chainId = ChainId.SEPOLIA;
// 50BAL-50WETH
const pool = {
id: '0x5e54dad6c2504d63473f95d8025b763fd5b893c6',
address: '0x5e54dad6c2504d63473f95d8025b763fd5b893c6' as Address,
};
const amountsIn = [
{
rawAmount: parseEther('0.0001'),
decimals: 18,
address: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9' as Address,
},
{
rawAmount: parseEther('0.0001'),
decimals: 18,
address: '0xb19382073c7a0addbb56ac6af1808fa49e377b75' as Address,
},
];
const slippage = Slippage.fromPercentage('1'); // 1%

const client = createTestClient({
mode: 'anvil',
chain: CHAINS[chainId],
transport: http(rpcUrl),
})
.extend(publicActions)
.extend(walletActions);

const userAccount = (await client.getAddresses())[0];

// build add liquidity call with permit2 approvals
const call = await addLiquidityWithPermit2Signature({
client,
userAccount,
rpcUrl,
chainId,
amountsIn,
poolId: pool.id,
slippage,
});

// Skip Permit2 approval during fork setup so we can test approvals through signatures
const approveOnPermit2 = false;

// Make the tx against the local fork and print the result
await makeForkTx(
call,
{
rpcUrl,
chainId,
impersonateAccount: userAccount,
forkTokens: amountsIn.map((a) => ({
address: a.address,
slot: getSlot(chainId, a.address),
rawBalance: a.rawAmount,
})),
client,
},
[...amountsIn.map((a) => a.address), pool.address],
call.protocolVersion,
approveOnPermit2,
);
MattPereira marked this conversation as resolved.
Show resolved Hide resolved
}

const addLiquidityWithPermit2Signature = async ({
client,
userAccount,
rpcUrl,
chainId,
poolId,
amountsIn,
slippage,
}) => {
// API is used to fetch relevant pool data
const balancerApi = new BalancerApi(TEST_API_ENDPOINT, chainId);
const poolState = await balancerApi.pools.fetchPoolState(poolId);

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

// Calculate price impact to ensure it's acceptable
const priceImpact = await PriceImpact.addLiquidityUnbalanced(
addLiquidityInput,
poolState,
);
console.log(`\nPrice Impact: ${priceImpact.percentage.toFixed(2)}%`);

// Simulate addLiquidity to get the amount of BPT out
const addLiquidity = new AddLiquidity();
const queryOutput = await addLiquidity.query(addLiquidityInput, poolState);

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

// Add liquidity build call input with slippage applied to BPT amount from query output
const addLiquidityBuildCallInput = {
...queryOutput,
slippage,
chainId,
wethIsEth: false,
};

// Sign permit2 approvals
const permit2 = await Permit2Helper.signAddLiquidityApproval({
...addLiquidityBuildCallInput,
client,
owner: userAccount,
});

// Build call with permit2 approvals
const call = addLiquidity.buildCallWithPermit2(
addLiquidityBuildCallInput,
permit2,
);

console.log('\nWith slippage applied:');
console.log('Max tokens in:');
call.maxAmountsIn.forEach((a) =>
console.log(a.token.address, a.amount.toString()),
);
console.log(`Min BPT Out: ${call.minBptOut.amount.toString()}`);

return {
...call,
protocolVersion: queryOutput.protocolVersion,
};
};

export default runAgainstFork;
Loading
Loading