@hyperbridge/sdk 2.8.0 → 2.8.3
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/browser/index.d.ts +298 -17
- package/dist/browser/index.js +496 -113
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +497 -111
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +46 -6
- package/dist/node/index.d.ts +46 -6
- package/dist/node/index.js +496 -113
- package/dist/node/index.js.map +1 -1
- package/dist/node/{intents-helpers-gyGREuyu.d.cts → intents-helpers-CxCDx-hH.d.cts} +300 -19
- package/dist/node/{intents-helpers-gyGREuyu.d.ts → intents-helpers-CxCDx-hH.d.ts} +300 -19
- package/dist/node/intents-helpers.cjs +406 -50
- package/dist/node/intents-helpers.cjs.map +1 -1
- package/dist/node/intents-helpers.d.cts +1 -1
- package/dist/node/intents-helpers.d.ts +1 -1
- package/dist/node/intents-helpers.js +405 -51
- package/dist/node/intents-helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -2041,6 +2041,12 @@ var CryptoUtils = class _CryptoUtils {
|
|
|
2041
2041
|
* signing infrastructure (hardware wallets, MPC/TEE policy engines) instead
|
|
2042
2042
|
* of an opaque 32-byte digest.
|
|
2043
2043
|
*
|
|
2044
|
+
* The payload must be a standard self-describing `eth_signTypedData_v4`
|
|
2045
|
+
* payload — `EIP712Domain` listed in `types`, `chainId` as a JSON number —
|
|
2046
|
+
* because some signing backends (e.g. MPC Vault) hash it server-side from
|
|
2047
|
+
* the JSON rather than locally via viem. viem ignores both details when
|
|
2048
|
+
* hashing, so the digest is unchanged for local signers.
|
|
2049
|
+
*
|
|
2044
2050
|
* @param userOp - The packed UserOperation to sign (signature field ignored).
|
|
2045
2051
|
* @param entryPoint - Address of the EntryPoint v0.8 contract.
|
|
2046
2052
|
* @param chainId - Chain ID of the network on which the operation will execute.
|
|
@@ -2051,10 +2057,22 @@ var CryptoUtils = class _CryptoUtils {
|
|
|
2051
2057
|
domain: {
|
|
2052
2058
|
name: "ERC4337",
|
|
2053
2059
|
version: "1",
|
|
2054
|
-
chainId
|
|
2060
|
+
// Runtime number so JSON.stringify emits a canonical v4 numeric chainId for
|
|
2061
|
+
// server-side hashers; viem's uint256 type mapping wants bigint but its
|
|
2062
|
+
// runtime accepts numbers, hence the cast.
|
|
2063
|
+
chainId: Number(chainId),
|
|
2055
2064
|
verifyingContract: entryPoint
|
|
2056
2065
|
},
|
|
2066
|
+
// `as const`: viem derives the domain's TYPE from `types.EIP712Domain`, so the
|
|
2067
|
+
// entries must stay string literals — widened `string` fields make viem's
|
|
2068
|
+
// typed-data generics reject the payload at every call site.
|
|
2057
2069
|
types: {
|
|
2070
|
+
EIP712Domain: [
|
|
2071
|
+
{ name: "name", type: "string" },
|
|
2072
|
+
{ name: "version", type: "string" },
|
|
2073
|
+
{ name: "chainId", type: "uint256" },
|
|
2074
|
+
{ name: "verifyingContract", type: "address" }
|
|
2075
|
+
],
|
|
2058
2076
|
PackedUserOperation: [
|
|
2059
2077
|
{ name: "sender", type: "address" },
|
|
2060
2078
|
{ name: "nonce", type: "uint256" },
|
|
@@ -2318,6 +2336,107 @@ var CryptoUtils = class _CryptoUtils {
|
|
|
2318
2336
|
}
|
|
2319
2337
|
};
|
|
2320
2338
|
|
|
2339
|
+
// src/protocols/intents/uniswap-v4-position.ts
|
|
2340
|
+
var Q96 = 1n << 96n;
|
|
2341
|
+
var MIN_TICK = -887272;
|
|
2342
|
+
var MAX_TICK = 887272;
|
|
2343
|
+
function word(data, index) {
|
|
2344
|
+
const hex = data.startsWith("0x") ? data.slice(2) : data;
|
|
2345
|
+
const start = index * 64;
|
|
2346
|
+
if (hex.length < start + 64) throw new Error(`return data too short for word ${index}`);
|
|
2347
|
+
return BigInt(`0x${hex.slice(start, start + 64)}`);
|
|
2348
|
+
}
|
|
2349
|
+
function asSigned(value, bits) {
|
|
2350
|
+
const width = 1n << BigInt(bits);
|
|
2351
|
+
const masked = value & width - 1n;
|
|
2352
|
+
return masked >= width >> 1n ? masked - width : masked;
|
|
2353
|
+
}
|
|
2354
|
+
function addressFromWord(data, index) {
|
|
2355
|
+
const hex = data.startsWith("0x") ? data.slice(2) : data;
|
|
2356
|
+
return `0x${hex.slice(index * 64 + 24, (index + 1) * 64)}`.toLowerCase();
|
|
2357
|
+
}
|
|
2358
|
+
function getSqrtRatioAtTick(tick) {
|
|
2359
|
+
if (!Number.isInteger(tick) || tick < MIN_TICK || tick > MAX_TICK) {
|
|
2360
|
+
throw new Error(`tick out of range: ${tick}`);
|
|
2361
|
+
}
|
|
2362
|
+
const absTick = BigInt(Math.abs(tick));
|
|
2363
|
+
let ratio = (absTick & 0x1n) !== 0n ? 0xfffcb933bd6fad37aa2d162d1a594001n : 0x100000000000000000000000000000000n;
|
|
2364
|
+
const factors = [
|
|
2365
|
+
[0x2n, 0xfff97272373d413259a46990580e213an],
|
|
2366
|
+
[0x4n, 0xfff2e50f5f656932ef12357cf3c7fdccn],
|
|
2367
|
+
[0x8n, 0xffe5caca7e10e4e61c3624eaa0941cd0n],
|
|
2368
|
+
[0x10n, 0xffcb9843d60f6159c9db58835c926644n],
|
|
2369
|
+
[0x20n, 0xff973b41fa98c081472e6896dfb254c0n],
|
|
2370
|
+
[0x40n, 0xff2ea16466c96a3843ec78b326b52861n],
|
|
2371
|
+
[0x80n, 0xfe5dee046a99a2a811c461f1969c3053n],
|
|
2372
|
+
[0x100n, 0xfcbe86c7900a88aedcffc83b479aa3a4n],
|
|
2373
|
+
[0x200n, 0xf987a7253ac413176f2b074cf7815e54n],
|
|
2374
|
+
[0x400n, 0xf3392b0822b70005940c7a398e4b70f3n],
|
|
2375
|
+
[0x800n, 0xe7159475a2c29b7443b29c7fa6e889d9n],
|
|
2376
|
+
[0x1000n, 0xd097f3bdfd2022b8845ad8f792aa5825n],
|
|
2377
|
+
[0x2000n, 0xa9f746462d870fdf8a65dc1f90e061e5n],
|
|
2378
|
+
[0x4000n, 0x70d869a156d2a1b890bb3df62baf32f7n],
|
|
2379
|
+
[0x8000n, 0x31be135f97d08fd981231505542fcfa6n],
|
|
2380
|
+
[0x10000n, 0x9aa508b5b7a84e1c677de54f3e99bc9n],
|
|
2381
|
+
[0x20000n, 0x5d6af8dedb81196699c329225ee604n],
|
|
2382
|
+
[0x40000n, 0x2216e584f5fa1ea926041bedfe98n],
|
|
2383
|
+
[0x80000n, 0x48a170391f7dc42444e8fa2n]
|
|
2384
|
+
];
|
|
2385
|
+
for (const [bit, factor] of factors) {
|
|
2386
|
+
if ((absTick & bit) !== 0n) ratio = ratio * factor >> 128n;
|
|
2387
|
+
}
|
|
2388
|
+
if (tick > 0) ratio = (1n << 256n) / ratio;
|
|
2389
|
+
return (ratio >> 32n) + (ratio % (1n << 32n) === 0n ? 0n : 1n);
|
|
2390
|
+
}
|
|
2391
|
+
function positionTicks(info) {
|
|
2392
|
+
return {
|
|
2393
|
+
tickLower: Number(asSigned(info >> 8n, 24)),
|
|
2394
|
+
tickUpper: Number(asSigned(info >> 32n, 24))
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
function getAmountsForLiquidity(params) {
|
|
2398
|
+
let { sqrtRatioAX96, sqrtRatioBX96 } = params;
|
|
2399
|
+
const { sqrtPriceX96, liquidity } = params;
|
|
2400
|
+
if (sqrtRatioAX96 > sqrtRatioBX96) [sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96];
|
|
2401
|
+
const amount0For = (from, to) => liquidity * Q96 * (to - from) / to / from;
|
|
2402
|
+
const amount1For = (from, to) => liquidity * (to - from) / Q96;
|
|
2403
|
+
if (sqrtPriceX96 <= sqrtRatioAX96) {
|
|
2404
|
+
return { amount0: amount0For(sqrtRatioAX96, sqrtRatioBX96), amount1: 0n };
|
|
2405
|
+
}
|
|
2406
|
+
if (sqrtPriceX96 < sqrtRatioBX96) {
|
|
2407
|
+
return {
|
|
2408
|
+
amount0: amount0For(sqrtPriceX96, sqrtRatioBX96),
|
|
2409
|
+
amount1: amount1For(sqrtRatioAX96, sqrtPriceX96)
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
return { amount0: 0n, amount1: amount1For(sqrtRatioAX96, sqrtRatioBX96) };
|
|
2413
|
+
}
|
|
2414
|
+
function decodePoolAndPositionInfo(data) {
|
|
2415
|
+
const hex = data.startsWith("0x") ? data.slice(2) : data;
|
|
2416
|
+
if (hex.length < 6 * 64) throw new Error("getPoolAndPositionInfo returned too little data");
|
|
2417
|
+
const { tickLower, tickUpper } = positionTicks(word(data, 5));
|
|
2418
|
+
return {
|
|
2419
|
+
currency0: addressFromWord(data, 0),
|
|
2420
|
+
currency1: addressFromWord(data, 1),
|
|
2421
|
+
poolKeyEncoded: `0x${hex.slice(0, 5 * 64)}`,
|
|
2422
|
+
tickLower,
|
|
2423
|
+
tickUpper
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
function positionAmountOfToken(params) {
|
|
2427
|
+
const { info, liquidity, sqrtPriceX96 } = params;
|
|
2428
|
+
const outputToken = params.outputToken.toLowerCase();
|
|
2429
|
+
if (outputToken !== info.currency0 && outputToken !== info.currency1) return 0n;
|
|
2430
|
+
if (liquidity <= 0n || sqrtPriceX96 <= 0n) return 0n;
|
|
2431
|
+
const { amount0, amount1 } = getAmountsForLiquidity({
|
|
2432
|
+
sqrtPriceX96,
|
|
2433
|
+
sqrtRatioAX96: getSqrtRatioAtTick(info.tickLower),
|
|
2434
|
+
sqrtRatioBX96: getSqrtRatioAtTick(info.tickUpper),
|
|
2435
|
+
liquidity
|
|
2436
|
+
});
|
|
2437
|
+
return outputToken === info.currency0 ? amount0 : amount1;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2321
2440
|
// src/protocols/intents/phantom-aggregation.ts
|
|
2322
2441
|
var ENTRY_POINT_V08_ADDRESS = "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108";
|
|
2323
2442
|
var injectedFetch;
|
|
@@ -2331,6 +2450,14 @@ function rpcFetch() {
|
|
|
2331
2450
|
}
|
|
2332
2451
|
return f;
|
|
2333
2452
|
}
|
|
2453
|
+
var PhantomRpcError = class extends Error {
|
|
2454
|
+
constructor(message, cause) {
|
|
2455
|
+
super(message);
|
|
2456
|
+
this.cause = cause;
|
|
2457
|
+
this.name = "PhantomRpcError";
|
|
2458
|
+
}
|
|
2459
|
+
cause;
|
|
2460
|
+
};
|
|
2334
2461
|
async function rpcCall(url, payload) {
|
|
2335
2462
|
let lastErr;
|
|
2336
2463
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
@@ -2348,23 +2475,46 @@ async function rpcCall(url, payload) {
|
|
|
2348
2475
|
}),
|
|
2349
2476
|
timeout
|
|
2350
2477
|
]);
|
|
2351
|
-
|
|
2478
|
+
const body = await response.json();
|
|
2479
|
+
if (body?.error) {
|
|
2480
|
+
lastErr = new Error(`rpc error: ${JSON.stringify(body.error).slice(0, 200)}`);
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
return body;
|
|
2352
2484
|
} catch (err) {
|
|
2353
2485
|
lastErr = err;
|
|
2354
2486
|
} finally {
|
|
2355
2487
|
if (timer) clearTimeout(timer);
|
|
2356
2488
|
}
|
|
2357
2489
|
}
|
|
2358
|
-
throw lastErr;
|
|
2490
|
+
throw new PhantomRpcError(`RPC call failed after 4 attempts: ${url}`, lastErr);
|
|
2359
2491
|
}
|
|
2360
2492
|
var FILL_ORDER_ABI = IntentGatewayV2_default.ABI;
|
|
2361
|
-
var
|
|
2362
|
-
var
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2493
|
+
var DECLARATION_V1 = 1;
|
|
2494
|
+
var DECLARATION_V2 = 2;
|
|
2495
|
+
var MAX_DECLARED_ENTRIES = 255;
|
|
2496
|
+
var MAX_TOKEN_ID_BYTES = 32;
|
|
2497
|
+
function tokenIdToBytes(tokenId) {
|
|
2498
|
+
if (tokenId < 0n) throw new Error(`Uniswap V4 tokenId cannot be negative: ${tokenId}`);
|
|
2499
|
+
const bytes = [];
|
|
2500
|
+
let rest = tokenId;
|
|
2501
|
+
while (rest > 0n) {
|
|
2502
|
+
bytes.unshift(Number(rest & 0xffn));
|
|
2503
|
+
rest >>= 8n;
|
|
2366
2504
|
}
|
|
2367
|
-
|
|
2505
|
+
return bytes.length > 0 ? bytes : [0];
|
|
2506
|
+
}
|
|
2507
|
+
function encodePhantomBidDeclaration(declaration) {
|
|
2508
|
+
const chains = declaration.acceptedSourceChains ?? [];
|
|
2509
|
+
const positions = declaration.uniswapV4Positions ?? [];
|
|
2510
|
+
if (chains.length > MAX_DECLARED_ENTRIES) {
|
|
2511
|
+
throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} source chains`);
|
|
2512
|
+
}
|
|
2513
|
+
if (positions.length > MAX_DECLARED_ENTRIES) {
|
|
2514
|
+
throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} Uniswap V4 positions`);
|
|
2515
|
+
}
|
|
2516
|
+
const version = positions.length > 0 ? DECLARATION_V2 : DECLARATION_V1;
|
|
2517
|
+
const bytes = [version, chains.length];
|
|
2368
2518
|
for (const chain of chains) {
|
|
2369
2519
|
const encoded = util.stringToU8a(chain);
|
|
2370
2520
|
if (encoded.length === 0 || encoded.length > 255) {
|
|
@@ -2372,25 +2522,59 @@ function encodeAcceptedSourceChains(chains) {
|
|
|
2372
2522
|
}
|
|
2373
2523
|
bytes.push(encoded.length, ...encoded);
|
|
2374
2524
|
}
|
|
2525
|
+
if (version === DECLARATION_V2) {
|
|
2526
|
+
bytes.push(positions.length);
|
|
2527
|
+
for (const tokenId of positions) {
|
|
2528
|
+
const encoded = tokenIdToBytes(tokenId);
|
|
2529
|
+
if (encoded.length > MAX_TOKEN_ID_BYTES) {
|
|
2530
|
+
throw new Error(`Uniswap V4 tokenId exceeds uint256: ${tokenId}`);
|
|
2531
|
+
}
|
|
2532
|
+
bytes.push(encoded.length, ...encoded);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2375
2535
|
return util.u8aToHex(new Uint8Array(bytes));
|
|
2376
2536
|
}
|
|
2377
|
-
function
|
|
2378
|
-
|
|
2537
|
+
function decodePhantomBidDeclaration(paymasterAndData) {
|
|
2538
|
+
const absent = { acceptedSources: null, uniswapV4Positions: [] };
|
|
2539
|
+
if (!paymasterAndData || !util.isHex(paymasterAndData)) return absent;
|
|
2379
2540
|
const bytes = util.hexToU8a(paymasterAndData);
|
|
2380
|
-
if (bytes.length < 2
|
|
2381
|
-
const
|
|
2541
|
+
if (bytes.length < 2) return absent;
|
|
2542
|
+
const version = bytes[0];
|
|
2543
|
+
if (version !== DECLARATION_V1 && version !== DECLARATION_V2) return absent;
|
|
2382
2544
|
const chains = [];
|
|
2383
2545
|
let offset = 2;
|
|
2384
|
-
for (let entry = 0; entry <
|
|
2385
|
-
if (offset >= bytes.length) return
|
|
2546
|
+
for (let entry = 0; entry < bytes[1]; entry++) {
|
|
2547
|
+
if (offset >= bytes.length) return absent;
|
|
2386
2548
|
const length = bytes[offset];
|
|
2387
2549
|
offset += 1;
|
|
2388
|
-
if (length === 0 || offset + length > bytes.length) return
|
|
2550
|
+
if (length === 0 || offset + length > bytes.length) return absent;
|
|
2389
2551
|
chains.push(util.u8aToString(bytes.subarray(offset, offset + length)));
|
|
2390
2552
|
offset += length;
|
|
2391
2553
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2554
|
+
const positions = [];
|
|
2555
|
+
if (version === DECLARATION_V2) {
|
|
2556
|
+
if (offset >= bytes.length) return absent;
|
|
2557
|
+
const count = bytes[offset];
|
|
2558
|
+
offset += 1;
|
|
2559
|
+
for (let entry = 0; entry < count; entry++) {
|
|
2560
|
+
if (offset >= bytes.length) return absent;
|
|
2561
|
+
const length = bytes[offset];
|
|
2562
|
+
offset += 1;
|
|
2563
|
+
if (length === 0 || length > MAX_TOKEN_ID_BYTES || offset + length > bytes.length) return absent;
|
|
2564
|
+
let tokenId = 0n;
|
|
2565
|
+
for (const byte of bytes.subarray(offset, offset + length)) tokenId = tokenId << 8n | BigInt(byte);
|
|
2566
|
+
positions.push(tokenId);
|
|
2567
|
+
offset += length;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
if (offset !== bytes.length) return absent;
|
|
2571
|
+
return { acceptedSources: chains, uniswapV4Positions: positions };
|
|
2572
|
+
}
|
|
2573
|
+
function encodeAcceptedSourceChains(chains) {
|
|
2574
|
+
return encodePhantomBidDeclaration({ acceptedSourceChains: chains });
|
|
2575
|
+
}
|
|
2576
|
+
function decodeAcceptedSourceChains(paymasterAndData) {
|
|
2577
|
+
return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
|
|
2394
2578
|
}
|
|
2395
2579
|
function zipFillLegs(assets, outputs) {
|
|
2396
2580
|
return assets.map((asset, index) => {
|
|
@@ -2475,17 +2659,46 @@ async function isDelegatedToSolverAccount(evmRpcUrl, account, solverAccount) {
|
|
|
2475
2659
|
method: "eth_getCode",
|
|
2476
2660
|
params: [account, "latest"]
|
|
2477
2661
|
});
|
|
2478
|
-
|
|
2662
|
+
if (typeof response.result !== "string") {
|
|
2663
|
+
throw new PhantomRpcError(`eth_getCode returned no code for ${account} on ${evmRpcUrl}`);
|
|
2664
|
+
}
|
|
2665
|
+
const code = response.result.toLowerCase();
|
|
2479
2666
|
if (!code.startsWith(DELEGATION_INDICATOR_PREFIX)) return false;
|
|
2480
2667
|
return `0x${code.slice(DELEGATION_INDICATOR_PREFIX.length)}` === solverAccount.toLowerCase();
|
|
2481
2668
|
}
|
|
2669
|
+
function memoizedDelegationCheck() {
|
|
2670
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2671
|
+
return (evmRpcUrl, account, solverAccount) => {
|
|
2672
|
+
const key = `${evmRpcUrl}|${account.toLowerCase()}|${solverAccount.toLowerCase()}`;
|
|
2673
|
+
let pending = cache.get(key);
|
|
2674
|
+
if (!pending) {
|
|
2675
|
+
pending = isDelegatedToSolverAccount(evmRpcUrl, account, solverAccount).catch((err) => {
|
|
2676
|
+
cache.delete(key);
|
|
2677
|
+
throw err;
|
|
2678
|
+
});
|
|
2679
|
+
cache.set(key, pending);
|
|
2680
|
+
}
|
|
2681
|
+
return pending;
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2482
2684
|
function evmChainId(chain) {
|
|
2483
2685
|
const [prefix, id] = chain.split("-");
|
|
2484
2686
|
if (prefix !== "EVM" || !id || !/^\d+$/.test(id)) return null;
|
|
2485
2687
|
return BigInt(id);
|
|
2486
2688
|
}
|
|
2487
2689
|
async function isVerifiedSolverBid(params) {
|
|
2488
|
-
const {
|
|
2690
|
+
const {
|
|
2691
|
+
userOp,
|
|
2692
|
+
commitment,
|
|
2693
|
+
sessionKey,
|
|
2694
|
+
chainId,
|
|
2695
|
+
solverAccount,
|
|
2696
|
+
evmRpcUrl,
|
|
2697
|
+
recoverSigner,
|
|
2698
|
+
bidNonceKey,
|
|
2699
|
+
isDelegated,
|
|
2700
|
+
logger
|
|
2701
|
+
} = params;
|
|
2489
2702
|
const solver = userOp.sender;
|
|
2490
2703
|
const parsed = splitBidSignature(userOp.signature);
|
|
2491
2704
|
if (!parsed) {
|
|
@@ -2508,7 +2721,7 @@ async function isVerifiedSolverBid(params) {
|
|
|
2508
2721
|
logger?.warn({ solver, commitment, signer }, "Rejecting phantom bid: signature does not recover to the sender");
|
|
2509
2722
|
return false;
|
|
2510
2723
|
}
|
|
2511
|
-
if (!await
|
|
2724
|
+
if (!await isDelegated(evmRpcUrl, solver, solverAccount)) {
|
|
2512
2725
|
logger?.warn({ solver, commitment, solverAccount }, "Rejecting phantom bid: sender is not a delegated solver");
|
|
2513
2726
|
return false;
|
|
2514
2727
|
}
|
|
@@ -2524,18 +2737,17 @@ async function fetchBidsForOrder(nodeUrl, commitment) {
|
|
|
2524
2737
|
return Array.isArray(data.result) ? data.result : [];
|
|
2525
2738
|
}
|
|
2526
2739
|
async function ethCallUint(evmRpcUrl, to, data) {
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
} catch {
|
|
2537
|
-
return 0n;
|
|
2740
|
+
const result = await rpcCall(evmRpcUrl, {
|
|
2741
|
+
id: 1,
|
|
2742
|
+
jsonrpc: "2.0",
|
|
2743
|
+
method: "eth_call",
|
|
2744
|
+
params: [{ to, data }, "latest"]
|
|
2745
|
+
});
|
|
2746
|
+
if (result.result === "0x") return 0n;
|
|
2747
|
+
if (typeof result.result !== "string") {
|
|
2748
|
+
throw new PhantomRpcError(`eth_call returned no result for ${to} on ${evmRpcUrl}`);
|
|
2538
2749
|
}
|
|
2750
|
+
return BigInt(result.result);
|
|
2539
2751
|
}
|
|
2540
2752
|
async function getTotalSolverBalance(evmRpcUrl, chain, token, solver, yieldVaults) {
|
|
2541
2753
|
const padded = solver.replace("0x", "").padStart(64, "0");
|
|
@@ -2547,27 +2759,104 @@ async function getTotalSolverBalance(evmRpcUrl, chain, token, solver, yieldVault
|
|
|
2547
2759
|
);
|
|
2548
2760
|
return vaultBalances.reduce((acc, b) => acc + b, raw);
|
|
2549
2761
|
}
|
|
2762
|
+
var SELECTOR_OWNER_OF = "0x6352211e";
|
|
2763
|
+
var SELECTOR_POSITION_LIQUIDITY = "0x1efeed33";
|
|
2764
|
+
var SELECTOR_POOL_AND_POSITION_INFO = "0x7ba03aad";
|
|
2765
|
+
var SELECTOR_GET_SLOT0 = "0xc815641c";
|
|
2766
|
+
var uint256Arg = (value) => value.toString(16).padStart(64, "0");
|
|
2767
|
+
async function readV4Position(evmRpcUrl, contracts, tokenId, keccak, logger) {
|
|
2768
|
+
const call = async (to, data) => {
|
|
2769
|
+
const result = await rpcCall(evmRpcUrl, {
|
|
2770
|
+
id: 1,
|
|
2771
|
+
jsonrpc: "2.0",
|
|
2772
|
+
method: "eth_call",
|
|
2773
|
+
params: [{ to, data }, "latest"]
|
|
2774
|
+
});
|
|
2775
|
+
if (result.result === "0x") return null;
|
|
2776
|
+
if (typeof result.result !== "string") {
|
|
2777
|
+
throw new PhantomRpcError(`eth_call returned no result for ${to} on ${evmRpcUrl}`);
|
|
2778
|
+
}
|
|
2779
|
+
return result.result;
|
|
2780
|
+
};
|
|
2781
|
+
const arg = uint256Arg(tokenId);
|
|
2782
|
+
const [ownerData, infoData, liquidityData] = await Promise.all([
|
|
2783
|
+
call(contracts.positionManager, `${SELECTOR_OWNER_OF}${arg}`),
|
|
2784
|
+
call(contracts.positionManager, `${SELECTOR_POOL_AND_POSITION_INFO}${arg}`),
|
|
2785
|
+
call(contracts.positionManager, `${SELECTOR_POSITION_LIQUIDITY}${arg}`)
|
|
2786
|
+
]);
|
|
2787
|
+
if (!ownerData) return null;
|
|
2788
|
+
if (!infoData || !liquidityData) {
|
|
2789
|
+
logger?.warn(
|
|
2790
|
+
{ tokenId: tokenId.toString(), positionManager: contracts.positionManager },
|
|
2791
|
+
"Uniswap V4 position exists but its pool info or liquidity did not read back \u2014 check the configured PositionManager"
|
|
2792
|
+
);
|
|
2793
|
+
return null;
|
|
2794
|
+
}
|
|
2795
|
+
const info = decodePoolAndPositionInfo(infoData);
|
|
2796
|
+
const slot0Data = await call(contracts.stateView, `${SELECTOR_GET_SLOT0}${keccak(info.poolKeyEncoded).slice(2)}`);
|
|
2797
|
+
if (!slot0Data) {
|
|
2798
|
+
logger?.warn(
|
|
2799
|
+
{ tokenId: tokenId.toString(), stateView: contracts.stateView, evmRpcUrl },
|
|
2800
|
+
"Uniswap V4 slot0 read returned nothing for a live position \u2014 the configured StateView address is wrong"
|
|
2801
|
+
);
|
|
2802
|
+
return null;
|
|
2803
|
+
}
|
|
2804
|
+
return {
|
|
2805
|
+
owner: `0x${ownerData.slice(-40)}`.toLowerCase(),
|
|
2806
|
+
info,
|
|
2807
|
+
liquidity: word(liquidityData, 0),
|
|
2808
|
+
sqrtPriceX96: word(slot0Data, 0)
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
function memoizedV4Position(keccak, logger) {
|
|
2812
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2813
|
+
return (evmRpcUrl, chain, contracts, tokenId) => {
|
|
2814
|
+
const key = `${chain}|${tokenId}`;
|
|
2815
|
+
let pending = cache.get(key);
|
|
2816
|
+
if (!pending) {
|
|
2817
|
+
pending = readV4Position(evmRpcUrl, contracts, tokenId, keccak, logger).catch((err) => {
|
|
2818
|
+
cache.delete(key);
|
|
2819
|
+
throw err;
|
|
2820
|
+
});
|
|
2821
|
+
cache.set(key, pending);
|
|
2822
|
+
}
|
|
2823
|
+
return pending;
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2550
2826
|
function memoizedSolverBalance(yieldVaults) {
|
|
2551
2827
|
const cache = /* @__PURE__ */ new Map();
|
|
2552
2828
|
return (evmRpcUrl, chain, token, solver) => {
|
|
2553
2829
|
const key = `${chain}|${token.toLowerCase()}|${solver.toLowerCase()}`;
|
|
2554
2830
|
let pending = cache.get(key);
|
|
2555
2831
|
if (!pending) {
|
|
2556
|
-
pending = getTotalSolverBalance(evmRpcUrl, chain, token, solver, yieldVaults)
|
|
2832
|
+
pending = getTotalSolverBalance(evmRpcUrl, chain, token, solver, yieldVaults).catch((err) => {
|
|
2833
|
+
cache.delete(key);
|
|
2834
|
+
throw err;
|
|
2835
|
+
});
|
|
2557
2836
|
cache.set(key, pending);
|
|
2558
2837
|
}
|
|
2559
2838
|
return pending;
|
|
2560
2839
|
};
|
|
2561
2840
|
}
|
|
2562
|
-
async function sweepSolverLiquidity(evmRpcUrls, yieldVaults, solver, getBalance) {
|
|
2841
|
+
async function sweepSolverLiquidity(evmRpcUrls, yieldVaults, solver, getBalance, positions) {
|
|
2563
2842
|
const balances = [];
|
|
2564
2843
|
for (const [chain, tokens] of Object.entries(yieldVaults)) {
|
|
2565
2844
|
const url = evmRpcUrls[chain];
|
|
2566
2845
|
if (!url) continue;
|
|
2567
2846
|
for (const token of Object.keys(tokens)) {
|
|
2568
2847
|
const balance = await getBalance(url, chain, token, solver);
|
|
2569
|
-
|
|
2570
|
-
|
|
2848
|
+
const uniswapV4Balance = chain === positions.chain ? positions.states.reduce(
|
|
2849
|
+
(total2, state) => total2 + positionAmountOfToken({
|
|
2850
|
+
info: state.info,
|
|
2851
|
+
liquidity: state.liquidity,
|
|
2852
|
+
sqrtPriceX96: state.sqrtPriceX96,
|
|
2853
|
+
outputToken: token
|
|
2854
|
+
}),
|
|
2855
|
+
0n
|
|
2856
|
+
) : 0n;
|
|
2857
|
+
const total = balance + uniswapV4Balance;
|
|
2858
|
+
if (total === 0n) continue;
|
|
2859
|
+
balances.push({ solver, chain, tokenAddress: token, balance: total });
|
|
2571
2860
|
}
|
|
2572
2861
|
}
|
|
2573
2862
|
return balances;
|
|
@@ -2578,6 +2867,24 @@ function toAddress(token) {
|
|
|
2578
2867
|
return `0x${addr}`;
|
|
2579
2868
|
}
|
|
2580
2869
|
async function aggregatePhantomBids(params) {
|
|
2870
|
+
const isDelegated = memoizedDelegationCheck();
|
|
2871
|
+
let lastErr;
|
|
2872
|
+
for (let attempt = 1; attempt <= AGGREGATION_ATTEMPTS; attempt++) {
|
|
2873
|
+
try {
|
|
2874
|
+
return await runAggregation(params, isDelegated);
|
|
2875
|
+
} catch (err) {
|
|
2876
|
+
lastErr = err;
|
|
2877
|
+
params.logger?.warn(
|
|
2878
|
+
{ err, commitment: params.commitment, chain: params.chain, attempt, of: AGGREGATION_ATTEMPTS },
|
|
2879
|
+
"Phantom aggregation attempt failed"
|
|
2880
|
+
);
|
|
2881
|
+
if (attempt < AGGREGATION_ATTEMPTS) await new Promise((resolve) => setTimeout(resolve, 250 * attempt));
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
throw lastErr;
|
|
2885
|
+
}
|
|
2886
|
+
var AGGREGATION_ATTEMPTS = 5;
|
|
2887
|
+
async function runAggregation(params, isDelegated) {
|
|
2581
2888
|
const { nodeUrl, evmRpcUrls, chain, gatewayAddress, commitment, yieldVaults, solverAccount, logger } = params;
|
|
2582
2889
|
const extractFill = params.extractFill ?? extractFillData;
|
|
2583
2890
|
const recoverSigner = params.recoverSigner ?? recoverBidSignerViem;
|
|
@@ -2592,6 +2899,8 @@ async function aggregatePhantomBids(params) {
|
|
|
2592
2899
|
}
|
|
2593
2900
|
const bids = await fetchBidsForOrder(nodeUrl, commitment);
|
|
2594
2901
|
if (bids.length === 0) return null;
|
|
2902
|
+
const v4Contracts = params.uniswapV4?.[chain];
|
|
2903
|
+
const readPosition = memoizedV4Position(params.keccak ?? viem.keccak256, logger);
|
|
2595
2904
|
const getBalance = params.getBalance ?? memoizedSolverBalance(yieldVaults);
|
|
2596
2905
|
const quotesByLeg = /* @__PURE__ */ new Map();
|
|
2597
2906
|
const lpBalances = [];
|
|
@@ -2622,6 +2931,7 @@ async function aggregatePhantomBids(params) {
|
|
|
2622
2931
|
evmRpcUrl: destUrl,
|
|
2623
2932
|
recoverSigner,
|
|
2624
2933
|
bidNonceKey,
|
|
2934
|
+
isDelegated,
|
|
2625
2935
|
logger
|
|
2626
2936
|
});
|
|
2627
2937
|
if (!verified) continue;
|
|
@@ -2631,12 +2941,39 @@ async function aggregatePhantomBids(params) {
|
|
|
2631
2941
|
continue;
|
|
2632
2942
|
}
|
|
2633
2943
|
countedSolvers.add(normalizedSolver);
|
|
2634
|
-
const
|
|
2944
|
+
const declaration = decodePhantomBidDeclaration(decoded.paymasterAndData);
|
|
2945
|
+
const acceptedSources = declaration.acceptedSources;
|
|
2635
2946
|
const quotedLegs = [...fillData.legs.entries()].filter(([, leg]) => leg.solverAmount !== 0n);
|
|
2947
|
+
const declaredPositions = v4Contracts ? declaration.uniswapV4Positions : [];
|
|
2948
|
+
const positions = (await Promise.all(
|
|
2949
|
+
declaredPositions.map((tokenId) => readPosition(destUrl, chain, v4Contracts, tokenId))
|
|
2950
|
+
)).filter((state, index) => {
|
|
2951
|
+
if (!state) return false;
|
|
2952
|
+
if (state.owner !== normalizedSolver) {
|
|
2953
|
+
logger?.warn(
|
|
2954
|
+
{ solver, commitment, tokenId: declaredPositions[index].toString(), owner: state.owner },
|
|
2955
|
+
"Ignoring declared Uniswap V4 position: not owned by the bidding solver"
|
|
2956
|
+
);
|
|
2957
|
+
return false;
|
|
2958
|
+
}
|
|
2959
|
+
return true;
|
|
2960
|
+
});
|
|
2636
2961
|
const weights = await Promise.all(
|
|
2637
2962
|
// Price influence: the solver's liquidity in THIS leg's output token on the destination
|
|
2638
2963
|
// chain, so a leg is weighted by the inventory that actually backs it.
|
|
2639
|
-
quotedLegs.map(([, leg]) =>
|
|
2964
|
+
quotedLegs.map(async ([, leg]) => {
|
|
2965
|
+
const outputToken = toAddress(leg.outputToken);
|
|
2966
|
+
const balance = await getBalance(destUrl, chain, outputToken, solver);
|
|
2967
|
+
return positions.reduce(
|
|
2968
|
+
(total, state) => total + positionAmountOfToken({
|
|
2969
|
+
info: state.info,
|
|
2970
|
+
liquidity: state.liquidity,
|
|
2971
|
+
sqrtPriceX96: state.sqrtPriceX96,
|
|
2972
|
+
outputToken
|
|
2973
|
+
}),
|
|
2974
|
+
balance
|
|
2975
|
+
);
|
|
2976
|
+
})
|
|
2640
2977
|
);
|
|
2641
2978
|
for (const [position, [legIndex, leg]] of quotedLegs.entries()) {
|
|
2642
2979
|
const weight = weights[position];
|
|
@@ -2645,23 +2982,40 @@ async function aggregatePhantomBids(params) {
|
|
|
2645
2982
|
entry.bidders.push({ solver: normalizedSolver, weight, acceptedSources });
|
|
2646
2983
|
quotesByLeg.set(legIndex, entry);
|
|
2647
2984
|
}
|
|
2648
|
-
lpBalances.push(
|
|
2985
|
+
lpBalances.push(
|
|
2986
|
+
...await sweepSolverLiquidity(evmRpcUrls, yieldVaults, solver, getBalance, {
|
|
2987
|
+
chain,
|
|
2988
|
+
states: positions
|
|
2989
|
+
})
|
|
2990
|
+
);
|
|
2649
2991
|
} catch (err) {
|
|
2992
|
+
if (err instanceof PhantomRpcError) throw err;
|
|
2650
2993
|
logger?.warn({ err, filler: bid.filler }, "Failed to process bid for price snapshot");
|
|
2651
2994
|
}
|
|
2652
2995
|
}
|
|
2653
2996
|
if (quotesByLeg.size === 0) return null;
|
|
2654
|
-
const legs = [...quotesByLeg.entries()].sort(([a], [b]) => a - b).
|
|
2655
|
-
const
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2997
|
+
const legs = [...quotesByLeg.entries()].sort(([a], [b]) => a - b).flatMap(([legIndex, { outputToken, quotes, bidders }]) => {
|
|
2998
|
+
const backedQuotes = quotes.filter((quote) => quote.weight > 0n);
|
|
2999
|
+
const backedBidders = bidders.filter((bidder) => bidder.weight > 0n);
|
|
3000
|
+
if (backedQuotes.length === 0) {
|
|
3001
|
+
logger?.warn(
|
|
3002
|
+
{ commitment, chain, legIndex, outputToken, quotes: quotes.length },
|
|
3003
|
+
"Dropping phantom leg: no bidder holds the output token on this chain, so no quote is backed"
|
|
3004
|
+
);
|
|
3005
|
+
return [];
|
|
3006
|
+
}
|
|
3007
|
+
const medianPrice = weightedMedian(backedQuotes);
|
|
3008
|
+
return [
|
|
3009
|
+
{
|
|
3010
|
+
legIndex,
|
|
3011
|
+
outputToken,
|
|
3012
|
+
lowestPrice: medianPrice,
|
|
3013
|
+
highestPrice: medianPrice,
|
|
3014
|
+
medianPrice,
|
|
3015
|
+
bidCount: backedQuotes.length,
|
|
3016
|
+
bidders: backedBidders
|
|
3017
|
+
}
|
|
3018
|
+
];
|
|
2665
3019
|
});
|
|
2666
3020
|
return { legs, lpBalances };
|
|
2667
3021
|
}
|
|
@@ -2672,9 +3026,11 @@ exports.IntentGatewayV2 = IntentGatewayV2_default;
|
|
|
2672
3026
|
exports.aggregatePhantomBids = aggregatePhantomBids;
|
|
2673
3027
|
exports.decodeAcceptedSourceChains = decodeAcceptedSourceChains;
|
|
2674
3028
|
exports.decodeERC7821ExecuteBatch = decodeERC7821ExecuteBatch;
|
|
3029
|
+
exports.decodePhantomBidDeclaration = decodePhantomBidDeclaration;
|
|
2675
3030
|
exports.decodeUserOpScale = decodeUserOpScale;
|
|
2676
3031
|
exports.encodeAcceptedSourceChains = encodeAcceptedSourceChains;
|
|
2677
3032
|
exports.encodeERC7821ExecuteBatch = encodeERC7821ExecuteBatch;
|
|
3033
|
+
exports.encodePhantomBidDeclaration = encodePhantomBidDeclaration;
|
|
2678
3034
|
exports.encodeUserOpScale = encodeUserOpScale;
|
|
2679
3035
|
exports.extractFillData = extractFillData;
|
|
2680
3036
|
exports.fetchBidsForOrder = fetchBidsForOrder;
|