@hyperbridge/sdk 2.8.2 → 2.8.4

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