@pafi-dev/trading 0.4.3 → 0.6.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/README.md +111 -50
- package/dist/index.cjs +179 -261
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -128
- package/dist/index.d.ts +105 -128
- package/dist/index.js +176 -251
- package/dist/index.js.map +1 -1
- package/package.json +13 -2
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,
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
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
|
|
39
|
-
const
|
|
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 ===
|
|
43
|
-
nextToken = pool.
|
|
44
|
-
} else if (curr ===
|
|
45
|
-
nextToken = pool.
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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 {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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:
|
|
73
|
+
abi: v3QuoterV2Abi,
|
|
73
74
|
functionName: "quoteExactInput",
|
|
74
|
-
args: [
|
|
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,
|
|
79
|
-
const
|
|
80
|
+
async function quoteExactInputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
|
|
81
|
+
const result = await client.readContract({
|
|
80
82
|
address: quoterAddress,
|
|
81
|
-
abi:
|
|
83
|
+
abi: v3QuoterV2Abi,
|
|
82
84
|
functionName: "quoteExactInputSingle",
|
|
83
85
|
args: [
|
|
84
86
|
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
exactAmount,
|
|
88
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
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 ??
|
|
139
|
+
const quoter = quoterAddress ?? QUOTER_V2_ADDRESSES[chainId];
|
|
134
140
|
if (!quoter) {
|
|
135
|
-
throw new Error(`No
|
|
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,
|
|
149
|
+
return quoteBestRoute(client, quoter, paths, exactAmount);
|
|
144
150
|
}
|
|
145
|
-
async function quoteExactOutput(client, quoterAddress,
|
|
146
|
-
const
|
|
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:
|
|
155
|
+
abi: v3QuoterV2Abi,
|
|
149
156
|
functionName: "quoteExactOutput",
|
|
150
|
-
args: [
|
|
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,
|
|
155
|
-
const
|
|
162
|
+
async function quoteExactOutputSingle(client, quoterAddress, tokenIn, tokenOut, fee, exactAmount, sqrtPriceLimitX96 = 0n) {
|
|
163
|
+
const result = await client.readContract({
|
|
156
164
|
address: quoterAddress,
|
|
157
|
-
abi:
|
|
165
|
+
abi: v3QuoterV2Abi,
|
|
158
166
|
functionName: "quoteExactOutputSingle",
|
|
159
167
|
args: [
|
|
160
168
|
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
exactAmount,
|
|
164
|
-
|
|
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,
|
|
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:
|
|
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
|
|
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
|
|
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 ??
|
|
221
|
+
const quoter = quoterAddress ?? QUOTER_V2_ADDRESSES[chainId];
|
|
210
222
|
if (!quoter) {
|
|
211
|
-
throw new Error(`No
|
|
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,
|
|
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
|
|
230
|
+
`No exact-output paths found from ${tokenIn} to ${tokenOut}`
|
|
219
231
|
);
|
|
220
232
|
}
|
|
221
|
-
return quoteBestRouteExactOut(client, quoter,
|
|
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
|
-
|
|
254
|
-
var
|
|
255
|
-
var
|
|
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
|
|
261
|
-
{ name: "
|
|
262
|
-
{ name: "
|
|
263
|
-
{ name: "
|
|
264
|
-
{ name: "
|
|
265
|
-
{ name: "
|
|
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
|
|
268
|
-
{ name: "
|
|
269
|
-
{
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
|
304
|
+
function buildV3SwapInputExactOut(recipient, path, amountOut, maxAmountIn) {
|
|
339
305
|
if (amountOut <= 0n || amountOut > UINT128_MAX) {
|
|
340
306
|
throw new Error(
|
|
341
|
-
`
|
|
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
|
-
`
|
|
312
|
+
`buildV3SwapInputExactOut: maxAmountIn (${maxAmountIn}) must be in (0, 2^128-1]`
|
|
347
313
|
);
|
|
348
314
|
}
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
393
|
-
const commands = encodePacked(["uint8"], [
|
|
325
|
+
function buildUniversalRouterExecuteArgs(recipient, path, amountIn, minAmountOut) {
|
|
326
|
+
const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
|
|
394
327
|
const inputs = [
|
|
395
|
-
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 —
|
|
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
|
|
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 —
|
|
793
|
+
// POST /swap/exact-out — V3 exact-output swap UserOp
|
|
868
794
|
// =========================================================================
|
|
869
795
|
/**
|
|
870
|
-
* Build a
|
|
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 (
|
|
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
|
-
|
|
1612
|
-
|
|
1536
|
+
buildV3SwapInputExactIn,
|
|
1537
|
+
buildV3SwapInputExactOut,
|
|
1613
1538
|
checkAllowance,
|
|
1614
1539
|
combineRoutes,
|
|
1615
1540
|
fetchPafiPools,
|