Skip to content

Commit 82bc18e

Browse files
committed
fix: pool instace as param & add test
1 parent b595227 commit 82bc18e

File tree

8 files changed

+200
-57
lines changed

8 files changed

+200
-57
lines changed

src/core/uniDevKitV4.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,6 @@ export class UniDevKitV4 {
211211
}
212212

213213
async buildSwapCallData(params: BuildSwapCallDataParams): Promise<Hex> {
214-
return buildSwapCallData(params, this.instance);
214+
return buildSwapCallData(params);
215215
}
216216
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { buildSwapCallData } from "@/utils/buildSwapCallData";
2+
import { Token } from "@uniswap/sdk-core";
3+
import { Pool } from "@uniswap/v4-sdk";
4+
import { type Address, zeroAddress } from "viem";
5+
import { describe, expect, it } from "vitest";
6+
7+
describe("buildSwapCallData", () => {
8+
// USDC and WETH on Mainnet
9+
const mockTokens: [Address, Address] = [
10+
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
11+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
12+
];
13+
14+
const mockTokenInstances = [
15+
new Token(1, mockTokens[0], 6, "USDC", "USD Coin"),
16+
new Token(1, mockTokens[1], 18, "WETH", "Wrapped Ether"),
17+
];
18+
19+
const mockPool = new Pool(
20+
mockTokenInstances[0],
21+
mockTokenInstances[1],
22+
3000,
23+
60,
24+
zeroAddress,
25+
"79228162514264337593543950336",
26+
"1000000",
27+
0,
28+
);
29+
30+
it("should build swap calldata for token0 to token1", async () => {
31+
const params = {
32+
tokenIn: mockTokens[0],
33+
amountIn: BigInt(1000000), // 1 USDC
34+
amountOutMin: BigInt(0),
35+
pool: mockPool,
36+
};
37+
38+
const calldata = await buildSwapCallData(params);
39+
expect(calldata).toBeDefined();
40+
expect(calldata).toMatch(/^0x/); // Should be a hex string
41+
});
42+
43+
it("should build swap calldata for token1 to token0", async () => {
44+
const params = {
45+
tokenIn: mockTokens[1],
46+
amountIn: BigInt(1000000000000000000), // 1 WETH
47+
amountOutMin: BigInt(0),
48+
pool: mockPool,
49+
};
50+
51+
const calldata = await buildSwapCallData(params);
52+
expect(calldata).toBeDefined();
53+
expect(calldata).toMatch(/^0x/);
54+
});
55+
56+
it("should include minimum output amount in calldata", async () => {
57+
const params = {
58+
tokenIn: mockTokens[0],
59+
amountIn: BigInt(1000000),
60+
amountOutMin: BigInt(500000), // 0.5 WETH minimum
61+
pool: mockPool,
62+
};
63+
64+
const calldata = await buildSwapCallData(params);
65+
expect(calldata).toBeDefined();
66+
expect(calldata).toMatch(/^0x/);
67+
});
68+
69+
it("should handle zero minimum output amount", async () => {
70+
const params = {
71+
tokenIn: mockTokens[0],
72+
amountIn: BigInt(1000000),
73+
amountOutMin: BigInt(0),
74+
pool: mockPool,
75+
};
76+
77+
const calldata = await buildSwapCallData(params);
78+
expect(calldata).toBeDefined();
79+
expect(calldata).toMatch(/^0x/);
80+
});
81+
82+
it("should include deadline in calldata", async () => {
83+
const params = {
84+
tokenIn: mockTokens[0],
85+
amountIn: BigInt(1000000),
86+
amountOutMin: BigInt(0),
87+
pool: mockPool,
88+
};
89+
90+
const calldata = await buildSwapCallData(params);
91+
expect(calldata).toBeDefined();
92+
expect(calldata).toMatch(/^0x/);
93+
});
94+
});

src/test/utils/getPoolKeyFromPoolId.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe("getPoolKeyFromPoolId", () => {
3737
});
3838
expect(mockDeps.client.readContract).toHaveBeenCalledWith({
3939
address: mockDeps.contracts.positionManager,
40-
abi: undefined,
40+
abi: expect.any(Object),
4141
functionName: "poolKeys",
4242
args: ["0x123"],
4343
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { Address } from "viem";
2+
3+
/**
4+
* Token permissions structure for Permit2 SignatureTransfer
5+
* @public
6+
*/
7+
export interface TokenPermissions {
8+
/** The token contract address */
9+
token: Address;
10+
/** The amount to be transferred */
11+
amount: bigint;
12+
}
13+
14+
/**
15+
* Permit data structure for SignatureTransfer operations
16+
* @public
17+
*/
18+
export interface PermitTransferFrom {
19+
/** Token and amount permissions */
20+
permitted: TokenPermissions;
21+
/** Unique nonce for replay protection */
22+
nonce: bigint;
23+
/** Unix timestamp when the permit expires */
24+
deadline: bigint;
25+
}
26+
27+
/**
28+
* EIP-712 typed data structure for PermitTransferFrom signatures
29+
* @public
30+
*/
31+
export interface PermitTransferFromTypedData {
32+
/** EIP-712 domain separator */
33+
domain: {
34+
/** Domain name */
35+
name: string;
36+
/** Domain version */
37+
version: string;
38+
/** Chain ID */
39+
chainId: number;
40+
/** Verifying contract address */
41+
verifyingContract: Address;
42+
};
43+
/** EIP-712 type definitions */
44+
types: {
45+
/** PermitTransferFrom type definition */
46+
PermitTransferFrom: readonly [
47+
{ readonly name: "permitted"; readonly type: "TokenPermissions" },
48+
{ readonly name: "spender"; readonly type: "address" },
49+
{ readonly name: "nonce"; readonly type: "uint256" },
50+
{ readonly name: "deadline"; readonly type: "uint256" },
51+
];
52+
/** TokenPermissions type definition */
53+
TokenPermissions: readonly [
54+
{ readonly name: "token"; readonly type: "address" },
55+
{ readonly name: "amount"; readonly type: "uint256" },
56+
];
57+
};
58+
/** Primary type for signing */
59+
primaryType: "PermitTransferFrom";
60+
/** Message data to be signed */
61+
message: PermitTransferFrom & {
62+
/** Address authorized to execute the transfer */
63+
spender: Address;
64+
};
65+
}
66+
67+
/**
68+
* Permit parameters for building typed data
69+
* @public
70+
*/
71+
export interface PermitParams {
72+
/** Token contract address */
73+
token: Address;
74+
/** Amount to be permitted for transfer */
75+
amount: bigint;
76+
/** Token owner address */
77+
owner: Address;
78+
/** Address authorized to execute the transfer */
79+
spender: Address;
80+
/** Optional unique nonce for replay protection (auto-fetched if not provided) */
81+
nonce?: bigint;
82+
/** Optional deadline in seconds from now (defaults to 30 minutes) */
83+
deadlineSeconds?: number;
84+
}

src/types/utils/buildSwapCallData.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { FeeTier } from "@/types";
1+
import type { Pool } from "@uniswap/v4-sdk";
22
import type { Address } from "viem";
33

44
/**
@@ -18,16 +18,10 @@ export const COMMANDS = {
1818
export type BuildSwapCallDataParams = {
1919
/** Input token address */
2020
tokenIn: Address;
21-
/** Output token address */
22-
tokenOut: Address;
2321
/** Amount of input tokens to swap (in token's smallest unit) */
2422
amountIn: bigint;
2523
/** Minimum amount of output tokens to receive (in token's smallest unit) */
2624
amountOutMin?: bigint;
27-
/** Pool fee tier (e.g., 3000 for 0.3%) */
28-
fee?: FeeTier;
29-
/** Pool tick spacing */
30-
tickSpacing?: number;
31-
/** Hook contract address (use zero address if no hooks) */
32-
hooks?: Address;
25+
/** Pool */
26+
pool: Pool;
3327
};

src/utils/buildSwapCallData.ts

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import type { BuildSwapCallDataParams } from "@/types";
2-
import {
3-
COMMANDS,
4-
FeeTier,
5-
TICK_SPACING_BY_FEE,
6-
type UniDevKitV4Instance,
7-
} from "@/types";
8-
import { DEFAULT_HOOKS, getPool } from "@/utils/getPool";
9-
import { getTokens } from "@/utils/getTokens";
2+
import { COMMANDS } from "@/types";
103
import { ethers } from "ethers";
114
import type { Hex } from "viem";
125

@@ -27,51 +20,29 @@ import type { Hex } from "viem";
2720
* ```typescript
2821
* const swapParams = {
2922
* tokenIn: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
30-
* tokenOut: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
3123
* amountIn: parseUnits("100", 6), // 100 USDC
3224
* amountOutMin: parseUnits("0.05", 18), // Min 0.05 WETH
25+
* pool: pool,
3326
* };
3427
*
35-
* const calldata = await buildSwapCallData(swapParams, instance);
28+
* const calldata = await buildSwapCallData(swapParams);
3629
*
3730
* // Send transaction
3831
* const tx = await sendTransaction({
39-
* to: instance.contracts.universalRouter,
32+
* to: universalRouterAddress,
4033
* data: calldata,
4134
* value: 0,
4235
* });
4336
* ```
4437
*/
4538
export async function buildSwapCallData(
4639
params: BuildSwapCallDataParams,
47-
instance: UniDevKitV4Instance,
4840
): Promise<Hex> {
4941
// Extract and set default parameters
50-
const {
51-
tokenIn,
52-
tokenOut,
53-
amountIn,
54-
amountOutMin = 0n,
55-
fee = FeeTier.MEDIUM,
56-
tickSpacing = TICK_SPACING_BY_FEE[fee],
57-
hooks = DEFAULT_HOOKS,
58-
} = params;
42+
const { tokenIn, amountIn, amountOutMin = 0n, pool } = params;
5943

60-
// Get pool information
61-
const pool = await getPool(
62-
{ tokens: [tokenIn, tokenOut], fee, tickSpacing, hooks },
63-
instance,
64-
);
65-
if (!pool) {
66-
throw new Error("No swapable pool found");
67-
}
68-
69-
// Get token instances for address comparison
70-
const tokenInstances = await getTokens(
71-
{ addresses: [tokenIn, tokenOut] },
72-
instance,
73-
);
74-
const zeroForOne = tokenInstances[0].address === tokenIn;
44+
const zeroForOne =
45+
tokenIn.toLowerCase() === pool.poolKey.currency0.toLowerCase();
7546

7647
// Encode Universal Router commands
7748
const commands = ethers.utils.solidityPack(
@@ -94,8 +65,8 @@ export async function buildSwapCallData(
9465
{
9566
poolKey: pool.poolKey,
9667
zeroForOne,
97-
amountIn: ethers.BigNumber.from(amountIn),
98-
amountOutMinimum: ethers.BigNumber.from(amountOutMin),
68+
amountIn: ethers.BigNumber.from(amountIn.toString()),
69+
amountOutMinimum: ethers.BigNumber.from(amountOutMin.toString()),
9970
hookData: "0x",
10071
},
10172
],
@@ -106,11 +77,13 @@ export async function buildSwapCallData(
10677
exactInputSingleParams,
10778
ethers.utils.defaultAbiCoder.encode(
10879
["address", "uint128"],
109-
[pool.poolKey.currency0, amountIn],
80+
zeroForOne
81+
? [pool.poolKey.currency0, amountIn]
82+
: [pool.poolKey.currency1, amountIn],
11083
),
11184
ethers.utils.defaultAbiCoder.encode(
11285
["address", "uint128"],
113-
[pool.poolKey.currency1, 0],
86+
zeroForOne ? [pool.poolKey.currency1, 0] : [pool.poolKey.currency0, 0],
11487
),
11588
];
11689

src/utils/getPool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getTokens } from "@/utils/getTokens";
88
import { Pool } from "@uniswap/v4-sdk";
99
import { slice, zeroAddress } from "viem";
1010

11-
const DEFAULT_HOOKS = zeroAddress;
11+
export const DEFAULT_HOOKS = zeroAddress;
1212

1313
/**
1414
* Retrieves a Uniswap V4 pool instance for a given token pair, fee tier, tick spacing, and hooks configuration.

src/utils/getPoolKeyFromPoolId.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { V4PositionManagerAbi } from "@/constants/abis/V4PositionMananger";
1+
import V4PositionManagerAbi from "@/constants/abis/V4PositionMananger";
22
import type { UniDevKitV4Instance } from "@/types/core";
3-
import type {
4-
GetPoolKeyFromPoolIdParams,
5-
PoolKey,
6-
} from "@/types/utils/getPoolKeyFromPoolId";
3+
import type { GetPoolKeyFromPoolIdParams } from "@/types/utils/getPoolKeyFromPoolId";
4+
import type { PoolKey } from "@uniswap/v4-sdk";
75

86
/**
97
* Retrieves the pool key information for a given pool ID.

0 commit comments

Comments
 (0)