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