@panoptic-eng/sdk 1.0.49 → 1.0.51
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/cow/index.js.map +1 -1
- package/dist/{cow-DMX9ebPk.js → cow-CSSKPknb.js} +1 -1
- package/dist/deployments.d.ts +12 -4
- package/dist/deployments.d.ts.map +1 -1
- package/dist/deployments.js +78 -10
- package/dist/deployments.js.map +1 -1
- package/dist/{greeks-9aTE-DwO.js → greeks-CNi1-cnp.js} +39 -16
- package/dist/index.d.ts +518 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +724 -91
- package/dist/index.js.map +1 -1
- package/dist/{irm-mySi_YMF.js → irm-BJaJSUjF.js} +80 -12
- package/dist/panoptic/v2/greeks/index.d.ts +12 -1
- package/dist/panoptic/v2/greeks/index.d.ts.map +1 -1
- package/dist/panoptic/v2/greeks/index.js +39 -16
- package/dist/panoptic/v2/greeks/index.js.map +1 -1
- package/dist/panoptic/v2/index.d.ts +1336 -74
- package/dist/panoptic/v2/index.d.ts.map +1 -1
- package/dist/panoptic/v2/index.js +20322 -18660
- package/dist/panoptic/v2/index.js.map +1 -1
- package/dist/panoptic/v2/react-public.d.ts +1518 -240
- package/dist/panoptic/v2/react-public.d.ts.map +1 -1
- package/dist/panoptic/v2/react-public.js +11518 -9319
- package/dist/panoptic/v2/react-public.js.map +1 -1
- package/dist/{rates-CDLIUGDz.js → rates-COAJpKRd.js} +612 -73
- package/dist/{router-45ulPnWe.js → router-B-I1eACz.js} +5 -43
- package/dist/router-BSS7HKd2.js +837 -0
- package/dist/{transactionFees-DUm6ilXB.js → transactionFees-DE-Whxab.js} +24 -8
- package/dist/uniswap/index.d.ts +465 -215
- package/dist/uniswap/index.d.ts.map +1 -1
- package/dist/uniswap/index.js +1799 -1076
- package/dist/uniswap/index.js.map +1 -1
- package/dist/{v2-DshssxX4.js → v2-zF6b01PI.js} +2488 -1596
- package/dist/vault-transaction-fees.d.ts +9 -4
- package/dist/vault-transaction-fees.d.ts.map +1 -1
- package/dist/vault-transaction-fees.js +24 -8
- package/dist/vault-transaction-fees.js.map +1 -1
- package/dist/{writes-2reV_omF.js → writes-D1zN0v9x.js} +4 -3
- package/dist/zodiac/index.d.ts +51 -7
- package/dist/zodiac/index.d.ts.map +1 -1
- package/dist/zodiac/index.js +154 -2
- package/dist/zodiac/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/router-B2jY9Nyb.js +0 -312
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import { PanopticError, getBlockMeta, getPoolMetadata, submitWrite } from "./rates-COAJpKRd.js";
|
|
2
|
+
import { tickToSqrtPriceX96 } from "./greeks-CNi1-cnp.js";
|
|
3
|
+
import { decodeAllLegs } from "./writes-D1zN0v9x.js";
|
|
4
|
+
import { BaseError, ContractFunctionRevertedError, encodeAbiParameters, encodeFunctionData, encodePacked, isAddressEqual, zeroAddress } from "viem";
|
|
5
|
+
|
|
6
|
+
//#region src/uniswap/lpDeposit.ts
|
|
7
|
+
const Q96 = 1n << 96n;
|
|
8
|
+
const ceilDiv = (n, d) => (n + d - 1n) / d;
|
|
9
|
+
/** Uniswap mint principal in raw token units, rounded up per leg, plus 5% headroom. */
|
|
10
|
+
function getLpDepositAmounts(ranges, sqrtPriceX96) {
|
|
11
|
+
return getLpDepositBreakdown(ranges, sqrtPriceX96).total;
|
|
12
|
+
}
|
|
13
|
+
/** Principal and separately identified funding headroom, in native token units. */
|
|
14
|
+
function getLpDepositBreakdown(ranges, sqrtPriceX96) {
|
|
15
|
+
if (sqrtPriceX96 <= 0n) throw new Error("Invalid pool price");
|
|
16
|
+
let amount0 = 0n;
|
|
17
|
+
let amount1 = 0n;
|
|
18
|
+
for (const { tickLower, tickUpper, liquidity } of ranges) {
|
|
19
|
+
if (!Number.isInteger(tickLower) || !Number.isInteger(tickUpper) || tickLower < -887272 || tickUpper > 887272 || tickLower >= tickUpper || liquidity < 0n) throw new Error("Invalid LP range");
|
|
20
|
+
const lower = tickToSqrtPriceX96(BigInt(tickLower));
|
|
21
|
+
const upper = tickToSqrtPriceX96(BigInt(tickUpper));
|
|
22
|
+
const price = sqrtPriceX96 < lower ? lower : sqrtPriceX96 > upper ? upper : sqrtPriceX96;
|
|
23
|
+
amount0 += ceilDiv(liquidity * Q96 * (upper - price), upper * price);
|
|
24
|
+
amount1 += ceilDiv(liquidity * (price - lower), Q96);
|
|
25
|
+
}
|
|
26
|
+
const buffer = {
|
|
27
|
+
amount0: ceilDiv(amount0 * 5n, 100n),
|
|
28
|
+
amount1: ceilDiv(amount1 * 5n, 100n)
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
principal: {
|
|
32
|
+
amount0,
|
|
33
|
+
amount1
|
|
34
|
+
},
|
|
35
|
+
buffer,
|
|
36
|
+
total: {
|
|
37
|
+
amount0: amount0 + buffer.amount0,
|
|
38
|
+
amount1: amount1 + buffer.amount1
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Full deployed liquidity value; never a leveraged protocol margin estimate. */
|
|
43
|
+
function getLpPositionFunding(params) {
|
|
44
|
+
const { tokenId, positionSize, tickSpacing, sqrtPriceX96, valuationSqrtPriceX96, quoteTokenIndex } = params;
|
|
45
|
+
if (positionSize < 0n || tickSpacing <= 0n || valuationSqrtPriceX96 <= 0n) throw new Error("Invalid LP funding input");
|
|
46
|
+
const legs = decodeAllLegs(tokenId);
|
|
47
|
+
if (legs.length === 0 || legs.some((leg) => leg.isLong || leg.width === 0n)) throw new Error("AMM Liquidity requires short liquidity legs without loans or credits");
|
|
48
|
+
const ranges = legs.map((leg) => {
|
|
49
|
+
const width = leg.width * tickSpacing;
|
|
50
|
+
const tickLower = leg.strike - width / 2n;
|
|
51
|
+
const tickUpper = leg.strike + (width + 1n) / 2n;
|
|
52
|
+
const lower = tickToSqrtPriceX96(tickLower);
|
|
53
|
+
const upper = tickToSqrtPriceX96(tickUpper);
|
|
54
|
+
const amount = positionSize * leg.optionRatio;
|
|
55
|
+
const liquidity = leg.asset === 0n ? amount * (lower * upper / Q96) / (upper - lower) : amount * Q96 / (upper - lower);
|
|
56
|
+
return {
|
|
57
|
+
tickLower: Number(tickLower),
|
|
58
|
+
tickUpper: Number(tickUpper),
|
|
59
|
+
liquidity
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const breakdown = getLpDepositBreakdown(ranges, sqrtPriceX96);
|
|
63
|
+
const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
|
|
64
|
+
const value = ({ amount0, amount1 }) => quoteTokenIndex === 0 ? amount0 + ceilDiv(amount1 * Q96 * Q96, priceSquared) : amount1 + ceilDiv(amount0 * priceSquared, Q96 * Q96);
|
|
65
|
+
const principalInQuote = value(breakdown.principal);
|
|
66
|
+
const totalInQuote = value(breakdown.total);
|
|
67
|
+
return {
|
|
68
|
+
...breakdown,
|
|
69
|
+
principalInQuote,
|
|
70
|
+
bufferInQuote: totalInQuote - principalInQuote,
|
|
71
|
+
totalInQuote
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Remaining deposit in the requested token mix, rounded up to cover the shortfall. */
|
|
75
|
+
function getLpFundingDeposit({ funding, availableInQuote, minimumInQuote = 0n, quoteTokenIndex, valuationSqrtPriceX96, quotePercent }) {
|
|
76
|
+
if (valuationSqrtPriceX96 <= 0n || quotePercent !== void 0 && (quotePercent < 0n || quotePercent > 100n)) throw new Error("Invalid LP funding split");
|
|
77
|
+
const requiredInQuote = minimumInQuote > funding.totalInQuote ? minimumInQuote : funding.totalInQuote;
|
|
78
|
+
const shortfallInQuote = requiredInQuote > availableInQuote ? requiredInQuote - availableInQuote : 0n;
|
|
79
|
+
const defaultQuotePercent = funding.totalInQuote > 0n ? (quoteTokenIndex === 0 ? funding.total.amount0 : funding.total.amount1) * 100n / funding.totalInQuote : 100n;
|
|
80
|
+
if (quotePercent === void 0) return {
|
|
81
|
+
requiredInQuote,
|
|
82
|
+
shortfallInQuote,
|
|
83
|
+
defaultQuotePercent,
|
|
84
|
+
amount0: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount0 * shortfallInQuote, funding.totalInQuote) : 0n,
|
|
85
|
+
amount1: funding.totalInQuote > 0n ? ceilDiv(funding.total.amount1 * shortfallInQuote, funding.totalInQuote) : 0n
|
|
86
|
+
};
|
|
87
|
+
const quoteAmount = shortfallInQuote * quotePercent / 100n;
|
|
88
|
+
const assetValue = shortfallInQuote - quoteAmount;
|
|
89
|
+
const priceSquared = valuationSqrtPriceX96 * valuationSqrtPriceX96;
|
|
90
|
+
return {
|
|
91
|
+
requiredInQuote,
|
|
92
|
+
shortfallInQuote,
|
|
93
|
+
defaultQuotePercent,
|
|
94
|
+
amount0: quoteTokenIndex === 0 ? quoteAmount : ceilDiv(assetValue * Q96 * Q96, priceSquared),
|
|
95
|
+
amount1: quoteTokenIndex === 1 ? quoteAmount : ceilDiv(assetValue * priceSquared, Q96 * Q96)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Largest raw size funded by available collateral, including native-token rounding. */
|
|
99
|
+
function getMaxLpPositionSize(params) {
|
|
100
|
+
if (params.minimumRequirement && (params.minimumRequirement.referenceSize <= 0n || params.minimumRequirement.requiredInQuote < 0n)) throw new Error("Invalid reference requirement");
|
|
101
|
+
if (params.availableInQuote <= 0n) return 0n;
|
|
102
|
+
let low = 0n;
|
|
103
|
+
let high = (1n << 128n) - 1n;
|
|
104
|
+
while (low < high) {
|
|
105
|
+
const mid = (low + high + 1n) / 2n;
|
|
106
|
+
const funding = getLpPositionFunding({
|
|
107
|
+
...params,
|
|
108
|
+
positionSize: mid
|
|
109
|
+
});
|
|
110
|
+
const minimum = params.minimumRequirement ? ceilDiv(params.minimumRequirement.requiredInQuote * mid, params.minimumRequirement.referenceSize) : 0n;
|
|
111
|
+
if (funding.totalInQuote <= params.availableInQuote && minimum <= params.availableInQuote) low = mid;
|
|
112
|
+
else high = mid - 1n;
|
|
113
|
+
}
|
|
114
|
+
return low;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/uniswap/v4/router/errors.ts
|
|
119
|
+
/**
|
|
120
|
+
* The chain has no configured Uniswap v4 addresses and none were supplied via
|
|
121
|
+
* overrides.
|
|
122
|
+
*/
|
|
123
|
+
var UnsupportedChainError = class extends PanopticError {
|
|
124
|
+
name = "UnsupportedChainError";
|
|
125
|
+
constructor(chainId, cause) {
|
|
126
|
+
super(`Uniswap v4 router not configured for chain ${chainId}`, cause);
|
|
127
|
+
this.chainId = chainId;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* `tokenIn` is neither `currency0` nor `currency1` of the resolved pool.
|
|
132
|
+
*/
|
|
133
|
+
var InvalidSwapTokenError = class extends PanopticError {
|
|
134
|
+
name = "InvalidSwapTokenError";
|
|
135
|
+
constructor(token, currency0, currency1, cause) {
|
|
136
|
+
super(`Token ${token} is not part of the pool (currency0=${currency0}, currency1=${currency1})`, cause);
|
|
137
|
+
this.token = token;
|
|
138
|
+
this.currency0 = currency0;
|
|
139
|
+
this.currency1 = currency1;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* An amount exceeds the uint128 range required by the v4 swap encoding.
|
|
144
|
+
*/
|
|
145
|
+
var AmountExceedsUint128Error = class extends PanopticError {
|
|
146
|
+
name = "AmountExceedsUint128Error";
|
|
147
|
+
constructor(amount, cause) {
|
|
148
|
+
super(`Amount ${amount} exceeds uint128 maximum`, cause);
|
|
149
|
+
this.amount = amount;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* A native-ETH swap needs a trailing Universal Router SWEEP to deliver the ETH
|
|
154
|
+
* output (or refund the input overpay), but no `recipient` was supplied.
|
|
155
|
+
*/
|
|
156
|
+
var MissingSweepRecipientError = class extends PanopticError {
|
|
157
|
+
name = "MissingSweepRecipientError";
|
|
158
|
+
constructor(cause) {
|
|
159
|
+
super("A recipient is required to sweep native ETH back to the user", cause);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* The V4Quoter is not available for the chain (no fallback in v1).
|
|
164
|
+
*/
|
|
165
|
+
var QuoterUnavailableError = class extends PanopticError {
|
|
166
|
+
name = "QuoterUnavailableError";
|
|
167
|
+
constructor(chainId, cause) {
|
|
168
|
+
super(`V4Quoter unavailable for chain ${chainId}`, cause);
|
|
169
|
+
this.chainId = chainId;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/uniswap/v3/addresses.ts
|
|
175
|
+
/**
|
|
176
|
+
* Verified Uniswap v3 QuoterV2 addresses keyed by chainId. Add a chain only
|
|
177
|
+
* after verifying against the canonical Uniswap v3 deployment listing.
|
|
178
|
+
*/
|
|
179
|
+
const UNISWAP_V3_ADDRESSES = {
|
|
180
|
+
1: {
|
|
181
|
+
quoterV2: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e",
|
|
182
|
+
nonfungiblePositionManager: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
|
183
|
+
},
|
|
184
|
+
4663: {
|
|
185
|
+
quoterV2: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
|
|
186
|
+
nonfungiblePositionManager: "0x73991a25c818bf1f1128deaab1492d45638de0d3"
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Resolve Uniswap v3 addresses for a chain, applying optional overrides.
|
|
191
|
+
*
|
|
192
|
+
* @throws {UnsupportedChainError} when the chain is not listed and overrides do
|
|
193
|
+
* not supply every required address.
|
|
194
|
+
*/
|
|
195
|
+
function getUniswapV3Addresses(chainId, overrides) {
|
|
196
|
+
const id = Number(chainId);
|
|
197
|
+
const base = UNISWAP_V3_ADDRESSES[id];
|
|
198
|
+
const merged = {
|
|
199
|
+
...base,
|
|
200
|
+
...overrides
|
|
201
|
+
};
|
|
202
|
+
if (!merged.quoterV2 || !merged.nonfungiblePositionManager) throw new UnsupportedChainError(BigInt(id));
|
|
203
|
+
return merged;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/uniswap/v4/abis/universalRouter.ts
|
|
208
|
+
/**
|
|
209
|
+
* Minimal Universal Router ABI (only the `execute` overload we use).
|
|
210
|
+
* @module uniswap/v4/abis/universalRouter
|
|
211
|
+
*/
|
|
212
|
+
const universalRouterAbi = [{
|
|
213
|
+
type: "function",
|
|
214
|
+
name: "execute",
|
|
215
|
+
stateMutability: "payable",
|
|
216
|
+
inputs: [
|
|
217
|
+
{
|
|
218
|
+
name: "commands",
|
|
219
|
+
type: "bytes"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "inputs",
|
|
223
|
+
type: "bytes[]"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "deadline",
|
|
227
|
+
type: "uint256"
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
outputs: []
|
|
231
|
+
}];
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/uniswap/v3/router/encodeSwap.ts
|
|
235
|
+
/** Universal Router command byte for a v3 exact-in swap. */
|
|
236
|
+
const V3_SWAP_EXACT_IN = 0;
|
|
237
|
+
/** Universal Router command byte for a v3 exact-out swap. */
|
|
238
|
+
const V3_SWAP_EXACT_OUT = 1;
|
|
239
|
+
/**
|
|
240
|
+
* Universal Router recipient sentinel: the router maps `address(1)` to
|
|
241
|
+
* `msg.sender` (the `execute` caller). Uniswap `Constants.MSG_SENDER`.
|
|
242
|
+
*/
|
|
243
|
+
const MSG_SENDER = "0x0000000000000000000000000000000000000001";
|
|
244
|
+
const UINT128_MAX$1 = (1n << 128n) - 1n;
|
|
245
|
+
const v3ExactInInputAbi = [
|
|
246
|
+
{
|
|
247
|
+
name: "recipient",
|
|
248
|
+
type: "address"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "amountIn",
|
|
252
|
+
type: "uint256"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "amountOutMinimum",
|
|
256
|
+
type: "uint256"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "path",
|
|
260
|
+
type: "bytes"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "payerIsUser",
|
|
264
|
+
type: "bool"
|
|
265
|
+
}
|
|
266
|
+
];
|
|
267
|
+
function assertUint128(amount) {
|
|
268
|
+
if (amount < 0n || amount > UINT128_MAX$1) throw new AmountExceedsUint128Error(amount);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Encode the packed v3 single-hop path `tokenIn ++ fee(uint24) ++ tokenOut`.
|
|
272
|
+
*/
|
|
273
|
+
function encodeV3Path(tokenIn, fee, tokenOut) {
|
|
274
|
+
if (fee < 0n || fee > 0xffffffn) throw new PanopticError(`v3 fee ${fee} exceeds uint24`);
|
|
275
|
+
return encodePacked([
|
|
276
|
+
"address",
|
|
277
|
+
"uint24",
|
|
278
|
+
"address"
|
|
279
|
+
], [
|
|
280
|
+
tokenIn,
|
|
281
|
+
Number(fee),
|
|
282
|
+
tokenOut
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Build the typed `execute(...)` args for an exact-in single-hop v3 swap whose
|
|
287
|
+
* output is delivered to the `execute` caller (recipient = MSG_SENDER).
|
|
288
|
+
*/
|
|
289
|
+
function buildV3SwapExecuteArgs(args) {
|
|
290
|
+
const { tokenIn, tokenOut, fee, amountIn, amountOutMinimum, deadline } = args;
|
|
291
|
+
if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-in router builder");
|
|
292
|
+
assertUint128(amountIn);
|
|
293
|
+
assertUint128(amountOutMinimum);
|
|
294
|
+
const path = encodeV3Path(tokenIn, fee, tokenOut);
|
|
295
|
+
const input = encodeAbiParameters(v3ExactInInputAbi, [
|
|
296
|
+
MSG_SENDER,
|
|
297
|
+
amountIn,
|
|
298
|
+
amountOutMinimum,
|
|
299
|
+
path,
|
|
300
|
+
true
|
|
301
|
+
]);
|
|
302
|
+
const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_IN]);
|
|
303
|
+
return {
|
|
304
|
+
args: [
|
|
305
|
+
commands,
|
|
306
|
+
[input],
|
|
307
|
+
deadline
|
|
308
|
+
],
|
|
309
|
+
value: 0n
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Build the `execute(commands, inputs, deadline)` calldata for an exact-in
|
|
314
|
+
* single-hop v3 swap.
|
|
315
|
+
*/
|
|
316
|
+
function buildV3SwapExecuteCalldata(args) {
|
|
317
|
+
const { args: executeArgs, value } = buildV3SwapExecuteArgs(args);
|
|
318
|
+
const data = encodeFunctionData({
|
|
319
|
+
abi: universalRouterAbi,
|
|
320
|
+
functionName: "execute",
|
|
321
|
+
args: executeArgs
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
data,
|
|
325
|
+
value
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
const v3ExactOutInputAbi = [
|
|
329
|
+
{
|
|
330
|
+
name: "recipient",
|
|
331
|
+
type: "address"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: "amountOut",
|
|
335
|
+
type: "uint256"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "amountInMaximum",
|
|
339
|
+
type: "uint256"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "path",
|
|
343
|
+
type: "bytes"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "payerIsUser",
|
|
347
|
+
type: "bool"
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
/**
|
|
351
|
+
* Build the typed `execute(...)` args for an exact-out single-hop v3 swap.
|
|
352
|
+
* The v3 exact-out path is reversed: `tokenOut ++ fee ++ tokenIn`.
|
|
353
|
+
*/
|
|
354
|
+
function buildV3ExactOutSwapExecuteArgs(args) {
|
|
355
|
+
const { tokenIn, tokenOut, fee, amountOut, amountInMaximum, deadline } = args;
|
|
356
|
+
if (tokenIn === zeroAddress || tokenOut === zeroAddress) throw new PanopticError("native ETH is not supported by the v3 exact-out router builder");
|
|
357
|
+
assertUint128(amountOut);
|
|
358
|
+
assertUint128(amountInMaximum);
|
|
359
|
+
const path = encodeV3Path(tokenOut, fee, tokenIn);
|
|
360
|
+
const input = encodeAbiParameters(v3ExactOutInputAbi, [
|
|
361
|
+
MSG_SENDER,
|
|
362
|
+
amountOut,
|
|
363
|
+
amountInMaximum,
|
|
364
|
+
path,
|
|
365
|
+
true
|
|
366
|
+
]);
|
|
367
|
+
const commands = encodePacked(["uint8"], [V3_SWAP_EXACT_OUT]);
|
|
368
|
+
return {
|
|
369
|
+
args: [
|
|
370
|
+
commands,
|
|
371
|
+
[input],
|
|
372
|
+
deadline
|
|
373
|
+
],
|
|
374
|
+
value: 0n
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function buildV3ExactOutSwapExecuteCalldata(args) {
|
|
378
|
+
const { args: executeArgs, value } = buildV3ExactOutSwapExecuteArgs(args);
|
|
379
|
+
const data = encodeFunctionData({
|
|
380
|
+
abi: universalRouterAbi,
|
|
381
|
+
functionName: "execute",
|
|
382
|
+
args: executeArgs
|
|
383
|
+
});
|
|
384
|
+
return {
|
|
385
|
+
data,
|
|
386
|
+
value
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/uniswap/v3/abis/quoterV2.ts
|
|
392
|
+
/**
|
|
393
|
+
* Minimal Uniswap v3 QuoterV2 ABI.
|
|
394
|
+
*
|
|
395
|
+
* QuoterV2 is revert/staticcall-based (nonpayable but intended for `eth_call` /
|
|
396
|
+
* `simulateContract`), so it must be simulated, not read.
|
|
397
|
+
* @module uniswap/v3/abis/quoterV2
|
|
398
|
+
*/
|
|
399
|
+
const quoterV2Abi = [{
|
|
400
|
+
type: "function",
|
|
401
|
+
name: "quoteExactInputSingle",
|
|
402
|
+
stateMutability: "nonpayable",
|
|
403
|
+
inputs: [{
|
|
404
|
+
name: "params",
|
|
405
|
+
type: "tuple",
|
|
406
|
+
components: [
|
|
407
|
+
{
|
|
408
|
+
name: "tokenIn",
|
|
409
|
+
type: "address"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "tokenOut",
|
|
413
|
+
type: "address"
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "amountIn",
|
|
417
|
+
type: "uint256"
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: "fee",
|
|
421
|
+
type: "uint24"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: "sqrtPriceLimitX96",
|
|
425
|
+
type: "uint160"
|
|
426
|
+
}
|
|
427
|
+
]
|
|
428
|
+
}],
|
|
429
|
+
outputs: [
|
|
430
|
+
{
|
|
431
|
+
name: "amountOut",
|
|
432
|
+
type: "uint256"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "sqrtPriceX96After",
|
|
436
|
+
type: "uint160"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "initializedTicksCrossed",
|
|
440
|
+
type: "uint32"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: "gasEstimate",
|
|
444
|
+
type: "uint256"
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
}, {
|
|
448
|
+
type: "function",
|
|
449
|
+
name: "quoteExactOutputSingle",
|
|
450
|
+
stateMutability: "nonpayable",
|
|
451
|
+
inputs: [{
|
|
452
|
+
name: "params",
|
|
453
|
+
type: "tuple",
|
|
454
|
+
components: [
|
|
455
|
+
{
|
|
456
|
+
name: "tokenIn",
|
|
457
|
+
type: "address"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
name: "tokenOut",
|
|
461
|
+
type: "address"
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "amount",
|
|
465
|
+
type: "uint256"
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
name: "fee",
|
|
469
|
+
type: "uint24"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
name: "sqrtPriceLimitX96",
|
|
473
|
+
type: "uint160"
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
}],
|
|
477
|
+
outputs: [
|
|
478
|
+
{
|
|
479
|
+
name: "amountIn",
|
|
480
|
+
type: "uint256"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: "sqrtPriceX96After",
|
|
484
|
+
type: "uint160"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
name: "initializedTicksCrossed",
|
|
488
|
+
type: "uint32"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "gasEstimate",
|
|
492
|
+
type: "uint256"
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
}];
|
|
496
|
+
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/uniswap/v3/router/quote.ts
|
|
499
|
+
const BPS_DENOMINATOR = 10000n;
|
|
500
|
+
const UINT128_MAX = (1n << 128n) - 1n;
|
|
501
|
+
/**
|
|
502
|
+
* Quote an exact-in v3 swap. Returns `null` when the quote reverts (e.g. the
|
|
503
|
+
* pool does not exist / has no liquidity) so callers can skip that pool when
|
|
504
|
+
* ranking venues, rather than aborting the whole cycle.
|
|
505
|
+
*/
|
|
506
|
+
async function quoteV3ExactIn(params) {
|
|
507
|
+
const { client, chainId, tokenIn, tokenOut, fee, amountIn, slippageBps, blockNumber } = params;
|
|
508
|
+
if (amountIn < 0n || amountIn > UINT128_MAX) throw new PanopticError(`amountIn ${amountIn} exceeds uint128 maximum`);
|
|
509
|
+
assertSlippageBps(slippageBps);
|
|
510
|
+
const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
|
|
511
|
+
try {
|
|
512
|
+
const { result } = await client.simulateContract({
|
|
513
|
+
address: quoterV2,
|
|
514
|
+
abi: quoterV2Abi,
|
|
515
|
+
functionName: "quoteExactInputSingle",
|
|
516
|
+
blockNumber,
|
|
517
|
+
args: [{
|
|
518
|
+
tokenIn,
|
|
519
|
+
tokenOut,
|
|
520
|
+
amountIn,
|
|
521
|
+
fee: Number(fee),
|
|
522
|
+
sqrtPriceLimitX96: 0n
|
|
523
|
+
}]
|
|
524
|
+
});
|
|
525
|
+
const [amountOut, , , gasEstimate] = result;
|
|
526
|
+
const amountOutMinimum = amountOut * (BPS_DENOMINATOR - slippageBps) / BPS_DENOMINATOR;
|
|
527
|
+
return {
|
|
528
|
+
amountOut,
|
|
529
|
+
amountOutMinimum,
|
|
530
|
+
gasEstimate
|
|
531
|
+
};
|
|
532
|
+
} catch (err) {
|
|
533
|
+
if (isRevert(err)) return null;
|
|
534
|
+
throw err;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Quote an exact-out v3 swap. Returns `null` on revert (no pool / no liquidity).
|
|
539
|
+
*/
|
|
540
|
+
async function quoteV3ExactOut(params) {
|
|
541
|
+
const { client, chainId, tokenIn, tokenOut, fee, amountOut, slippageBps, blockNumber } = params;
|
|
542
|
+
if (amountOut < 0n || amountOut > UINT128_MAX) throw new PanopticError(`amountOut ${amountOut} exceeds uint128 maximum`);
|
|
543
|
+
assertSlippageBps(slippageBps);
|
|
544
|
+
const { quoterV2 } = getUniswapV3Addresses(chainId, params.addresses);
|
|
545
|
+
try {
|
|
546
|
+
const { result } = await client.simulateContract({
|
|
547
|
+
address: quoterV2,
|
|
548
|
+
abi: quoterV2Abi,
|
|
549
|
+
functionName: "quoteExactOutputSingle",
|
|
550
|
+
blockNumber,
|
|
551
|
+
args: [{
|
|
552
|
+
tokenIn,
|
|
553
|
+
tokenOut,
|
|
554
|
+
amount: amountOut,
|
|
555
|
+
fee: Number(fee),
|
|
556
|
+
sqrtPriceLimitX96: 0n
|
|
557
|
+
}]
|
|
558
|
+
});
|
|
559
|
+
const [amountIn, , , gasEstimate] = result;
|
|
560
|
+
const amountInMaximum = (amountIn * (BPS_DENOMINATOR + slippageBps) + BPS_DENOMINATOR - 1n) / BPS_DENOMINATOR;
|
|
561
|
+
return {
|
|
562
|
+
amountIn,
|
|
563
|
+
amountInMaximum,
|
|
564
|
+
gasEstimate
|
|
565
|
+
};
|
|
566
|
+
} catch (err) {
|
|
567
|
+
if (isRevert(err)) return null;
|
|
568
|
+
throw err;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function assertSlippageBps(slippageBps) {
|
|
572
|
+
if (slippageBps < 0n || slippageBps > BPS_DENOMINATOR) throw new PanopticError(`invalid slippageBps ${slippageBps}, must be 0..10000`);
|
|
573
|
+
}
|
|
574
|
+
function isRevert(err) {
|
|
575
|
+
return err instanceof BaseError && err.walk((e) => e instanceof ContractFunctionRevertedError) instanceof ContractFunctionRevertedError;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
//#endregion
|
|
579
|
+
//#region src/uniswap/v3/router/resolveRoute.ts
|
|
580
|
+
async function resolveV3SwapRoute(params) {
|
|
581
|
+
const { client, poolAddress, tokenIn } = params;
|
|
582
|
+
const meta = await getPoolMetadata({
|
|
583
|
+
client,
|
|
584
|
+
poolAddress
|
|
585
|
+
});
|
|
586
|
+
if (meta.isV4) throw new PanopticError("resolveV3SwapRoute called on a v4 pool");
|
|
587
|
+
const isToken0 = isAddressEqual(tokenIn, meta.token0Asset);
|
|
588
|
+
const isToken1 = isAddressEqual(tokenIn, meta.token1Asset);
|
|
589
|
+
if (!isToken0 && !isToken1) throw new InvalidSwapTokenError(tokenIn, meta.token0Asset, meta.token1Asset);
|
|
590
|
+
const zeroForOne = isToken0;
|
|
591
|
+
return {
|
|
592
|
+
tokenIn,
|
|
593
|
+
tokenOut: zeroForOne ? meta.token1Asset : meta.token0Asset,
|
|
594
|
+
fee: meta.fee,
|
|
595
|
+
zeroForOne
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/uniswap/v3/router/quoteViaRouter.ts
|
|
601
|
+
const FALLBACK_META = {
|
|
602
|
+
blockNumber: 0n,
|
|
603
|
+
blockTimestamp: 0n,
|
|
604
|
+
blockHash: "0x0"
|
|
605
|
+
};
|
|
606
|
+
async function quoteSwapExactInViaV3Router(params) {
|
|
607
|
+
const { client, poolAddress, chainId, tokenIn, amountIn, slippageBps, blockNumber, addresses } = params;
|
|
608
|
+
try {
|
|
609
|
+
const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
|
|
610
|
+
const metaPromise = getBlockMeta({
|
|
611
|
+
client,
|
|
612
|
+
blockNumber: targetBlockNumber
|
|
613
|
+
});
|
|
614
|
+
metaPromise.catch(() => void 0);
|
|
615
|
+
const route = await resolveV3SwapRoute({
|
|
616
|
+
client,
|
|
617
|
+
poolAddress,
|
|
618
|
+
tokenIn
|
|
619
|
+
});
|
|
620
|
+
const quote = await quoteV3ExactIn({
|
|
621
|
+
client,
|
|
622
|
+
chainId,
|
|
623
|
+
tokenIn: route.tokenIn,
|
|
624
|
+
tokenOut: route.tokenOut,
|
|
625
|
+
fee: route.fee,
|
|
626
|
+
amountIn,
|
|
627
|
+
slippageBps,
|
|
628
|
+
blockNumber: targetBlockNumber,
|
|
629
|
+
addresses
|
|
630
|
+
});
|
|
631
|
+
if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
|
|
632
|
+
const _meta = await metaPromise;
|
|
633
|
+
return {
|
|
634
|
+
success: true,
|
|
635
|
+
data: {
|
|
636
|
+
amountOut: quote.amountOut,
|
|
637
|
+
amountOutMinimum: quote.amountOutMinimum,
|
|
638
|
+
zeroForOne: route.zeroForOne,
|
|
639
|
+
tokenOut: route.tokenOut,
|
|
640
|
+
gasEstimate: quote.gasEstimate
|
|
641
|
+
},
|
|
642
|
+
gasEstimate: quote.gasEstimate,
|
|
643
|
+
_meta
|
|
644
|
+
};
|
|
645
|
+
} catch (error) {
|
|
646
|
+
return {
|
|
647
|
+
success: false,
|
|
648
|
+
error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
|
|
649
|
+
_meta: FALLBACK_META
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async function quoteSwapExactOutViaV3Router(params) {
|
|
654
|
+
const { client, poolAddress, chainId, tokenIn, amountOut, slippageBps, blockNumber, addresses } = params;
|
|
655
|
+
try {
|
|
656
|
+
const targetBlockNumber = blockNumber ?? await client.getBlockNumber();
|
|
657
|
+
const metaPromise = getBlockMeta({
|
|
658
|
+
client,
|
|
659
|
+
blockNumber: targetBlockNumber
|
|
660
|
+
});
|
|
661
|
+
metaPromise.catch(() => void 0);
|
|
662
|
+
const route = await resolveV3SwapRoute({
|
|
663
|
+
client,
|
|
664
|
+
poolAddress,
|
|
665
|
+
tokenIn
|
|
666
|
+
});
|
|
667
|
+
const quote = await quoteV3ExactOut({
|
|
668
|
+
client,
|
|
669
|
+
chainId,
|
|
670
|
+
tokenIn: route.tokenIn,
|
|
671
|
+
tokenOut: route.tokenOut,
|
|
672
|
+
fee: route.fee,
|
|
673
|
+
amountOut,
|
|
674
|
+
slippageBps,
|
|
675
|
+
blockNumber: targetBlockNumber,
|
|
676
|
+
addresses
|
|
677
|
+
});
|
|
678
|
+
if (!quote) throw new PanopticError("v3 quote returned no result (pool may have no liquidity)");
|
|
679
|
+
const _meta = await metaPromise;
|
|
680
|
+
return {
|
|
681
|
+
success: true,
|
|
682
|
+
data: {
|
|
683
|
+
amountIn: quote.amountIn,
|
|
684
|
+
amountInMaximum: quote.amountInMaximum,
|
|
685
|
+
zeroForOne: route.zeroForOne,
|
|
686
|
+
tokenOut: route.tokenOut,
|
|
687
|
+
gasEstimate: quote.gasEstimate
|
|
688
|
+
},
|
|
689
|
+
gasEstimate: quote.gasEstimate,
|
|
690
|
+
_meta
|
|
691
|
+
};
|
|
692
|
+
} catch (error) {
|
|
693
|
+
return {
|
|
694
|
+
success: false,
|
|
695
|
+
error: error instanceof PanopticError ? error : new PanopticError(error instanceof Error ? error.message : "Quote failed", error instanceof Error ? error : void 0),
|
|
696
|
+
_meta: FALLBACK_META
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
//#endregion
|
|
702
|
+
//#region src/uniswap/v4/addresses.ts
|
|
703
|
+
/**
|
|
704
|
+
* Canonical Permit2, identical across all chains.
|
|
705
|
+
*/
|
|
706
|
+
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
|
|
707
|
+
/**
|
|
708
|
+
* Verified Uniswap v4 deployment addresses keyed by chainId.
|
|
709
|
+
*
|
|
710
|
+
* Sourced from the official Uniswap v4 deployments. Add a chain here only after
|
|
711
|
+
* verifying each address against the canonical Uniswap deployment listing —
|
|
712
|
+
* Universal Router in particular is NOT the same address across chains.
|
|
713
|
+
*/
|
|
714
|
+
const UNISWAP_V4_ADDRESSES = {
|
|
715
|
+
1: {
|
|
716
|
+
universalRouter: "0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af",
|
|
717
|
+
v4Quoter: "0x52F0E24D1c21C8A0cB1e5a5dD6198556BD9E1203",
|
|
718
|
+
poolManager: "0x000000000004444c5dc75cB358380D2e3dE08A90",
|
|
719
|
+
positionManager: "0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e",
|
|
720
|
+
permit2: PERMIT2_ADDRESS
|
|
721
|
+
},
|
|
722
|
+
4663: {
|
|
723
|
+
universalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
|
|
724
|
+
v4Quoter: "0x8dc178efb8111bb0973dd9d722ebeff267c98f94",
|
|
725
|
+
poolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
|
|
726
|
+
positionManager: "0x58daec3116aae6d93017baaea7749052e8a04fa7",
|
|
727
|
+
permit2: PERMIT2_ADDRESS
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
/**
|
|
731
|
+
* Resolve the Uniswap v4 addresses for a chain, applying optional overrides.
|
|
732
|
+
*
|
|
733
|
+
* @param chainId - Target chain ID.
|
|
734
|
+
* @param overrides - Partial override of any address (e.g. for fork tests).
|
|
735
|
+
* @returns Fully-resolved {@link UniswapV4Addresses}.
|
|
736
|
+
* @throws {UnsupportedChainError} when the chain is not listed and the
|
|
737
|
+
* overrides do not supply every required address.
|
|
738
|
+
*/
|
|
739
|
+
function getUniswapV4Addresses(chainId, overrides) {
|
|
740
|
+
const base = UNISWAP_V4_ADDRESSES[Number(chainId)];
|
|
741
|
+
const merged = {
|
|
742
|
+
...base,
|
|
743
|
+
...overrides,
|
|
744
|
+
permit2: overrides?.permit2 ?? base?.permit2 ?? PERMIT2_ADDRESS
|
|
745
|
+
};
|
|
746
|
+
if (merged.universalRouter === void 0 || merged.v4Quoter === void 0 || merged.poolManager === void 0 || merged.positionManager === void 0 || merged.permit2 === void 0) throw new UnsupportedChainError(chainId);
|
|
747
|
+
return merged;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region src/uniswap/v3/router/swap.ts
|
|
752
|
+
const DEFAULT_DEADLINE_SECONDS = 1800n;
|
|
753
|
+
async function swapExactInViaV3Router(params) {
|
|
754
|
+
const { client, walletClient, account, poolAddress, chainId, tokenIn, amountIn, slippageBps, deadline, recipient, txOverrides, addresses } = params;
|
|
755
|
+
if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
|
|
756
|
+
const resolved = getUniswapV4Addresses(chainId, addresses);
|
|
757
|
+
const route = await resolveV3SwapRoute({
|
|
758
|
+
client,
|
|
759
|
+
poolAddress,
|
|
760
|
+
tokenIn
|
|
761
|
+
});
|
|
762
|
+
const quote = await quoteV3ExactIn({
|
|
763
|
+
client,
|
|
764
|
+
chainId,
|
|
765
|
+
tokenIn: route.tokenIn,
|
|
766
|
+
tokenOut: route.tokenOut,
|
|
767
|
+
fee: route.fee,
|
|
768
|
+
amountIn,
|
|
769
|
+
slippageBps,
|
|
770
|
+
addresses
|
|
771
|
+
});
|
|
772
|
+
if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
|
|
773
|
+
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
|
|
774
|
+
const { args, value } = buildV3SwapExecuteArgs({
|
|
775
|
+
tokenIn: route.tokenIn,
|
|
776
|
+
tokenOut: route.tokenOut,
|
|
777
|
+
fee: route.fee,
|
|
778
|
+
amountIn,
|
|
779
|
+
amountOutMinimum: quote.amountOutMinimum,
|
|
780
|
+
deadline: resolvedDeadline
|
|
781
|
+
});
|
|
782
|
+
return submitWrite({
|
|
783
|
+
client,
|
|
784
|
+
walletClient,
|
|
785
|
+
account,
|
|
786
|
+
address: resolved.universalRouter,
|
|
787
|
+
abi: universalRouterAbi,
|
|
788
|
+
functionName: "execute",
|
|
789
|
+
args,
|
|
790
|
+
value,
|
|
791
|
+
txOverrides
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
async function swapExactOutViaV3Router(params) {
|
|
795
|
+
const { client, walletClient, account, poolAddress, chainId, tokenIn, amountOut, slippageBps, deadline, recipient, txOverrides, addresses } = params;
|
|
796
|
+
if (recipient !== void 0 && !isAddressEqual(recipient, account)) throw new PanopticError("Custom recipient is not supported yet; output goes to the sender");
|
|
797
|
+
const resolved = getUniswapV4Addresses(chainId, addresses);
|
|
798
|
+
const route = await resolveV3SwapRoute({
|
|
799
|
+
client,
|
|
800
|
+
poolAddress,
|
|
801
|
+
tokenIn
|
|
802
|
+
});
|
|
803
|
+
const quote = await quoteV3ExactOut({
|
|
804
|
+
client,
|
|
805
|
+
chainId,
|
|
806
|
+
tokenIn: route.tokenIn,
|
|
807
|
+
tokenOut: route.tokenOut,
|
|
808
|
+
fee: route.fee,
|
|
809
|
+
amountOut,
|
|
810
|
+
slippageBps,
|
|
811
|
+
addresses
|
|
812
|
+
});
|
|
813
|
+
if (!quote) throw new PanopticError("v3 quote failed (pool may have no liquidity)");
|
|
814
|
+
const resolvedDeadline = deadline ?? (await getBlockMeta({ client })).blockTimestamp + DEFAULT_DEADLINE_SECONDS;
|
|
815
|
+
const { args, value } = buildV3ExactOutSwapExecuteArgs({
|
|
816
|
+
tokenIn: route.tokenIn,
|
|
817
|
+
tokenOut: route.tokenOut,
|
|
818
|
+
fee: route.fee,
|
|
819
|
+
amountOut,
|
|
820
|
+
amountInMaximum: quote.amountInMaximum,
|
|
821
|
+
deadline: resolvedDeadline
|
|
822
|
+
});
|
|
823
|
+
return submitWrite({
|
|
824
|
+
client,
|
|
825
|
+
walletClient,
|
|
826
|
+
account,
|
|
827
|
+
address: resolved.universalRouter,
|
|
828
|
+
abi: universalRouterAbi,
|
|
829
|
+
functionName: "execute",
|
|
830
|
+
args,
|
|
831
|
+
value,
|
|
832
|
+
txOverrides
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
//#endregion
|
|
837
|
+
export { AmountExceedsUint128Error, InvalidSwapTokenError, MSG_SENDER, MissingSweepRecipientError, PERMIT2_ADDRESS, QuoterUnavailableError, UNISWAP_V3_ADDRESSES, UNISWAP_V4_ADDRESSES, UnsupportedChainError, V3_SWAP_EXACT_IN, V3_SWAP_EXACT_OUT, buildV3ExactOutSwapExecuteArgs, buildV3ExactOutSwapExecuteCalldata, buildV3SwapExecuteArgs, buildV3SwapExecuteCalldata, encodeV3Path, getLpDepositAmounts, getLpDepositBreakdown, getLpFundingDeposit, getLpPositionFunding, getMaxLpPositionSize, getUniswapV3Addresses, getUniswapV4Addresses, quoteSwapExactInViaV3Router, quoteSwapExactOutViaV3Router, quoteV3ExactIn, quoteV3ExactOut, resolveV3SwapRoute, swapExactInViaV3Router, swapExactOutViaV3Router, universalRouterAbi };
|