@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.cjs CHANGED
@@ -21,19 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  PAFI_SUBGRAPH_URL: () => import_core10.PAFI_SUBGRAPH_URL,
24
- SWAP_EXACT_OUT: () => SWAP_EXACT_OUT,
25
- SWAP_EXACT_OUT_SINGLE: () => SWAP_EXACT_OUT_SINGLE,
26
24
  TradingHandlers: () => TradingHandlers,
25
+ V3_SWAP_EXACT_IN: () => V3_SWAP_EXACT_IN,
26
+ V3_SWAP_EXACT_OUT: () => V3_SWAP_EXACT_OUT,
27
27
  buildAllPaths: () => buildAllPaths,
28
28
  buildErc20ApprovalCalldata: () => buildErc20ApprovalCalldata,
29
29
  buildPermit2ApprovalCalldata: () => buildPermit2ApprovalCalldata,
30
- buildSwapFromQuote: () => buildSwapFromQuote,
31
30
  buildSwapUserOp: () => buildSwapUserOp,
32
31
  buildSwapUserOpExactOut: () => buildSwapUserOpExactOut,
33
32
  buildUniversalRouterExecuteArgs: () => buildUniversalRouterExecuteArgs,
34
33
  buildUniversalRouterExecuteArgsExactOut: () => buildUniversalRouterExecuteArgsExactOut,
35
- buildV4SwapInput: () => buildV4SwapInput,
36
- buildV4SwapInputExactOut: () => buildV4SwapInputExactOut,
34
+ buildV3SwapInputExactIn: () => buildV3SwapInputExactIn,
35
+ buildV3SwapInputExactOut: () => buildV3SwapInputExactOut,
37
36
  checkAllowance: () => checkAllowance,
38
37
  combineRoutes: () => combineRoutes,
39
38
  fetchPafiPools: () => import_core10.fetchPafiPools,
@@ -58,7 +57,6 @@ var import_core9 = require("@pafi-dev/core");
58
57
 
59
58
  // src/quoting/routes.ts
60
59
  var import_core = require("@pafi-dev/core");
61
- var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
62
60
  function combineRoutes(chainId, pointTokenAddress) {
63
61
  const commonPools = import_core.COMMON_POOLS[chainId] ?? [];
64
62
  const pointPools = import_core.POINT_TOKEN_POOLS[chainId]?.[pointTokenAddress] ?? [];
@@ -66,84 +64,77 @@ function combineRoutes(chainId, pointTokenAddress) {
66
64
  }
67
65
  function buildAllPaths(pools, tokenIn, tokenOut, maxHops = 3) {
68
66
  const results = [];
69
- function dfs(currentToken, currentPath, usedPoolIndices) {
70
- if (currentPath.length > maxHops) return;
71
- if (currentPath.length > 0 && currentToken.toLowerCase() === tokenOut.toLowerCase()) {
72
- results.push([...currentPath]);
67
+ function dfs(currentToken, tokens, fees, usedPoolIndices) {
68
+ const hopsSoFar = tokens.length - 1;
69
+ if (hopsSoFar > maxHops) return;
70
+ if (hopsSoFar > 0 && currentToken.toLowerCase() === tokenOut.toLowerCase()) {
71
+ results.push({ tokens: [...tokens], fees: [...fees] });
73
72
  return;
74
73
  }
75
74
  for (let i = 0; i < pools.length; i++) {
76
75
  if (usedPoolIndices.has(i)) continue;
77
76
  const pool = pools[i];
78
- const c0 = pool.currency0.toLowerCase();
79
- const c1 = pool.currency1.toLowerCase();
77
+ const t0 = pool.token0.toLowerCase();
78
+ const t1 = pool.token1.toLowerCase();
80
79
  const curr = currentToken.toLowerCase();
81
80
  let nextToken = null;
82
- if (curr === c0) {
83
- nextToken = pool.currency1;
84
- } else if (curr === c1) {
85
- nextToken = pool.currency0;
81
+ if (curr === t0) {
82
+ nextToken = pool.token1;
83
+ } else if (curr === t1) {
84
+ nextToken = pool.token0;
86
85
  }
87
86
  if (!nextToken) continue;
88
- const hop = {
89
- intermediateCurrency: nextToken,
90
- fee: pool.fee,
91
- tickSpacing: pool.tickSpacing,
92
- hooks: pool.hooks ?? ZERO_ADDRESS,
93
- hookData: "0x"
94
- };
95
87
  usedPoolIndices.add(i);
96
- currentPath.push(hop);
97
- dfs(nextToken, currentPath, usedPoolIndices);
98
- currentPath.pop();
88
+ tokens.push(nextToken);
89
+ fees.push(pool.fee);
90
+ dfs(nextToken, tokens, fees, usedPoolIndices);
91
+ tokens.pop();
92
+ fees.pop();
99
93
  usedPoolIndices.delete(i);
100
94
  }
101
95
  }
102
- dfs(tokenIn, [], /* @__PURE__ */ new Set());
96
+ dfs(tokenIn, [tokenIn], [], /* @__PURE__ */ new Set());
103
97
  return results;
104
98
  }
105
99
 
106
100
  // src/quoting/quote.ts
107
101
  var import_core2 = require("@pafi-dev/core");
108
- var import_core3 = require("@pafi-dev/core");
109
- async function quoteExactInput(client, quoterAddress, exactCurrency, path, exactAmount) {
110
- const [amountOut, gasEstimate] = await client.readContract({
102
+ async function quoteExactInput(client, quoterAddress, path, exactAmount) {
103
+ const pathBytes = (0, import_core2.encodeV3Path)(path);
104
+ const result = await client.readContract({
111
105
  address: quoterAddress,
112
- abi: import_core2.v4QuoterAbi,
106
+ abi: import_core2.v3QuoterV2Abi,
113
107
  functionName: "quoteExactInput",
114
- args: [{ exactCurrency, path, exactAmount }]
108
+ args: [pathBytes, exactAmount]
115
109
  });
110
+ const [amountOut, , , gasEstimate] = result;
116
111
  return { amountOut, gasEstimate, path };
117
112
  }
118
- async function quoteExactInputSingle(client, quoterAddress, poolKey, zeroForOne, exactAmount, hookData) {
119
- const [amountOut, gasEstimate] = await client.readContract({
113
+ async function quoteExactInputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
114
+ const result = await client.readContract({
120
115
  address: quoterAddress,
121
- abi: import_core2.v4QuoterAbi,
116
+ abi: import_core2.v3QuoterV2Abi,
122
117
  functionName: "quoteExactInputSingle",
123
118
  args: [
124
119
  {
125
- poolKey,
126
- zeroForOne,
127
- exactAmount,
128
- hookData
120
+ tokenIn,
121
+ tokenOut,
122
+ amountIn: exactAmount,
123
+ fee,
124
+ sqrtPriceLimitX96
129
125
  }
130
126
  ]
131
127
  });
128
+ const [amountOut, , , gasEstimate] = result;
132
129
  return { amountOut, gasEstimate };
133
130
  }
134
- async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exactAmount) {
131
+ async function quoteBestRoute(client, quoterAddress, routes, exactAmount) {
135
132
  const results = await client.multicall({
136
133
  contracts: routes.map((path) => ({
137
134
  address: quoterAddress,
138
- abi: import_core2.v4QuoterAbi,
135
+ abi: import_core2.v3QuoterV2Abi,
139
136
  functionName: "quoteExactInput",
140
- args: [
141
- {
142
- exactCurrency,
143
- path,
144
- exactAmount
145
- }
146
- ]
137
+ args: [(0, import_core2.encodeV3Path)(path), exactAmount]
147
138
  })),
148
139
  allowFailure: true
149
140
  });
@@ -152,7 +143,15 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
152
143
  for (let i = 0; i < results.length; i++) {
153
144
  const r = results[i];
154
145
  if (r.status === "success") {
155
- const [amountOut, gasEstimate] = r.result;
146
+ const tuple = r.result;
147
+ if (!Array.isArray(tuple) || tuple.length < 4) {
148
+ if (firstFailure === void 0) {
149
+ const len = Array.isArray(tuple) ? tuple.length : "n/a";
150
+ firstFailure = `unexpected QuoterV2 return shape (length=${len})`;
151
+ }
152
+ continue;
153
+ }
154
+ const [amountOut, , , gasEstimate] = tuple;
156
155
  allRoutes.push({ amountOut, gasEstimate, path: routes[i] });
157
156
  } else if (firstFailure === void 0) {
158
157
  const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
@@ -161,7 +160,7 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
161
160
  }
162
161
  if (allRoutes.length === 0) {
163
162
  throw new Error(
164
- `No valid routes found from ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
163
+ `No valid routes found (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
165
164
  );
166
165
  }
167
166
  const bestRoute = allRoutes.reduce(
@@ -170,56 +169,54 @@ async function quoteBestRoute(client, quoterAddress, exactCurrency, routes, exac
170
169
  return { bestRoute, allRoutes };
171
170
  }
172
171
  async function findBestQuote(client, chainId, tokenIn, tokenOut, exactAmount, pools = [], quoterAddress, maxHops = 3) {
173
- const quoter = quoterAddress ?? import_core3.V4_QUOTER_ADDRESSES[chainId];
172
+ const quoter = quoterAddress ?? import_core2.QUOTER_V2_ADDRESSES[chainId];
174
173
  if (!quoter) {
175
- throw new Error(`No V4 Quoter address configured for chain ${chainId}`);
174
+ throw new Error(`No V3 QuoterV2 address configured for chain ${chainId}`);
176
175
  }
177
- const commonPools = import_core3.COMMON_POOLS[chainId] ?? [];
176
+ const commonPools = import_core2.COMMON_POOLS[chainId] ?? [];
178
177
  const allPools = [...pools, ...commonPools];
179
178
  const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);
180
179
  if (paths.length === 0) {
181
180
  throw new Error(`No paths found from ${tokenIn} to ${tokenOut}`);
182
181
  }
183
- return quoteBestRoute(client, quoter, tokenIn, paths, exactAmount);
182
+ return quoteBestRoute(client, quoter, paths, exactAmount);
184
183
  }
185
- async function quoteExactOutput(client, quoterAddress, exactCurrency, path, exactAmount) {
186
- const [amountIn, gasEstimate] = await client.readContract({
184
+ async function quoteExactOutput(client, quoterAddress, path, exactAmount) {
185
+ const pathBytes = (0, import_core2.encodeV3PathReversed)(path);
186
+ const result = await client.readContract({
187
187
  address: quoterAddress,
188
- abi: import_core2.v4QuoterAbi,
188
+ abi: import_core2.v3QuoterV2Abi,
189
189
  functionName: "quoteExactOutput",
190
- args: [{ exactCurrency, path, exactAmount }]
190
+ args: [pathBytes, exactAmount]
191
191
  });
192
+ const [amountIn, , , gasEstimate] = result;
192
193
  return { amountIn, gasEstimate, path };
193
194
  }
194
- async function quoteExactOutputSingle(client, quoterAddress, poolKey, zeroForOne, exactAmount, hookData) {
195
- const [amountIn, gasEstimate] = await client.readContract({
195
+ async function quoteExactOutputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
196
+ const result = await client.readContract({
196
197
  address: quoterAddress,
197
- abi: import_core2.v4QuoterAbi,
198
+ abi: import_core2.v3QuoterV2Abi,
198
199
  functionName: "quoteExactOutputSingle",
199
200
  args: [
200
201
  {
201
- poolKey,
202
- zeroForOne,
203
- exactAmount,
204
- hookData
202
+ tokenIn,
203
+ tokenOut,
204
+ amount: exactAmount,
205
+ fee,
206
+ sqrtPriceLimitX96
205
207
  }
206
208
  ]
207
209
  });
210
+ const [amountIn, , , gasEstimate] = result;
208
211
  return { amountIn, gasEstimate };
209
212
  }
210
- async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, routes, exactAmount) {
213
+ async function quoteBestRouteExactOut(client, quoterAddress, routes, exactAmount) {
211
214
  const results = await client.multicall({
212
215
  contracts: routes.map((path) => ({
213
216
  address: quoterAddress,
214
- abi: import_core2.v4QuoterAbi,
217
+ abi: import_core2.v3QuoterV2Abi,
215
218
  functionName: "quoteExactOutput",
216
- args: [
217
- {
218
- exactCurrency,
219
- path,
220
- exactAmount
221
- }
222
- ]
219
+ args: [(0, import_core2.encodeV3PathReversed)(path), exactAmount]
223
220
  })),
224
221
  allowFailure: true
225
222
  });
@@ -228,7 +225,15 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
228
225
  for (let i = 0; i < results.length; i++) {
229
226
  const r = results[i];
230
227
  if (r.status === "success") {
231
- const [amountIn, gasEstimate] = r.result;
228
+ const tuple = r.result;
229
+ if (!Array.isArray(tuple) || tuple.length < 4) {
230
+ if (firstFailure === void 0) {
231
+ const len = Array.isArray(tuple) ? tuple.length : "n/a";
232
+ firstFailure = `unexpected QuoterV2 return shape (length=${len})`;
233
+ }
234
+ continue;
235
+ }
236
+ const [amountIn, , , gasEstimate] = tuple;
232
237
  allRoutes.push({ amountIn, gasEstimate, path: routes[i] });
233
238
  } else if (firstFailure === void 0) {
234
239
  const errMsg = r.error instanceof Error ? r.error.message : String(r.error ?? "unknown");
@@ -237,7 +242,7 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
237
242
  }
238
243
  if (allRoutes.length === 0) {
239
244
  throw new Error(
240
- `No valid exact-output routes found to ${exactCurrency} (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
245
+ `No valid exact-output routes found (${routes.length} candidates probed)` + (firstFailure ? `; first failure: ${firstFailure}` : "")
241
246
  );
242
247
  }
243
248
  const bestRoute = allRoutes.reduce(
@@ -246,43 +251,43 @@ async function quoteBestRouteExactOut(client, quoterAddress, exactCurrency, rout
246
251
  return { bestRoute, allRoutes };
247
252
  }
248
253
  async function findBestQuoteExactOut(client, chainId, tokenIn, tokenOut, exactAmount, pools = [], quoterAddress, maxHops = 3) {
249
- const quoter = quoterAddress ?? import_core3.V4_QUOTER_ADDRESSES[chainId];
254
+ const quoter = quoterAddress ?? import_core2.QUOTER_V2_ADDRESSES[chainId];
250
255
  if (!quoter) {
251
- throw new Error(`No V4 Quoter address configured for chain ${chainId}`);
256
+ throw new Error(`No V3 QuoterV2 address configured for chain ${chainId}`);
252
257
  }
253
- const commonPools = import_core3.COMMON_POOLS[chainId] ?? [];
258
+ const commonPools = import_core2.COMMON_POOLS[chainId] ?? [];
254
259
  const allPools = [...pools, ...commonPools];
255
- const paths = buildAllPaths(allPools, tokenOut, tokenIn, maxHops);
260
+ const paths = buildAllPaths(allPools, tokenIn, tokenOut, maxHops);
256
261
  if (paths.length === 0) {
257
262
  throw new Error(
258
- `No exact-output paths found to ${tokenOut} from ${tokenIn}`
263
+ `No exact-output paths found from ${tokenIn} to ${tokenOut}`
259
264
  );
260
265
  }
261
- return quoteBestRouteExactOut(client, quoter, tokenOut, paths, exactAmount);
266
+ return quoteBestRouteExactOut(client, quoter, paths, exactAmount);
262
267
  }
263
268
 
264
269
  // src/swap/approval.ts
265
270
  var import_viem = require("viem");
271
+ var import_core3 = require("@pafi-dev/core");
266
272
  var import_core4 = require("@pafi-dev/core");
267
- var import_core5 = require("@pafi-dev/core");
268
273
  async function checkAllowance(client, token, owner, spender) {
269
274
  return client.readContract({
270
275
  address: token,
271
- abi: import_core4.erc20Abi,
276
+ abi: import_core3.erc20Abi,
272
277
  functionName: "allowance",
273
278
  args: [owner, spender]
274
279
  });
275
280
  }
276
281
  function buildErc20ApprovalCalldata(spender, amount) {
277
282
  return (0, import_viem.encodeFunctionData)({
278
- abi: import_core4.erc20Abi,
283
+ abi: import_core3.erc20Abi,
279
284
  functionName: "approve",
280
285
  args: [spender, amount]
281
286
  });
282
287
  }
283
288
  function buildPermit2ApprovalCalldata(token, spender, amount, expiration) {
284
289
  return (0, import_viem.encodeFunctionData)({
285
- abi: import_core5.permit2Abi,
290
+ abi: import_core4.permit2Abi,
286
291
  functionName: "approve",
287
292
  args: [token, spender, amount, expiration]
288
293
  });
@@ -290,173 +295,79 @@ function buildPermit2ApprovalCalldata(token, spender, amount, expiration) {
290
295
 
291
296
  // src/swap/universalRouter.ts
292
297
  var import_viem2 = require("viem");
293
- var V4_SWAP = 16;
294
- var SWAP_EXACT_IN = 7;
295
- var SWAP_EXACT_OUT_SINGLE = 8;
296
- var SWAP_EXACT_OUT = 9;
297
- var SETTLE_ALL = 12;
298
- var TAKE_ALL = 15;
298
+ var import_core5 = require("@pafi-dev/core");
299
+ var V3_SWAP_EXACT_IN = 0;
300
+ var V3_SWAP_EXACT_OUT = 1;
299
301
  var UINT128_MAX = 2n ** 128n - 1n;
300
- var PATH_KEY_ABI_COMPONENTS = [
301
- { name: "intermediateCurrency", type: "address" },
302
- { name: "fee", type: "uint256" },
303
- { name: "tickSpacing", type: "int24" },
304
- { name: "hooks", type: "address" },
305
- { name: "hookData", type: "bytes" }
302
+ var V3_EXACT_IN_INPUT_ABI = [
303
+ { name: "recipient", type: "address" },
304
+ { name: "amountIn", type: "uint256" },
305
+ { name: "amountOutMinimum", type: "uint256" },
306
+ { name: "path", type: "bytes" },
307
+ { name: "payerIsUser", type: "bool" }
306
308
  ];
307
- var EXACT_INPUT_PARAMS_ABI = [
308
- { name: "currencyIn", type: "address" },
309
- {
310
- name: "path",
311
- type: "tuple[]",
312
- components: PATH_KEY_ABI_COMPONENTS
313
- },
314
- { name: "amountIn", type: "uint128" },
315
- { name: "amountOutMinimum", type: "uint128" }
309
+ var V3_EXACT_OUT_INPUT_ABI = [
310
+ { name: "recipient", type: "address" },
311
+ { name: "amountOut", type: "uint256" },
312
+ { name: "amountInMaximum", type: "uint256" },
313
+ { name: "path", type: "bytes" },
314
+ { name: "payerIsUser", type: "bool" }
316
315
  ];
317
- var EXACT_OUTPUT_PARAMS_ABI = [
318
- { name: "currencyOut", type: "address" },
319
- {
320
- name: "path",
321
- type: "tuple[]",
322
- components: PATH_KEY_ABI_COMPONENTS
323
- },
324
- { name: "amountOut", type: "uint128" },
325
- { name: "amountInMaximum", type: "uint128" }
326
- ];
327
- function buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency) {
328
- const actions = (0, import_viem2.encodePacked)(
329
- ["uint8", "uint8", "uint8"],
330
- [SWAP_EXACT_IN, SETTLE_ALL, TAKE_ALL]
331
- );
332
- const swapParam = (0, import_viem2.encodeAbiParameters)(
333
- [{ name: "swap", type: "tuple", components: EXACT_INPUT_PARAMS_ABI }],
334
- [
335
- {
336
- currencyIn,
337
- path: path.map((p) => ({
338
- intermediateCurrency: p.intermediateCurrency,
339
- fee: BigInt(p.fee),
340
- tickSpacing: p.tickSpacing,
341
- hooks: p.hooks,
342
- hookData: p.hookData
343
- })),
344
- amountIn,
345
- amountOutMinimum: minAmountOut
346
- }
347
- ]
348
- );
349
- const settleParam = (0, import_viem2.encodeAbiParameters)(
350
- [
351
- { name: "currency", type: "address" },
352
- { name: "maxAmount", type: "uint256" }
353
- ],
354
- [currencyIn, amountIn]
355
- );
356
- const takeParam = (0, import_viem2.encodeAbiParameters)(
357
- [
358
- { name: "currency", type: "address" },
359
- { name: "minAmount", type: "uint256" }
360
- ],
361
- [outputCurrency, minAmountOut]
362
- );
363
- return (0, import_viem2.encodeAbiParameters)(
364
- [
365
- { name: "actions", type: "bytes" },
366
- { name: "params", type: "bytes[]" }
367
- ],
368
- [actions, [swapParam, settleParam, takeParam]]
369
- );
370
- }
371
- function buildUniversalRouterExecuteArgs(currencyIn, path, amountIn, minAmountOut, outputCurrency) {
372
- const commands = (0, import_viem2.encodePacked)(["uint8"], [V4_SWAP]);
373
- const inputs = [
374
- buildV4SwapInput(currencyIn, path, amountIn, minAmountOut, outputCurrency)
375
- ];
376
- return { commands, inputs };
316
+ function buildV3SwapInputExactIn(recipient, path, amountIn, minAmountOut) {
317
+ if (amountIn <= 0n || amountIn > UINT128_MAX) {
318
+ throw new Error(
319
+ `buildV3SwapInputExactIn: amountIn (${amountIn}) must be in (0, 2^128-1]`
320
+ );
321
+ }
322
+ if (minAmountOut < 0n) {
323
+ throw new Error(
324
+ `buildV3SwapInputExactIn: minAmountOut (${minAmountOut}) must be non-negative`
325
+ );
326
+ }
327
+ const pathBytes = (0, import_core5.encodeV3Path)(path);
328
+ return (0, import_viem2.encodeAbiParameters)(V3_EXACT_IN_INPUT_ABI, [
329
+ recipient,
330
+ amountIn,
331
+ minAmountOut,
332
+ pathBytes,
333
+ true
334
+ // payerIsUser — router pulls via Permit2 (the SDK's only flow)
335
+ ]);
377
336
  }
378
- function buildV4SwapInputExactOut(currencyOut, path, amountOut, maxAmountIn, inputCurrency) {
337
+ function buildV3SwapInputExactOut(recipient, path, amountOut, maxAmountIn) {
379
338
  if (amountOut <= 0n || amountOut > UINT128_MAX) {
380
339
  throw new Error(
381
- `buildV4SwapInputExactOut: amountOut (${amountOut}) must be in (0, 2^128-1]`
340
+ `buildV3SwapInputExactOut: amountOut (${amountOut}) must be in (0, 2^128-1]`
382
341
  );
383
342
  }
384
343
  if (maxAmountIn <= 0n || maxAmountIn > UINT128_MAX) {
385
344
  throw new Error(
386
- `buildV4SwapInputExactOut: maxAmountIn (${maxAmountIn}) must be in (0, 2^128-1]`
345
+ `buildV3SwapInputExactOut: maxAmountIn (${maxAmountIn}) must be in (0, 2^128-1]`
387
346
  );
388
347
  }
389
- const actions = (0, import_viem2.encodePacked)(
390
- ["uint8", "uint8", "uint8"],
391
- [SWAP_EXACT_OUT, SETTLE_ALL, TAKE_ALL]
392
- );
393
- const swapParam = (0, import_viem2.encodeAbiParameters)(
394
- [{ name: "swap", type: "tuple", components: EXACT_OUTPUT_PARAMS_ABI }],
395
- [
396
- {
397
- currencyOut,
398
- path: path.map((p) => ({
399
- intermediateCurrency: p.intermediateCurrency,
400
- fee: BigInt(p.fee),
401
- tickSpacing: p.tickSpacing,
402
- hooks: p.hooks,
403
- hookData: p.hookData
404
- })),
405
- amountOut,
406
- amountInMaximum: maxAmountIn
407
- }
408
- ]
409
- );
410
- const settleParam = (0, import_viem2.encodeAbiParameters)(
411
- [
412
- { name: "currency", type: "address" },
413
- { name: "maxAmount", type: "uint256" }
414
- ],
415
- [inputCurrency, maxAmountIn]
416
- );
417
- const takeParam = (0, import_viem2.encodeAbiParameters)(
418
- [
419
- { name: "currency", type: "address" },
420
- { name: "minAmount", type: "uint256" }
421
- ],
422
- [currencyOut, amountOut]
423
- );
424
- return (0, import_viem2.encodeAbiParameters)(
425
- [
426
- { name: "actions", type: "bytes" },
427
- { name: "params", type: "bytes[]" }
428
- ],
429
- [actions, [swapParam, settleParam, takeParam]]
430
- );
348
+ const pathBytes = (0, import_core5.encodeV3PathReversed)(path);
349
+ return (0, import_viem2.encodeAbiParameters)(V3_EXACT_OUT_INPUT_ABI, [
350
+ recipient,
351
+ amountOut,
352
+ maxAmountIn,
353
+ pathBytes,
354
+ true
355
+ // payerIsUser
356
+ ]);
431
357
  }
432
- function buildUniversalRouterExecuteArgsExactOut(currencyOut, path, amountOut, maxAmountIn, inputCurrency) {
433
- const commands = (0, import_viem2.encodePacked)(["uint8"], [V4_SWAP]);
358
+ function buildUniversalRouterExecuteArgs(recipient, path, amountIn, minAmountOut) {
359
+ const commands = (0, import_viem2.encodePacked)(["uint8"], [V3_SWAP_EXACT_IN]);
434
360
  const inputs = [
435
- buildV4SwapInputExactOut(
436
- currencyOut,
437
- path,
438
- amountOut,
439
- maxAmountIn,
440
- inputCurrency
441
- )
361
+ buildV3SwapInputExactIn(recipient, path, amountIn, minAmountOut)
442
362
  ];
443
363
  return { commands, inputs };
444
364
  }
445
- var _buildSwapFromQuoteWarned = false;
446
- function buildSwapFromQuote(params) {
447
- if (!_buildSwapFromQuoteWarned) {
448
- _buildSwapFromQuoteWarned = true;
449
- console.warn(
450
- "[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`."
451
- );
452
- }
453
- return buildUniversalRouterExecuteArgs(
454
- params.currencyIn,
455
- params.quote.path,
456
- params.amountIn,
457
- params.minAmountOut,
458
- params.currencyOut
459
- );
365
+ function buildUniversalRouterExecuteArgsExactOut(recipient, path, amountOut, maxAmountIn) {
366
+ const commands = (0, import_viem2.encodePacked)(["uint8"], [V3_SWAP_EXACT_OUT]);
367
+ const inputs = [
368
+ buildV3SwapInputExactOut(recipient, path, amountOut, maxAmountIn)
369
+ ];
370
+ return { commands, inputs };
460
371
  }
461
372
 
462
373
  // src/swap/simulate.ts
@@ -496,9 +407,14 @@ function buildSwapUserOp(params) {
496
407
  "buildSwapUserOp: minAmountOut must be >= gasFeeAmountOutput so the post-swap fee transfer cannot revert"
497
408
  );
498
409
  }
499
- if (params.swapPath.length === 0) {
410
+ if (params.swapPath.tokens.length < 2 || params.swapPath.fees.length < 1) {
500
411
  throw new Error(
501
- "buildSwapUserOp: swapPath must contain at least one PathKey"
412
+ "buildSwapUserOp: swapPath must contain at least one hop (>=2 tokens, >=1 fee)"
413
+ );
414
+ }
415
+ if (params.swapPath.tokens.length !== params.swapPath.fees.length + 1) {
416
+ throw new Error(
417
+ "buildSwapUserOp: swapPath.tokens.length must equal swapPath.fees.length + 1"
502
418
  );
503
419
  }
504
420
  const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
@@ -508,11 +424,10 @@ function buildSwapUserOp(params) {
508
424
  );
509
425
  }
510
426
  const { commands, inputs } = buildUniversalRouterExecuteArgs(
511
- params.inputTokenAddress,
427
+ params.userAddress,
512
428
  params.swapPath,
513
429
  params.amountIn,
514
- params.minAmountOut,
515
- params.outputTokenAddress
430
+ params.minAmountOut
516
431
  );
517
432
  const swapCallData = (0, import_viem3.encodeFunctionData)({
518
433
  abi: import_core8.universalRouterAbi,
@@ -562,9 +477,14 @@ function buildSwapUserOpExactOut(params) {
562
477
  "buildSwapUserOpExactOut: gasFeeAmountInput must be non-negative"
563
478
  );
564
479
  }
565
- if (params.swapPath.length === 0) {
480
+ if (params.swapPath.tokens.length < 2 || params.swapPath.fees.length < 1) {
566
481
  throw new Error(
567
- "buildSwapUserOpExactOut: swapPath must contain at least one PathKey"
482
+ "buildSwapUserOpExactOut: swapPath must contain at least one hop (>=2 tokens, >=1 fee)"
483
+ );
484
+ }
485
+ if (params.swapPath.tokens.length !== params.swapPath.fees.length + 1) {
486
+ throw new Error(
487
+ "buildSwapUserOpExactOut: swapPath.tokens.length must equal swapPath.fees.length + 1"
568
488
  );
569
489
  }
570
490
  const PERMIT2_EXPIRATION_MAX = 2n ** 48n - 1n;
@@ -574,11 +494,10 @@ function buildSwapUserOpExactOut(params) {
574
494
  );
575
495
  }
576
496
  const { commands, inputs } = buildUniversalRouterExecuteArgsExactOut(
577
- params.outputTokenAddress,
497
+ params.userAddress,
578
498
  params.swapPath,
579
499
  params.amountOut,
580
- params.maxAmountIn,
581
- params.inputTokenAddress
500
+ params.maxAmountIn
582
501
  );
583
502
  const swapCallData = (0, import_viem3.encodeFunctionData)({
584
503
  abi: import_core8.universalRouterAbi,
@@ -639,7 +558,7 @@ var TradingHandlers = class {
639
558
  // GET /quote
640
559
  // =========================================================================
641
560
  /**
642
- * Quote exact-input PT → USDT via Uniswap V4 on-chain Quoter.
561
+ * Quote exact-input PT → USDT via Uniswap V3 QuoterV2.
643
562
  *
644
563
  * Uses multicall to batch all candidate routes into a single RPC call.
645
564
  * Returns `quoteError: "QUOTE_UNAVAILABLE"` when no pool/path exists
@@ -779,7 +698,7 @@ var TradingHandlers = class {
779
698
  request.chainId,
780
699
  outputTokenAddress
781
700
  ).catch(() => 0n);
782
- const hops = quoteResult.bestRoute.path.length;
701
+ const hops = quoteResult.bestRoute.path.fees.length;
783
702
  const slippageBps = request.slippageBps ?? (hops > 1 ? 100 : 50);
784
703
  const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
785
704
  const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
@@ -832,11 +751,11 @@ var TradingHandlers = class {
832
751
  };
833
752
  }
834
753
  // =========================================================================
835
- // GET /quote/exact-out — V4 exact-output quote
754
+ // GET /quote/exact-out — V3 exact-output quote
836
755
  // =========================================================================
837
756
  /**
838
757
  * Quote the input required to receive `request.amount` of the output
839
- * token via Uniswap V4. Input-side operator fee is auto-quoted, so
758
+ * token via Uniswap V3 QuoterV2. Input-side operator fee is auto-quoted, so
840
759
  * `inputGross = estimatedInputAmount + feeAmountInput` is the total
841
760
  * the user must hold.
842
761
  *
@@ -897,10 +816,10 @@ var TradingHandlers = class {
897
816
  }
898
817
  }
899
818
  // =========================================================================
900
- // POST /swap/exact-out — V4 exact-output swap UserOp
819
+ // POST /swap/exact-out — V3 exact-output swap UserOp
901
820
  // =========================================================================
902
821
  /**
903
- * Build a V4 exact-output swap UserOp.
822
+ * Build a V3 exact-output swap UserOp.
904
823
  *
905
824
  * Quotes the best exact-output route, applies slippage as a CEILING on
906
825
  * `maxAmountIn` (so the cap is never silently tightened by floor
@@ -909,7 +828,7 @@ var TradingHandlers = class {
909
828
  * inputToken.transfer(feeRecipient, gasFeeAmountInput) [if > 0]
910
829
  * → input.approve(Permit2, maxAmountIn)
911
830
  * → Permit2.approve(router, maxAmountIn)
912
- * → UniversalRouter.execute (V4 SWAP_EXACT_OUT)
831
+ * → UniversalRouter.execute (V3_SWAP_EXACT_OUT)
913
832
  *
914
833
  * Operator fee is INPUT-token-side (charged before swap) so the user
915
834
  * receives exactly `request.amount` of output.
@@ -979,7 +898,7 @@ var TradingHandlers = class {
979
898
  request.chainId,
980
899
  inputTokenAddress
981
900
  ).catch(() => 0n);
982
- const hops = quoteResult.bestRoute.path.length;
901
+ const hops = quoteResult.bestRoute.path.fees.length;
983
902
  const slippageBps = request.slippageBps ?? (hops > 1 ? 100 : 50);
984
903
  const estimatedInputAmount = quoteResult.bestRoute.amountIn;
985
904
  const slippageNumerator = estimatedInputAmount * BigInt(1e4 + slippageBps);
@@ -1302,7 +1221,7 @@ async function swapDirect(params) {
1302
1221
  `swapDirect: no swap path found from ${params.inputTokenAddress} to ${params.outputTokenAddress} (cause: ${cause})`
1303
1222
  );
1304
1223
  }
1305
- const hops = quoteResult.bestRoute.path.length;
1224
+ const hops = quoteResult.bestRoute.path.fees.length;
1306
1225
  const slippageBps = params.slippageBps ?? (hops > 1 ? 100 : 50);
1307
1226
  const estimatedOutputAmount = quoteResult.bestRoute.amountOut;
1308
1227
  const minAmountOut = estimatedOutputAmount * BigInt(1e4 - slippageBps) / 10000n;
@@ -1417,7 +1336,7 @@ async function swapDirectExactOut(params) {
1417
1336
  `swapDirectExactOut: no swap path found from ${params.inputTokenAddress} to ${params.outputTokenAddress} (cause: ${cause})`
1418
1337
  );
1419
1338
  }
1420
- const hops = quoteResult.bestRoute.path.length;
1339
+ const hops = quoteResult.bestRoute.path.fees.length;
1421
1340
  const slippageBps = params.slippageBps ?? (hops > 1 ? 100 : 50);
1422
1341
  const estimatedInputAmount = quoteResult.bestRoute.amountIn;
1423
1342
  const slippageNumerator = estimatedInputAmount * BigInt(1e4 + slippageBps);
@@ -1604,19 +1523,18 @@ async function perpDepositDirect(params) {
1604
1523
  // Annotate the CommonJS export names for ESM import in node:
1605
1524
  0 && (module.exports = {
1606
1525
  PAFI_SUBGRAPH_URL,
1607
- SWAP_EXACT_OUT,
1608
- SWAP_EXACT_OUT_SINGLE,
1609
1526
  TradingHandlers,
1527
+ V3_SWAP_EXACT_IN,
1528
+ V3_SWAP_EXACT_OUT,
1610
1529
  buildAllPaths,
1611
1530
  buildErc20ApprovalCalldata,
1612
1531
  buildPermit2ApprovalCalldata,
1613
- buildSwapFromQuote,
1614
1532
  buildSwapUserOp,
1615
1533
  buildSwapUserOpExactOut,
1616
1534
  buildUniversalRouterExecuteArgs,
1617
1535
  buildUniversalRouterExecuteArgsExactOut,
1618
- buildV4SwapInput,
1619
- buildV4SwapInputExactOut,
1536
+ buildV3SwapInputExactIn,
1537
+ buildV3SwapInputExactOut,
1620
1538
  checkAllowance,
1621
1539
  combineRoutes,
1622
1540
  fetchPafiPools,