@pafi-dev/trading 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,7 +18,6 @@ import {
18
18
 
19
19
  // src/quoting/routes.ts
20
20
  import { COMMON_POOLS, POINT_TOKEN_POOLS } from "@pafi-dev/core";
21
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
22
21
  function combineRoutes(chainId, pointTokenAddress) {
23
22
  const commonPools = COMMON_POOLS[chainId] ?? [];
24
23
  const pointPools = POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];
@@ -26,84 +25,83 @@ function combineRoutes(chainId, pointTokenAddress) {
26
25
  }
27
26
  function buildAllPaths(pools, tokenIn, tokenOut, maxHops = 3) {
28
27
  const results = [];
29
- function dfs(currentToken, currentPath, usedPoolIndices) {
30
- if (currentPath.length > maxHops) return;
31
- if (currentPath.length > 0 && currentToken.toLowerCase() === tokenOut.toLowerCase()) {
32
- results.push([...currentPath]);
28
+ function dfs(currentToken, tokens, fees, usedPoolIndices) {
29
+ const hopsSoFar = tokens.length - 1;
30
+ if (hopsSoFar > maxHops) return;
31
+ if (hopsSoFar > 0 && currentToken.toLowerCase() === tokenOut.toLowerCase()) {
32
+ results.push({ tokens: [...tokens], fees: [...fees] });
33
33
  return;
34
34
  }
35
35
  for (let i = 0; i < pools.length; i++) {
36
36
  if (usedPoolIndices.has(i)) continue;
37
37
  const pool = pools[i];
38
- const c0 = pool.currency0.toLowerCase();
39
- const c1 = pool.currency1.toLowerCase();
38
+ const t0 = pool.token0.toLowerCase();
39
+ const t1 = pool.token1.toLowerCase();
40
40
  const curr = currentToken.toLowerCase();
41
41
  let nextToken = null;
42
- if (curr === c0) {
43
- nextToken = pool.currency1;
44
- } else if (curr === c1) {
45
- nextToken = pool.currency0;
42
+ if (curr === t0) {
43
+ nextToken = pool.token1;
44
+ } else if (curr === t1) {
45
+ nextToken = pool.token0;
46
46
  }
47
47
  if (!nextToken) continue;
48
- const hop = {
49
- intermediateCurrency: nextToken,
50
- fee: pool.fee,
51
- tickSpacing: pool.tickSpacing,
52
- hooks: pool.hooks ?? ZERO_ADDRESS,
53
- hookData: "0x"
54
- };
55
48
  usedPoolIndices.add(i);
56
- currentPath.push(hop);
57
- dfs(nextToken, currentPath, usedPoolIndices);
58
- currentPath.pop();
49
+ tokens.push(nextToken);
50
+ fees.push(pool.fee);
51
+ dfs(nextToken, tokens, fees, usedPoolIndices);
52
+ tokens.pop();
53
+ fees.pop();
59
54
  usedPoolIndices.delete(i);
60
55
  }
61
56
  }
62
- dfs(tokenIn, [], /* @__PURE__ */ new Set());
57
+ dfs(tokenIn, [tokenIn], [], /* @__PURE__ */ new Set());
63
58
  return results;
64
59
  }
65
60
 
66
61
  // src/quoting/quote.ts
67
- import { v4QuoterAbi } from "@pafi-dev/core";
68
- import { COMMON_POOLS as COMMON_POOLS2, V4_QUOTER_ADDRESSES } from "@pafi-dev/core";
69
- async function quoteExactInput(client, quoterAddress, exactCurrency, path, exactAmount) {
70
- const [amountOut, gasEstimate] = await client.readContract({
62
+ import {
63
+ COMMON_POOLS as COMMON_POOLS2,
64
+ QUOTER_V2_ADDRESSES,
65
+ encodeV3Path,
66
+ encodeV3PathReversed,
67
+ v3QuoterV2Abi
68
+ } from "@pafi-dev/core";
69
+ async function quoteExactInput(client, quoterAddress, path, exactAmount) {
70
+ const pathBytes = encodeV3Path(path);
71
+ const result = await client.readContract({
71
72
  address: quoterAddress,
72
- abi: v4QuoterAbi,
73
+ abi: v3QuoterV2Abi,
73
74
  functionName: "quoteExactInput",
74
- args: [{ exactCurrency, path, exactAmount }]
75
+ args: [pathBytes, exactAmount]
75
76
  });
77
+ const [amountOut, , , gasEstimate] = result;
76
78
  return { amountOut, gasEstimate, path };
77
79
  }
78
- async function quoteExactInputSingle(client, quoterAddress, poolKey, zeroForOne, exactAmount, hookData) {
79
- const [amountOut, gasEstimate] = await client.readContract({
80
+ async function quoteExactInputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
81
+ const result = await client.readContract({
80
82
  address: quoterAddress,
81
- abi: v4QuoterAbi,
83
+ abi: v3QuoterV2Abi,
82
84
  functionName: "quoteExactInputSingle",
83
85
  args: [
84
86
  {
85
- poolKey,
86
- zeroForOne,
87
- exactAmount,
88
- hookData
87
+ tokenIn,
88
+ tokenOut,
89
+ amountIn: exactAmount,
90
+ fee,
91
+ sqrtPriceLimitX96
89
92
  }
90
93
  ]
91
94
  });
95
+ const [amountOut, , , gasEstimate] = result;
92
96
  return { amountOut, gasEstimate };
93
97
  }
94
- async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exactAmount) {
98
+ async function quoteBestRoute(client, quoterAddress, routes, exactAmount) {
95
99
  const results = await client.multicall({
96
100
  contracts: routes.map((path) => ({
97
101
  address: quoterAddress,
98
- abi: v4QuoterAbi,
102
+ abi: v3QuoterV2Abi,
99
103
  functionName: "quoteExactInput",
100
- args: [
101
- {
102
- exactCurrency,
103
- path,
104
- exactAmount
105
- }
106
- ]
104
+ args: [encodeV3Path(path), exactAmount]
107
105
  })),
108
106
  allowFailure: true
109
107
  });
@@ -112,7 +110,15 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
112
110
  for (let i = 0; i < results.length; i++) {
113
111
  const r = results[i];
114
112
  if (r.status === "success") {
115
- const [amountOut, gasEstimate] = r.result;
113
+ const tuple = r.result;
114
+ if (!Array.isArray(tuple) || tuple.length < 4) {
115
+ if (firstFailure === void 0) {
116
+ const len = Array.isArray(tuple) ? tuple.length : "n/a";
117
+ firstFailure = `unexpected QuoterV2 return shape (length=${len})`;
118
+ }
119
+ continue;
120
+ }
121
+ const [amountOut, , , gasEstimate] = tuple;
116
122
  allRoutes.push({ amountOut, gasEstimate, path: routes[i] });
117
123
  } else if (firstFailure === void 0) {
118
124
  const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
@@ -121,7 +127,7 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
121
127
  }
122
128
  if (allRoutes.length === 0) {
123
129
  throw new Error(
124
- `No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
130
+ `No valid routes found (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
125
131
  );
126
132
  }
127
133
  const bestRoute = allRoutes.reduce(
@@ -130,9 +136,9 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
130
136
  return { bestRoute, allRoutes };
131
137
  }
132
138
  async function findBestQuote(client, chainId, tokenIn, tokenOut, exactAmount, pools = [], quoterAddress, maxHops = 3) {
133
- const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];
139
+ const quoter = quoterAddress ?? QUOTER_V2_ADDRESSES[chainId];
134
140
  if (!quoter) {
135
- throw new Error(`No V4 Quoter address configured for chain ${chainId}`);
141
+ throw new Error(`No V3 QuoterV2 address configured for chain ${chainId}`);
136
142
  }
137
143
  const commonPools = COMMON_POOLS2[chainId] ?? [];
138
144
  const allPools = [...pools, ...commonPools];
@@ -140,46 +146,44 @@ async function findBestQuote(client, chainId, tokenIn, tokenOut, exactAmount, po
140
146
  if (paths.length === 0) {
141
147
  throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);
142
148
  }
143
- return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);
149
+ return quoteBestRoute(client, quoter, paths, exactAmount);
144
150
  }
145
- async function quoteExactOutput(client, quoterAddress, exactCurrency, path, exactAmount) {
146
- const [amountIn, gasEstimate] = await client.readContract({
151
+ async function quoteExactOutput(client, quoterAddress, path, exactAmount) {
152
+ const pathBytes = encodeV3PathReversed(path);
153
+ const result = await client.readContract({
147
154
  address: quoterAddress,
148
- abi: v4QuoterAbi,
155
+ abi: v3QuoterV2Abi,
149
156
  functionName: "quoteExactOutput",
150
- args: [{ exactCurrency, path, exactAmount }]
157
+ args: [pathBytes, exactAmount]
151
158
  });
159
+ const [amountIn, , , gasEstimate] = result;
152
160
  return { amountIn, gasEstimate, path };
153
161
  }
154
- async function quoteExactOutputSingle(client, quoterAddress, poolKey, zeroForOne, exactAmount, hookData) {
155
- const [amountIn, gasEstimate] = await client.readContract({
162
+ async function quoteExactOutputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
163
+ const result = await client.readContract({
156
164
  address: quoterAddress,
157
- abi: v4QuoterAbi,
165
+ abi: v3QuoterV2Abi,
158
166
  functionName: "quoteExactOutputSingle",
159
167
  args: [
160
168
  {
161
- poolKey,
162
- zeroForOne,
163
- exactAmount,
164
- hookData
169
+ tokenIn,
170
+ tokenOut,
171
+ amount: exactAmount,
172
+ fee,
173
+ sqrtPriceLimitX96
165
174
  }
166
175
  ]
167
176
  });
177
+ const [amountIn, , , gasEstimate] = result;
168
178
  return { amountIn, gasEstimate };
169
179
  }
170
- async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, routes, exactAmount) {
180
+ async function quoteBestRouteExactOut(client, quoterAddress, routes, exactAmount) {
171
181
  const results = await client.multicall({
172
182
  contracts: routes.map((path) => ({
173
183
  address: quoterAddress,
174
- abi: v4QuoterAbi,
184
+ abi: v3QuoterV2Abi,
175
185
  functionName: "quoteExactOutput",
176
- args: [
177
- {
178
- exactCurrency,
179
- path,
180
- exactAmount
181
- }
182
- ]
186
+ args: [encodeV3PathReversed(path), exactAmount]
183
187
  })),
184
188
  allowFailure: true
185
189
  });
@@ -188,7 +192,15 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
188
192
  for (let i = 0; i < results.length; i++) {
189
193
  const r = results[i];
190
194
  if (r.status === "success") {
191
- const [amountIn, gasEstimate] = r.result;
195
+ const tuple = r.result;
196
+ if (!Array.isArray(tuple) || tuple.length < 4) {
197
+ if (firstFailure === void 0) {
198
+ const len = Array.isArray(tuple) ? tuple.length : "n/a";
199
+ firstFailure = `unexpected QuoterV2 return shape (length=${len})`;
200
+ }
201
+ continue;
202
+ }
203
+ const [amountIn, , , gasEstimate] = tuple;
192
204
  allRoutes.push({ amountIn, gasEstimate, path: routes[i] });
193
205
  } else if (firstFailure === void 0) {
194
206
  const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
@@ -197,7 +209,7 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
197
209
  }
198
210
  if (allRoutes.length === 0) {
199
211
  throw new Error(
200
- `No valid exact-output routes found to ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
212
+ `No valid exact-output routes found (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
201
213
  );
202
214
  }
203
215
  const bestRoute = allRoutes.reduce(
@@ -206,19 +218,19 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
206
218
  return { bestRoute, allRoutes };
207
219
  }
208
220
  async function findBestQuoteExactOut(client, chainId, tokenIn, tokenOut, exactAmount, pools = [], quoterAddress, maxHops = 3) {
209
- const quoter = quoterAddress ?? V4_QUOTER_ADDRESSES[chainId];
221
+ const quoter = quoterAddress ?? QUOTER_V2_ADDRESSES[chainId];
210
222
  if (!quoter) {
211
- throw new Error(`No V4 Quoter address configured for chain ${chainId}`);
223
+ throw new Error(`No V3 QuoterV2 address configured for chain ${chainId}`);
212
224
  }
213
225
  const commonPools = COMMON_POOLS2[chainId] ?? [];
214
226
  const allPools = [...pools, ...commonPools];
215
- const paths = buildAllPaths(allPools, tokenOut, tokenIn, maxHops);
227
+ const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);
216
228
  if (paths.length === 0) {
217
229
  throw new Error(
218
- `No exact-output paths found to ${tokenOut} from ${tokenIn}`
230
+ `No exact-output paths found from ${tokenIn} to ${tokenOut}`
219
231
  );
220
232
  }
221
- return quoteBestRouteExactOut(client, quoter, tokenOut, paths, exactAmount);
233
+ return quoteBestRouteExactOut(client, quoter, paths, exactAmount);
222
234
  }
223
235
 
224
236
  // src/swap/approval.ts
@@ -250,173 +262,79 @@ function buildPermit2ApprovalCalldata(token, spender, amount, expiration) {
250
262
 
251
263
  // src/swap/universalRouter.ts
252
264
  import { encodeAbiParameters, encodePacked } from "viem";
253
- var V4_SWAP = 16;
254
- var SWAP_EXACT_IN = 7;
255
- var SWAP_EXACT_OUT_SINGLE = 8;
256
- var SWAP_EXACT_OUT = 9;
257
- var SETTLE_ALL = 12;
258
- var TAKE_ALL = 15;
265
+ import { encodeV3Path as encodeV3Path2, encodeV3PathReversed as encodeV3PathReversed2 } from "@pafi-dev/core";
266
+ var V3_SWAP_EXACT_IN = 0;
267
+ var V3_SWAP_EXACT_OUT = 1;
259
268
  var UINT128_MAX = 2n ** 128n - 1n;
260
- var PATH_KEY_ABI_COMPONENTS = [
261
- { name: "intermediateCurrency", type: "address" },
262
- { name: "fee", type: "uint256" },
263
- { name: "tickSpacing", type: "int24" },
264
- { name: "hooks", type: "address" },
265
- { name: "hookData", type: "bytes" }
269
+ var V3_EXACT_IN_INPUT_ABI = [
270
+ { name: "recipient", type: "address" },
271
+ { name: "amountIn", type: "uint256" },
272
+ { name: "amountOutMinimum", type: "uint256" },
273
+ { name: "path", type: "bytes" },
274
+ { name: "payerIsUser", type: "bool" }
266
275
  ];
267
- var EXACT_INPUT_PARAMS_ABI = [
268
- { name: "currencyIn", type: "address" },
269
- {
270
- name: "path",
271
- type: "tuple[]",
272
- components: PATH_KEY_ABI_COMPONENTS
273
- },
274
- { name: "amountIn", type: "uint128" },
275
- { name: "amountOutMinimum", type: "uint128" }
276
+ var V3_EXACT_OUT_INPUT_ABI = [
277
+ { name: "recipient", type: "address" },
278
+ { name: "amountOut", type: "uint256" },
279
+ { name: "amountInMaximum", type: "uint256" },
280
+ { name: "path", type: "bytes" },
281
+ { name: "payerIsUser", type: "bool" }
276
282
  ];
277
- var EXACT_OUTPUT_PARAMS_ABI = [
278
- { name: "currencyOut", type: "address" },
279
- {
280
- name: "path",
281
- type: "tuple[]",
282
- components: PATH_KEY_ABI_COMPONENTS
283
- },
284
- { name: "amountOut", type: "uint128" },
285
- { name: "amountInMaximum", type: "uint128" }
286
- ];
287
- function buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency) {
288
- const actions = encodePacked(
289
- ["uint8", "uint8", "uint8"],
290
- [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL]
291
- );
292
- const swapParam = encodeAbiParameters(
293
- [{ name: "swap", type: "tuple", components: EXACT_INPUT_PARAMS_ABI }],
294
- [
295
- {
296
- currencyIn,
297
- path: path.map((p) => ({
298
- intermediateCurrency: p.intermediateCurrency,
299
- fee: BigInt(p.fee),
300
- tickSpacing: p.tickSpacing,
301
- hooks: p.hooks,
302
- hookData: p.hookData
303
- })),
304
- amountIn,
305
- amountOutMinimum: minAmountOut
306
- }
307
- ]
308
- );
309
- const settleParam = encodeAbiParameters(
310
- [
311
- { name: "currency", type: "address" },
312
- { name: "maxAmount", type: "uint256" }
313
- ],
314
- [currencyIn, amountIn]
315
- );
316
- const takeParam = encodeAbiParameters(
317
- [
318
- { name: "currency", type: "address" },
319
- { name: "minAmount", type: "uint256" }
320
- ],
321
- [outputCurrency, minAmountOut]
322
- );
323
- return encodeAbiParameters(
324
- [
325
- { name: "actions", type: "bytes" },
326
- { name: "params", type: "bytes[]" }
327
- ],
328
- [actions, [swapParam, settleParam, takeParam]]
329
- );
330
- }
331
- function buildUniversalRouterExecuteArgs(currencyIn, path, amountIn, minAmountOut, outputCurrency) {
332
- const commands = encodePacked(["uint8"], [V4_SWAP]);
333
- const inputs = [
334
- buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency)
335
- ];
336
- return { commands, inputs };
283
+ function buildV3SwapInputExactIn(recipient, path, amountIn, minAmountOut) {
284
+ if (amountIn <= 0n || amountIn > UINT128_MAX) {
285
+ throw new Error(
286
+ `buildV3SwapInputExactIn: amountIn (${amountIn}) must be in (0, 2^128-1]`
287
+ );
288
+ }
289
+ if (minAmountOut < 0n) {
290
+ throw new Error(
291
+ `buildV3SwapInputExactIn: minAmountOut (${minAmountOut}) must be non-negative`
292
+ );
293
+ }
294
+ const pathBytes = encodeV3Path2(path);
295
+ return encodeAbiParameters(V3_EXACT_IN_INPUT_ABI, [
296
+ recipient,
297
+ amountIn,
298
+ minAmountOut,
299
+ pathBytes,
300
+ true
301
+ // payerIsUser — router pulls via Permit2 (the SDK's only flow)
302
+ ]);
337
303
  }
338
- function buildV4SwapInputExactOut(currencyOut, path, amountOut, maxAmountIn, inputCurrency) {
304
+ function buildV3SwapInputExactOut(recipient, path, amountOut, maxAmountIn) {
339
305
  if (amountOut <= 0n || amountOut > UINT128_MAX) {
340
306
  throw new Error(
341
- `buildV4SwapInputExactOut: amountOut (${amountOut}) must be in (0, 2^128-1]`
307
+ `buildV3SwapInputExactOut: amountOut (${amountOut}) must be in (0, 2^128-1]`
342
308
  );
343
309
  }
344
310
  if (maxAmountIn <= 0n || maxAmountIn > UINT128_MAX) {
345
311
  throw new Error(
346
- `buildV4SwapInputExactOut: maxAmountIn (${maxAmountIn}) must be in (0, 2^128-1]`
312
+ `buildV3SwapInputExactOut: maxAmountIn (${maxAmountIn}) must be in (0, 2^128-1]`
347
313
  );
348
314
  }
349
- const actions = encodePacked(
350
- ["uint8", "uint8", "uint8"],
351
- [SWAP_EXACT_OUT, SETTLE_ALL, TAKE_ALL]
352
- );
353
- const swapParam = encodeAbiParameters(
354
- [{ name: "swap", type: "tuple", components: EXACT_OUTPUT_PARAMS_ABI }],
355
- [
356
- {
357
- currencyOut,
358
- path: path.map((p) => ({
359
- intermediateCurrency: p.intermediateCurrency,
360
- fee: BigInt(p.fee),
361
- tickSpacing: p.tickSpacing,
362
- hooks: p.hooks,
363
- hookData: p.hookData
364
- })),
365
- amountOut,
366
- amountInMaximum: maxAmountIn
367
- }
368
- ]
369
- );
370
- const settleParam = encodeAbiParameters(
371
- [
372
- { name: "currency", type: "address" },
373
- { name: "maxAmount", type: "uint256" }
374
- ],
375
- [inputCurrency, maxAmountIn]
376
- );
377
- const takeParam = encodeAbiParameters(
378
- [
379
- { name: "currency", type: "address" },
380
- { name: "minAmount", type: "uint256" }
381
- ],
382
- [currencyOut, amountOut]
383
- );
384
- return encodeAbiParameters(
385
- [
386
- { name: "actions", type: "bytes" },
387
- { name: "params", type: "bytes[]" }
388
- ],
389
- [actions, [swapParam, settleParam, takeParam]]
390
- );
315
+ const pathBytes = encodeV3PathReversed2(path);
316
+ return encodeAbiParameters(V3_EXACT_OUT_INPUT_ABI, [
317
+ recipient,
318
+ amountOut,
319
+ maxAmountIn,
320
+ pathBytes,
321
+ true
322
+ // payerIsUser
323
+ ]);
391
324
  }
392
- function buildUniversalRouterExecuteArgsExactOut(currencyOut, path, amountOut, maxAmountIn, inputCurrency) {
393
- const commands = encodePacked(["uint8"], [V4_SWAP]);
325
+ function buildUniversalRouterExecuteArgs(recipient, path, amountIn, minAmountOut) {
326
+ const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
394
327
  const inputs = [
395
- buildV4SwapInputExactOut(
396
- currencyOut,
397
- path,
398
- amountOut,
399
- maxAmountIn,
400
- inputCurrency
401
- )
328
+ buildV3SwapInputExactIn(recipient, path, amountIn, minAmountOut)
402
329
  ];
403
330
  return { commands, inputs };
404
331
  }
405
- var _buildSwapFromQuoteWarned = false;
406
- function buildSwapFromQuote(params) {
407
- if (!_buildSwapFromQuoteWarned) {
408
- _buildSwapFromQuoteWarned = true;
409
- console.warn(
410
- "[PAFI] DEPRECATION (v1.4+): `buildSwapFromQuote` from @pafi-dev/trading is deprecated and will be removed in v2.0. Use `buildUniversalRouterExecuteArgs` directly with `quote.bestRoute.path`."
411
- );
412
- }
413
- return buildUniversalRouterExecuteArgs(
414
- params.currencyIn,
415
- params.quote.path,
416
- params.amountIn,
417
- params.minAmountOut,
418
- params.currencyOut
419
- );
332
+ function buildUniversalRouterExecuteArgsExactOut(recipient, path, amountOut, maxAmountIn) {
333
+ const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_OUT]);
334
+ const inputs = [
335
+ buildV3SwapInputExactOut(recipient, path, amountOut, maxAmountIn)
336
+ ];
337
+ return { commands, inputs };
420
338
  }
421
339
 
422
340
  // src/swap/simulate.ts
@@ -463,9 +381,14 @@ function buildSwapUserOp(params) {
463
381
  "buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert"
464
382
  );
465
383
  }
466
- if (params.swapPath.length === 0) {
384
+ if (params.swapPath.tokens.length < 2 || params.swapPath.fees.length < 1) {
385
+ throw new Error(
386
+ "buildSwapUserOp: swapPath must contain at least one hop (>=2 tokens, >=1 fee)"
387
+ );
388
+ }
389
+ if (params.swapPath.tokens.length !== params.swapPath.fees.length + 1) {
467
390
  throw new Error(
468
- "buildSwapUserOp: swapPath must contain at least one PathKey"
391
+ "buildSwapUserOp: swapPath.tokens.length must equal swapPath.fees.length + 1"
469
392
  );
470
393
  }
471
394
  const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
@@ -475,11 +398,10 @@ function buildSwapUserOp(params) {
475
398
  );
476
399
  }
477
400
  const { commands, inputs } = buildUniversalRouterExecuteArgs(
478
- params.inputTokenAddress,
401
+ params.userAddress,
479
402
  params.swapPath,
480
403
  params.amountIn,
481
- params.minAmountOut,
482
- params.outputTokenAddress
404
+ params.minAmountOut
483
405
  );
484
406
  const swapCallData = encodeFunctionData2({
485
407
  abi: universalRouterAbi2,
@@ -529,9 +451,14 @@ function buildSwapUserOpExactOut(params) {
529
451
  "buildSwapUserOpExactOut: gasFeeAmountInput must be non-negative"
530
452
  );
531
453
  }
532
- if (params.swapPath.length === 0) {
454
+ if (params.swapPath.tokens.length < 2 || params.swapPath.fees.length < 1) {
533
455
  throw new Error(
534
- "buildSwapUserOpExactOut: swapPath must contain at least one PathKey"
456
+ "buildSwapUserOpExactOut: swapPath must contain at least one hop (>=2 tokens, >=1 fee)"
457
+ );
458
+ }
459
+ if (params.swapPath.tokens.length !== params.swapPath.fees.length + 1) {
460
+ throw new Error(
461
+ "buildSwapUserOpExactOut: swapPath.tokens.length must equal swapPath.fees.length + 1"
535
462
  );
536
463
  }
537
464
  const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
@@ -541,11 +468,10 @@ function buildSwapUserOpExactOut(params) {
541
468
  );
542
469
  }
543
470
  const { commands, inputs } = buildUniversalRouterExecuteArgsExactOut(
544
- params.outputTokenAddress,
471
+ params.userAddress,
545
472
  params.swapPath,
546
473
  params.amountOut,
547
- params.maxAmountIn,
548
- params.inputTokenAddress
474
+ params.maxAmountIn
549
475
  );
550
476
  const swapCallData = encodeFunctionData2({
551
477
  abi: universalRouterAbi2,
@@ -606,7 +532,7 @@ var TradingHandlers = class {
606
532
  // GET /quote
607
533
  // =========================================================================
608
534
  /**
609
- * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
535
+ * Quote exact-input PT → USDT via Uniswap V3 QuoterV2.
610
536
  *
611
537
  * Uses multicall to batch all candidate routes into a single RPC call.
612
538
  * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
@@ -746,7 +672,7 @@ var TradingHandlers = class {
746
672
  request.chainId,
747
673
  outputTokenAddress
748
674
  ).catch(() => 0n);
749
- const hops = quoteResult.bestRoute.path.length;
675
+ const hops = quoteResult.bestRoute.path.fees.length;
750
676
  const slippageBps = request.slippageBps ?? (hops > 1 ? 100 : 50);
751
677
  const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
752
678
  const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
@@ -799,11 +725,11 @@ var TradingHandlers = class {
799
725
  };
800
726
  }
801
727
  // =========================================================================
802
- // GET /quote/exact-out — V4 exact-output quote
728
+ // GET /quote/exact-out — V3 exact-output quote
803
729
  // =========================================================================
804
730
  /**
805
731
  * Quote the input required to receive `request.amount` of the output
806
- * token via Uniswap V4. Input-side operator fee is auto-quoted, so
732
+ * token via Uniswap V3 QuoterV2. Input-side operator fee is auto-quoted, so
807
733
  * `inputGross = estimatedInputAmount + feeAmountInput` is the total
808
734
  * the user must hold.
809
735
  *
@@ -864,10 +790,10 @@ var TradingHandlers = class {
864
790
  }
865
791
  }
866
792
  // =========================================================================
867
- // POST /swap/exact-out — V4 exact-output swap UserOp
793
+ // POST /swap/exact-out — V3 exact-output swap UserOp
868
794
  // =========================================================================
869
795
  /**
870
- * Build a V4 exact-output swap UserOp.
796
+ * Build a V3 exact-output swap UserOp.
871
797
  *
872
798
  * Quotes the best exact-output route, applies slippage as a CEILING on
873
799
  * `maxAmountIn` (so the cap is never silently tightened by floor
@@ -876,7 +802,7 @@ var TradingHandlers = class {
876
802
  * inputToken.transfer(feeRecipient, gasFeeAmountInput) [if > 0]
877
803
  * → input.approve(Permit2, maxAmountIn)
878
804
  * → Permit2.approve(router, maxAmountIn)
879
- * → UniversalRouter.execute (V4 SWAP_EXACT_OUT)
805
+ * → UniversalRouter.execute (V3_SWAP_EXACT_OUT)
880
806
  *
881
807
  * Operator fee is INPUT-token-side (charged before swap) so the user
882
808
  * receives exactly `request.amount` of output.
@@ -946,7 +872,7 @@ var TradingHandlers = class {
946
872
  request.chainId,
947
873
  inputTokenAddress
948
874
  ).catch(() => 0n);
949
- const hops = quoteResult.bestRoute.path.length;
875
+ const hops = quoteResult.bestRoute.path.fees.length;
950
876
  const slippageBps = request.slippageBps ?? (hops > 1 ? 100 : 50);
951
877
  const estimatedInputAmount = quoteResult.bestRoute.amountIn;
952
878
  const slippageNumerator = estimatedInputAmount * BigInt(1e4 + slippageBps);
@@ -1276,7 +1202,7 @@ async function swapDirect(params) {
1276
1202
  `swapDirect: no swap path found from ${params.inputTokenAddress} to ${params.outputTokenAddress} (cause: ${cause})`
1277
1203
  );
1278
1204
  }
1279
- const hops = quoteResult.bestRoute.path.length;
1205
+ const hops = quoteResult.bestRoute.path.fees.length;
1280
1206
  const slippageBps = params.slippageBps ?? (hops > 1 ? 100 : 50);
1281
1207
  const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
1282
1208
  const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
@@ -1398,7 +1324,7 @@ async function swapDirectExactOut(params) {
1398
1324
  `swapDirectExactOut: no swap path found from ${params.inputTokenAddress} to ${params.outputTokenAddress} (cause: ${cause})`
1399
1325
  );
1400
1326
  }
1401
- const hops = quoteResult.bestRoute.path.length;
1327
+ const hops = quoteResult.bestRoute.path.fees.length;
1402
1328
  const slippageBps = params.slippageBps ?? (hops > 1 ? 100 : 50);
1403
1329
  const estimatedInputAmount = quoteResult.bestRoute.amountIn;
1404
1330
  const slippageNumerator = estimatedInputAmount * BigInt(1e4 + slippageBps);
@@ -1597,19 +1523,18 @@ async function perpDepositDirect(params) {
1597
1523
  }
1598
1524
  export {
1599
1525
  PAFI_SUBGRAPH_URL,
1600
- SWAP_EXACT_OUT,
1601
- SWAP_EXACT_OUT_SINGLE,
1602
1526
  TradingHandlers,
1527
+ V3_SWAP_EXACT_IN,
1528
+ V3_SWAP_EXACT_OUT,
1603
1529
  buildAllPaths,
1604
1530
  buildErc20ApprovalCalldata,
1605
1531
  buildPermit2ApprovalCalldata,
1606
- buildSwapFromQuote,
1607
1532
  buildSwapUserOp,
1608
1533
  buildSwapUserOpExactOut,
1609
1534
  buildUniversalRouterExecuteArgs,
1610
1535
  buildUniversalRouterExecuteArgsExactOut,
1611
- buildV4SwapInput,
1612
- buildV4SwapInputExactOut,
1536
+ buildV3SwapInputExactIn,
1537
+ buildV3SwapInputExactOut,
1613
1538
  checkAllowance,
1614
1539
  combineRoutes,
1615
1540
  fetchPafiPools,