@hyperbridge/sdk 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ var util = require('@polkadot/util');
6
6
  require('@polkadot/util-crypto');
7
7
  var scaleTs = require('scale-ts');
8
8
  require('p-queue');
9
+ var accounts = require('viem/accounts');
9
10
 
10
11
  // src/protocols/intents/decode-utils.ts
11
12
 
@@ -1920,6 +1921,387 @@ function decodeUserOpScale(hex) {
1920
1921
  signature: util.u8aToHex(new Uint8Array(decoded.signature))
1921
1922
  };
1922
1923
  }
1924
+ var SELECT_SOLVER_TYPEHASH = viem.keccak256(viem.toHex("SelectSolver(bytes32 commitment,address solver)"));
1925
+ var PACKED_USEROP_TYPEHASH = viem.keccak256(
1926
+ viem.toHex(
1927
+ "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"
1928
+ )
1929
+ );
1930
+ var DOMAIN_TYPEHASH = viem.keccak256(
1931
+ viem.toHex("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
1932
+ );
1933
+ var CryptoUtils = class _CryptoUtils {
1934
+ /**
1935
+ * @param ctx - Shared IntentsV2 context; used to access the bundler URL for
1936
+ * JSON-RPC calls.
1937
+ */
1938
+ constructor(ctx) {
1939
+ this.ctx = ctx;
1940
+ }
1941
+ ctx;
1942
+ /**
1943
+ * Computes an EIP-712 domain separator for a given contract.
1944
+ *
1945
+ * @param contractName - Human-readable name of the contract (e.g. `"IntentGateway"`).
1946
+ * @param version - Version string (e.g. `"2"`).
1947
+ * @param chainId - Chain ID of the network the contract is deployed on.
1948
+ * @param contractAddress - Address of the verifying contract.
1949
+ * @returns The 32-byte domain separator as a hex string.
1950
+ */
1951
+ static getDomainSeparator(contractName, version, chainId, contractAddress) {
1952
+ return viem.keccak256(
1953
+ viem.encodeAbiParameters(viem.parseAbiParameters("bytes32, bytes32, bytes32, uint256, address"), [
1954
+ DOMAIN_TYPEHASH,
1955
+ viem.keccak256(viem.toHex(contractName)),
1956
+ viem.keccak256(viem.toHex(version)),
1957
+ chainId,
1958
+ contractAddress
1959
+ ])
1960
+ );
1961
+ }
1962
+ /**
1963
+ * Signs a `SelectSolver` EIP-712 message with a session key.
1964
+ *
1965
+ * The session key authorises the selection of a specific solver for the
1966
+ * given order commitment. The resulting signature is appended to the
1967
+ * solver's UserOperation signature before bundle submission.
1968
+ *
1969
+ * @param commitment - The order commitment (bytes32) being fulfilled.
1970
+ * @param solverAddress - Address of the solver account selected to fill the order.
1971
+ * @param domainSeparator - EIP-712 domain separator for the IntentGatewayV2 contract.
1972
+ * @param privateKey - Hex-encoded private key of the session key that signs the message.
1973
+ * @returns The ECDSA signature as a hex string, or `null` if signing fails.
1974
+ */
1975
+ static async signSolverSelection(commitment, solverAddress, domainSeparator, privateKey) {
1976
+ const account = accounts.privateKeyToAccount(privateKey);
1977
+ const structHash = viem.keccak256(
1978
+ viem.encodeAbiParameters(
1979
+ [{ type: "bytes32" }, { type: "bytes32" }, { type: "address" }],
1980
+ [SELECT_SOLVER_TYPEHASH, commitment, solverAddress]
1981
+ )
1982
+ );
1983
+ const digest = viem.keccak256(viem.concat(["0x1901", domainSeparator, structHash]));
1984
+ const signature = await account.sign({ hash: digest });
1985
+ return signature;
1986
+ }
1987
+ /**
1988
+ * Computes the EIP-712 hash of a `PackedUserOperation` as defined by
1989
+ * ERC-4337 EntryPoint v0.8.
1990
+ *
1991
+ * @param userOp - The packed UserOperation to hash.
1992
+ * @param entryPoint - Address of the EntryPoint v0.8 contract.
1993
+ * @param chainId - Chain ID of the network on which the operation will execute.
1994
+ * @returns The UserOperation hash as a hex string.
1995
+ */
1996
+ static computeUserOpHash(userOp, entryPoint, chainId) {
1997
+ const structHash = _CryptoUtils.getPackedUserStructHash(userOp);
1998
+ const domainSeparator = _CryptoUtils.getDomainSeparator("ERC4337", "1", chainId, entryPoint);
1999
+ return viem.keccak256(
2000
+ viem.encodePacked(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", domainSeparator, structHash])
2001
+ );
2002
+ }
2003
+ /**
2004
+ * Derives the ERC-4337 nonce key that binds a bid UserOperation to its
2005
+ * order and session key: the lower 192 bits of
2006
+ * `keccak256(commitment ‖ sessionKey)`. `SolverAccount` rejects bid
2007
+ * operations whose nonce key differs — this is what lets the solver sign
2008
+ * the plain userOpHash while staying committed to the order and to the
2009
+ * session key it bid against.
2010
+ *
2011
+ * @param commitment - The order commitment (`order.id`).
2012
+ * @param sessionKey - The order's session key address (`order.session`).
2013
+ * @returns The 192-bit nonce key as a bigint (pass to `EntryPoint.getNonce`).
2014
+ */
2015
+ static bidNonceKey(commitment, sessionKey) {
2016
+ return BigInt(viem.keccak256(viem.encodePacked(["bytes32", "address"], [commitment, sessionKey]))) & (1n << 192n) - 1n;
2017
+ }
2018
+ /**
2019
+ * Builds the EIP-712 typed-data payload whose digest is the EntryPoint v0.8
2020
+ * `userOpHash` (i.e. `hashTypedData(packedUserOpTypedData(...)) ===
2021
+ * computeUserOpHash(...)`). Signing this typed data is bit-identical to
2022
+ * signing the raw userOpHash, but keeps the full operation visible to
2023
+ * signing infrastructure (hardware wallets, MPC/TEE policy engines) instead
2024
+ * of an opaque 32-byte digest.
2025
+ *
2026
+ * @param userOp - The packed UserOperation to sign (signature field ignored).
2027
+ * @param entryPoint - Address of the EntryPoint v0.8 contract.
2028
+ * @param chainId - Chain ID of the network on which the operation will execute.
2029
+ * @returns A viem `TypedDataDefinition` for the operation.
2030
+ */
2031
+ static packedUserOpTypedData(userOp, entryPoint, chainId) {
2032
+ return {
2033
+ domain: {
2034
+ name: "ERC4337",
2035
+ version: "1",
2036
+ chainId,
2037
+ verifyingContract: entryPoint
2038
+ },
2039
+ types: {
2040
+ PackedUserOperation: [
2041
+ { name: "sender", type: "address" },
2042
+ { name: "nonce", type: "uint256" },
2043
+ { name: "initCode", type: "bytes" },
2044
+ { name: "callData", type: "bytes" },
2045
+ { name: "accountGasLimits", type: "bytes32" },
2046
+ { name: "preVerificationGas", type: "uint256" },
2047
+ { name: "gasFees", type: "bytes32" },
2048
+ { name: "paymasterAndData", type: "bytes" }
2049
+ ]
2050
+ },
2051
+ primaryType: "PackedUserOperation",
2052
+ message: {
2053
+ sender: userOp.sender,
2054
+ nonce: userOp.nonce,
2055
+ initCode: userOp.initCode,
2056
+ callData: userOp.callData,
2057
+ accountGasLimits: userOp.accountGasLimits,
2058
+ preVerificationGas: userOp.preVerificationGas,
2059
+ gasFees: userOp.gasFees,
2060
+ paymasterAndData: userOp.paymasterAndData
2061
+ }
2062
+ };
2063
+ }
2064
+ /**
2065
+ * Computes the EIP-712 struct hash of a `PackedUserOperation`.
2066
+ *
2067
+ * Hashes dynamic fields (`initCode`, `callData`, `paymasterAndData`) before
2068
+ * ABI-encoding so the final hash is a fixed-length 32-byte value.
2069
+ *
2070
+ * @param userOp - The packed UserOperation to hash.
2071
+ * @returns The struct hash as a 32-byte hex string.
2072
+ */
2073
+ static getPackedUserStructHash(userOp) {
2074
+ return viem.keccak256(
2075
+ viem.encodeAbiParameters(
2076
+ viem.parseAbiParameters("bytes32, address, uint256, bytes32, bytes32, bytes32, uint256, bytes32, bytes32"),
2077
+ [
2078
+ PACKED_USEROP_TYPEHASH,
2079
+ userOp.sender,
2080
+ userOp.nonce,
2081
+ viem.keccak256(userOp.initCode),
2082
+ viem.keccak256(userOp.callData),
2083
+ userOp.accountGasLimits,
2084
+ userOp.preVerificationGas,
2085
+ userOp.gasFees,
2086
+ viem.keccak256(userOp.paymasterAndData)
2087
+ ]
2088
+ )
2089
+ );
2090
+ }
2091
+ /**
2092
+ * Packs `verificationGasLimit` and `callGasLimit` into the ERC-4337
2093
+ * `accountGasLimits` bytes32 field.
2094
+ *
2095
+ * The high 16 bytes hold `verificationGasLimit` and the low 16 bytes hold
2096
+ * `callGasLimit`, matching the EntryPoint v0.8 packed representation.
2097
+ *
2098
+ * @param verificationGasLimit - Gas limit for the account verification step.
2099
+ * @param callGasLimit - Gas limit for the main execution call.
2100
+ * @returns A 32-byte hex string with both limits packed.
2101
+ */
2102
+ static packGasLimits(verificationGasLimit, callGasLimit) {
2103
+ const verificationGasHex = viem.pad(viem.toHex(verificationGasLimit), { size: 16 });
2104
+ const callGasHex = viem.pad(viem.toHex(callGasLimit), { size: 16 });
2105
+ return viem.concat([verificationGasHex, callGasHex]);
2106
+ }
2107
+ /**
2108
+ * Packs `maxPriorityFeePerGas` and `maxFeePerGas` into the ERC-4337
2109
+ * `gasFees` bytes32 field.
2110
+ *
2111
+ * The high 16 bytes hold `maxPriorityFeePerGas` and the low 16 bytes hold
2112
+ * `maxFeePerGas`, matching the EntryPoint v0.8 packed representation.
2113
+ *
2114
+ * @param maxPriorityFeePerGas - Maximum tip per gas (EIP-1559).
2115
+ * @param maxFeePerGas - Maximum total fee per gas (EIP-1559).
2116
+ * @returns A 32-byte hex string with both fee values packed.
2117
+ */
2118
+ static packGasFees(maxPriorityFeePerGas, maxFeePerGas) {
2119
+ const priorityFeeHex = viem.pad(viem.toHex(maxPriorityFeePerGas), { size: 16 });
2120
+ const maxFeeHex = viem.pad(viem.toHex(maxFeePerGas), { size: 16 });
2121
+ return viem.concat([priorityFeeHex, maxFeeHex]);
2122
+ }
2123
+ /**
2124
+ * Unpacks the `accountGasLimits` bytes32 field back into its constituent
2125
+ * gas limits.
2126
+ *
2127
+ * @param accountGasLimits - The packed 32-byte gas limits field from a `PackedUserOperation`.
2128
+ * @returns Object with `verificationGasLimit` and `callGasLimit` as bigints.
2129
+ */
2130
+ static unpackGasLimits(accountGasLimits) {
2131
+ const hex = accountGasLimits.slice(2);
2132
+ const verificationGasLimit = BigInt(`0x${hex.slice(0, 32)}`);
2133
+ const callGasLimit = BigInt(`0x${hex.slice(32, 64)}`);
2134
+ return { verificationGasLimit, callGasLimit };
2135
+ }
2136
+ /**
2137
+ * Unpacks the `gasFees` bytes32 field back into its constituent fee values.
2138
+ *
2139
+ * @param gasFees - The packed 32-byte gas fees field from a `PackedUserOperation`.
2140
+ * @returns Object with `maxPriorityFeePerGas` and `maxFeePerGas` as bigints.
2141
+ */
2142
+ static unpackGasFees(gasFees) {
2143
+ const hex = gasFees.slice(2);
2144
+ const maxPriorityFeePerGas = BigInt(`0x${hex.slice(0, 32)}`);
2145
+ const maxFeePerGas = BigInt(`0x${hex.slice(32, 64)}`);
2146
+ return { maxPriorityFeePerGas, maxFeePerGas };
2147
+ }
2148
+ /**
2149
+ * Converts a packed `PackedUserOperation` into the JSON object format
2150
+ * expected by ERC-4337 bundler JSON-RPC endpoints.
2151
+ *
2152
+ * Unpacks `accountGasLimits` and `gasFees`, separates optional factory and
2153
+ * paymaster fields, and converts all numeric fields to hex strings.
2154
+ *
2155
+ * @param userOp - The packed UserOperation to convert.
2156
+ * @returns A plain object safe to pass as the first element of bundler RPC params.
2157
+ */
2158
+ static prepareBundlerCall(userOp) {
2159
+ const { verificationGasLimit, callGasLimit } = _CryptoUtils.unpackGasLimits(userOp.accountGasLimits);
2160
+ const { maxPriorityFeePerGas, maxFeePerGas } = _CryptoUtils.unpackGasFees(userOp.gasFees);
2161
+ const hasFactory = userOp.initCode && userOp.initCode !== "0x" && userOp.initCode.length > 2;
2162
+ const factory = hasFactory ? `0x${userOp.initCode.slice(2, 42)}` : void 0;
2163
+ const factoryData = hasFactory ? `0x${userOp.initCode.slice(42)}` : void 0;
2164
+ const hasPaymaster = userOp.paymasterAndData && userOp.paymasterAndData !== "0x" && userOp.paymasterAndData.length > 2;
2165
+ const pmHex = hasPaymaster ? userOp.paymasterAndData.slice(2) : "";
2166
+ const paymaster = hasPaymaster ? `0x${pmHex.slice(0, 40)}` : void 0;
2167
+ const paymasterVerificationGasLimit = hasPaymaster ? BigInt(`0x${pmHex.slice(40, 72)}`) : void 0;
2168
+ const paymasterPostOpGasLimit = hasPaymaster ? BigInt(`0x${pmHex.slice(72, 104)}`) : void 0;
2169
+ const paymasterData = hasPaymaster ? `0x${pmHex.slice(104)}` : void 0;
2170
+ const userOpBundler = {
2171
+ sender: userOp.sender,
2172
+ nonce: viem.toHex(userOp.nonce),
2173
+ callData: userOp.callData,
2174
+ callGasLimit: viem.toHex(callGasLimit),
2175
+ verificationGasLimit: viem.toHex(verificationGasLimit),
2176
+ preVerificationGas: viem.toHex(userOp.preVerificationGas),
2177
+ maxFeePerGas: viem.toHex(maxFeePerGas),
2178
+ maxPriorityFeePerGas: viem.toHex(maxPriorityFeePerGas),
2179
+ signature: userOp.signature
2180
+ };
2181
+ if (factory) {
2182
+ userOpBundler.factory = factory;
2183
+ userOpBundler.factoryData = factoryData || "0x";
2184
+ }
2185
+ if (paymaster) {
2186
+ userOpBundler.paymaster = paymaster;
2187
+ userOpBundler.paymasterData = paymasterData || "0x";
2188
+ userOpBundler.paymasterVerificationGasLimit = viem.toHex(paymasterVerificationGasLimit);
2189
+ userOpBundler.paymasterPostOpGasLimit = viem.toHex(paymasterPostOpGasLimit);
2190
+ }
2191
+ return userOpBundler;
2192
+ }
2193
+ /**
2194
+ * Sends a JSON-RPC request to the configured ERC-4337 bundler endpoint.
2195
+ *
2196
+ * @param method - The JSON-RPC method name (one of {@link BundlerMethod}).
2197
+ * @param params - Array of parameters for the RPC call.
2198
+ * @returns Resolves with the `result` field of the bundler's JSON-RPC response,
2199
+ * typed as `T`.
2200
+ * @throws If the bundler URL is not configured or the bundler returns an error.
2201
+ */
2202
+ async sendBundler(method, params = []) {
2203
+ if (!this.ctx.bundlerUrl) {
2204
+ throw new Error("Bundler URL not configured");
2205
+ }
2206
+ const response = await fetch(this.ctx.bundlerUrl, {
2207
+ method: "POST",
2208
+ headers: { "Content-Type": "application/json" },
2209
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
2210
+ });
2211
+ const result = await response.json();
2212
+ if (result.error) {
2213
+ throw new Error(`Bundler error: ${result.error.message || JSON.stringify(result.error)}`);
2214
+ }
2215
+ return result.result;
2216
+ }
2217
+ /**
2218
+ * Sends multiple JSON-RPC requests to the bundler in a single HTTP call
2219
+ * using JSON-RPC 2.0 batch syntax. Results are returned in the same order
2220
+ * as the input `requests` array.
2221
+ *
2222
+ * @throws If the bundler URL is not configured, the HTTP call fails, or any
2223
+ * individual response contains an error.
2224
+ */
2225
+ async sendBundlerBatch(requests) {
2226
+ if (!this.ctx.bundlerUrl) {
2227
+ throw new Error("Bundler URL not configured");
2228
+ }
2229
+ const body = requests.map((r, i) => ({
2230
+ jsonrpc: "2.0",
2231
+ id: i + 1,
2232
+ method: r.method,
2233
+ params: r.params
2234
+ }));
2235
+ const response = await fetch(this.ctx.bundlerUrl, {
2236
+ method: "POST",
2237
+ headers: { "Content-Type": "application/json" },
2238
+ body: JSON.stringify(body)
2239
+ });
2240
+ const results = await response.json();
2241
+ results.sort((a, b) => a.id - b.id);
2242
+ return results.map((r) => {
2243
+ if (r.error) {
2244
+ throw new Error(`Bundler error: ${r.error.message || JSON.stringify(r.error)}`);
2245
+ }
2246
+ return r.result;
2247
+ });
2248
+ }
2249
+ /**
2250
+ * Encodes a list of calls into ERC-7821 `execute` calldata using
2251
+ * single-batch mode (`ERC7821_BATCH_MODE`).
2252
+ *
2253
+ * @param calls - Ordered list of calls to batch; each specifies a `target`
2254
+ * address, ETH `value`, and `data`.
2255
+ * @returns ABI-encoded calldata for the ERC-7821 `execute(bytes32,bytes)` function.
2256
+ */
2257
+ encodeERC7821Execute(calls) {
2258
+ const executionData = viem.encodeAbiParameters(
2259
+ [{ type: "tuple[]", components: erc7281_default.ABI[1].components }],
2260
+ [calls.map((call) => ({ target: call.target, value: call.value, data: call.data }))]
2261
+ );
2262
+ return viem.encodeFunctionData({
2263
+ abi: erc7281_default.ABI,
2264
+ functionName: "execute",
2265
+ args: [ERC7821_BATCH_MODE, executionData]
2266
+ });
2267
+ }
2268
+ /**
2269
+ * Decodes ERC-7821 `execute` calldata back into its constituent calls.
2270
+ *
2271
+ * Returns `null` if the calldata does not match the expected `execute`
2272
+ * function signature or cannot be decoded.
2273
+ *
2274
+ * @param callData - Hex-encoded calldata previously produced by
2275
+ * {@link encodeERC7821Execute} or an equivalent encoder.
2276
+ * @returns Array of decoded {@link ERC7821Call} objects, or `null` on failure.
2277
+ */
2278
+ decodeERC7821Execute(callData) {
2279
+ try {
2280
+ const decoded = viem.decodeFunctionData({
2281
+ abi: erc7281_default.ABI,
2282
+ data: callData
2283
+ });
2284
+ if (decoded?.functionName !== "execute" || !decoded.args || decoded.args.length < 2) {
2285
+ return null;
2286
+ }
2287
+ const executionData = decoded.args[1];
2288
+ const [calls] = viem.decodeAbiParameters(
2289
+ [{ type: "tuple[]", components: erc7281_default.ABI[1].components }],
2290
+ executionData
2291
+ );
2292
+ return calls.map((call) => ({
2293
+ target: call.target,
2294
+ value: call.value,
2295
+ data: call.data
2296
+ }));
2297
+ } catch {
2298
+ return null;
2299
+ }
2300
+ }
2301
+ };
2302
+
2303
+ // src/protocols/intents/phantom-aggregation.ts
2304
+ var ENTRY_POINT_V08_ADDRESS = "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108";
1923
2305
  var injectedFetch;
1924
2306
  function setAggregationFetch(fetchImpl) {
1925
2307
  injectedFetch = fetchImpl;
@@ -1993,13 +2375,101 @@ function extractFillData(callData, gatewayAddress) {
1993
2375
  }
1994
2376
  return null;
1995
2377
  }
2378
+ var FILL_ORDER_INPUT = FILL_ORDER_ABI.find(
2379
+ (item) => item?.type === "function" && item?.name === "fillOrder"
2380
+ )?.inputs?.[0];
2381
+ function orderCommitmentFromDecoded(order) {
2382
+ try {
2383
+ if (!FILL_ORDER_INPUT) return null;
2384
+ return viem.keccak256(viem.encodeAbiParameters([FILL_ORDER_INPUT], [order]));
2385
+ } catch {
2386
+ return null;
2387
+ }
2388
+ }
2389
+ var BID_COMMITMENT_BYTES = 32;
2390
+ var SOLVER_SIGNATURE_BYTES = 65;
2391
+ function splitBidSignature(signature) {
2392
+ const raw = signature.replace(/^0x/, "");
2393
+ const commitmentChars = BID_COMMITMENT_BYTES * 2;
2394
+ const end = commitmentChars + SOLVER_SIGNATURE_BYTES * 2;
2395
+ if (raw.length < end) return null;
2396
+ return {
2397
+ commitment: `0x${raw.slice(0, commitmentChars)}`,
2398
+ solverSignature: `0x${raw.slice(commitmentChars, end)}`
2399
+ };
2400
+ }
2401
+ var recoverBidSignerViem = async (userOp, entryPoint, chainId, solverSignature) => {
2402
+ try {
2403
+ const userOpHash = CryptoUtils.computeUserOpHash(userOp, entryPoint, chainId);
2404
+ return await viem.recoverAddress({ hash: userOpHash, signature: solverSignature });
2405
+ } catch {
2406
+ return null;
2407
+ }
2408
+ };
2409
+ var DELEGATION_INDICATOR_PREFIX = "0xef0100";
2410
+ async function isDelegatedToSolverAccount(evmRpcUrl, account, solverAccount) {
2411
+ const response = await rpcCall(evmRpcUrl, {
2412
+ id: 1,
2413
+ jsonrpc: "2.0",
2414
+ method: "eth_getCode",
2415
+ params: [account, "latest"]
2416
+ });
2417
+ const code = typeof response.result === "string" ? response.result.toLowerCase() : "";
2418
+ if (!code.startsWith(DELEGATION_INDICATOR_PREFIX)) return false;
2419
+ return `0x${code.slice(DELEGATION_INDICATOR_PREFIX.length)}` === solverAccount.toLowerCase();
2420
+ }
2421
+ function evmChainId(chain) {
2422
+ const [prefix, id] = chain.split("-");
2423
+ if (prefix !== "EVM" || !id || !/^\d+$/.test(id)) return null;
2424
+ return BigInt(id);
2425
+ }
2426
+ async function isVerifiedSolverBid(params) {
2427
+ const { userOp, commitment, sessionKey, chainId, solverAccount, evmRpcUrl, recoverSigner, bidNonceKey, logger } = params;
2428
+ const solver = userOp.sender;
2429
+ const parsed = splitBidSignature(userOp.signature);
2430
+ if (!parsed) {
2431
+ logger?.warn({ solver, commitment }, "Rejecting phantom bid: malformed userOp signature");
2432
+ return false;
2433
+ }
2434
+ if (parsed.commitment.toLowerCase() !== commitment.toLowerCase()) {
2435
+ logger?.warn(
2436
+ { solver, commitment, signedFor: parsed.commitment },
2437
+ "Rejecting phantom bid: signed for another order"
2438
+ );
2439
+ return false;
2440
+ }
2441
+ if (BigInt(userOp.nonce) >> 64n !== bidNonceKey(commitment, sessionKey)) {
2442
+ logger?.warn({ solver, commitment }, "Rejecting phantom bid: nonce does not bind order and session key");
2443
+ return false;
2444
+ }
2445
+ const signer = await recoverSigner(userOp, ENTRY_POINT_V08_ADDRESS, chainId, parsed.solverSignature);
2446
+ if (!signer || signer.toLowerCase() !== solver.toLowerCase()) {
2447
+ logger?.warn({ solver, commitment, signer }, "Rejecting phantom bid: signature does not recover to the sender");
2448
+ return false;
2449
+ }
2450
+ if (!await isDelegatedToSolverAccount(evmRpcUrl, solver, solverAccount)) {
2451
+ logger?.warn({ solver, commitment, solverAccount }, "Rejecting phantom bid: sender is not a delegated solver");
2452
+ return false;
2453
+ }
2454
+ return true;
2455
+ }
1996
2456
  async function fetchBidsForOrder(nodeUrl, commitment) {
1997
- const data = await rpcCall(nodeUrl, { id: 1, jsonrpc: "2.0", method: "intents_getBidsForOrder", params: [commitment] });
2457
+ const data = await rpcCall(nodeUrl, {
2458
+ id: 1,
2459
+ jsonrpc: "2.0",
2460
+ method: "intents_getBidsForOrder",
2461
+ params: [commitment]
2462
+ });
1998
2463
  return Array.isArray(data.result) ? data.result : [];
1999
2464
  }
2000
2465
  async function ethCallUint(evmRpcUrl, to, data) {
2001
2466
  try {
2002
- const result = await rpcCall(evmRpcUrl, { id: 1, jsonrpc: "2.0", method: "eth_call", params: [{ to, data }, "latest"] });
2467
+ const result = await rpcCall(evmRpcUrl, {
2468
+ id: 1,
2469
+ jsonrpc: "2.0",
2470
+ method: "eth_call",
2471
+ params: [{ to, data }, "latest"]
2472
+ });
2003
2473
  if (result.error || !result.result || result.result === "0x") return 0n;
2004
2474
  return BigInt(result.result);
2005
2475
  } catch {
@@ -2035,14 +2505,23 @@ function toAddress(token) {
2035
2505
  return `0x${addr}`;
2036
2506
  }
2037
2507
  async function aggregatePhantomBids(params) {
2038
- const { nodeUrl, evmRpcUrls, chain, gatewayAddress, commitment, yieldVaults, logger } = params;
2508
+ const { nodeUrl, evmRpcUrls, chain, gatewayAddress, commitment, yieldVaults, solverAccount, logger } = params;
2039
2509
  const extractFill = params.extractFill ?? extractFillData;
2510
+ const recoverSigner = params.recoverSigner ?? recoverBidSignerViem;
2511
+ const bidNonceKey = params.bidNonceKey ?? CryptoUtils.bidNonceKey;
2512
+ const orderCommitment = params.orderCommitment ?? orderCommitmentFromDecoded;
2040
2513
  const destUrl = evmRpcUrls[chain];
2041
2514
  if (!destUrl) return null;
2515
+ const chainId = evmChainId(chain);
2516
+ if (!solverAccount || chainId === null) {
2517
+ logger?.warn({ chain, commitment }, "Cannot verify phantom bids: no SolverAccount or chain id for chain");
2518
+ return null;
2519
+ }
2042
2520
  const bids = await fetchBidsForOrder(nodeUrl, commitment);
2043
2521
  if (bids.length === 0) return null;
2044
2522
  const quotes = [];
2045
2523
  const lpBalances = [];
2524
+ const countedSolvers = /* @__PURE__ */ new Set();
2046
2525
  for (const bid of bids) {
2047
2526
  if (!bid.user_op) continue;
2048
2527
  try {
@@ -2050,6 +2529,34 @@ async function aggregatePhantomBids(params) {
2050
2529
  const solver = decoded.sender;
2051
2530
  const fillData = extractFill(decoded.callData, gatewayAddress);
2052
2531
  if (!fillData) continue;
2532
+ const decodedCommitment = orderCommitment(fillData.order);
2533
+ if (!decodedCommitment || decodedCommitment.toLowerCase() !== commitment.toLowerCase()) {
2534
+ logger?.warn({ solver, commitment }, "Rejecting phantom bid: calldata order is not the indexed order");
2535
+ continue;
2536
+ }
2537
+ const sessionKey = fillData.order?.session;
2538
+ if (!sessionKey) {
2539
+ logger?.warn({ solver, commitment }, "Rejecting phantom bid: order carries no session key");
2540
+ continue;
2541
+ }
2542
+ const verified = await isVerifiedSolverBid({
2543
+ userOp: decoded,
2544
+ commitment,
2545
+ sessionKey,
2546
+ chainId,
2547
+ solverAccount,
2548
+ evmRpcUrl: destUrl,
2549
+ recoverSigner,
2550
+ bidNonceKey,
2551
+ logger
2552
+ });
2553
+ if (!verified) continue;
2554
+ const normalizedSolver = solver.toLowerCase();
2555
+ if (countedSolvers.has(normalizedSolver)) {
2556
+ logger?.warn({ solver, commitment }, "Skipping phantom bid: solver already counted for this order");
2557
+ continue;
2558
+ }
2559
+ countedSolvers.add(normalizedSolver);
2053
2560
  const outputTokenAddress = toAddress(fillData.outputToken);
2054
2561
  const weight = await getTotalSolverBalance(destUrl, chain, outputTokenAddress, solver, yieldVaults);
2055
2562
  quotes.push({ price: fillData.solverAmount, weight });
@@ -2059,16 +2566,17 @@ async function aggregatePhantomBids(params) {
2059
2566
  }
2060
2567
  }
2061
2568
  if (quotes.length === 0) return null;
2062
- const sortedPrices = quotes.map((q) => q.price).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
2569
+ const medianPrice = weightedMedian(quotes);
2063
2570
  return {
2064
- lowestPrice: sortedPrices[0],
2065
- highestPrice: sortedPrices[sortedPrices.length - 1],
2066
- medianPrice: weightedMedian(quotes),
2571
+ lowestPrice: medianPrice,
2572
+ highestPrice: medianPrice,
2573
+ medianPrice,
2067
2574
  bidCount: quotes.length,
2068
2575
  lpBalances
2069
2576
  };
2070
2577
  }
2071
2578
 
2579
+ exports.ENTRY_POINT_V08_ADDRESS = ENTRY_POINT_V08_ADDRESS;
2072
2580
  exports.FILL_ORDER_ABI = FILL_ORDER_ABI;
2073
2581
  exports.IntentGatewayV2 = IntentGatewayV2_default;
2074
2582
  exports.aggregatePhantomBids = aggregatePhantomBids;
@@ -2078,7 +2586,10 @@ exports.encodeERC7821ExecuteBatch = encodeERC7821ExecuteBatch;
2078
2586
  exports.encodeUserOpScale = encodeUserOpScale;
2079
2587
  exports.extractFillData = extractFillData;
2080
2588
  exports.fetchBidsForOrder = fetchBidsForOrder;
2589
+ exports.orderCommitmentFromDecoded = orderCommitmentFromDecoded;
2590
+ exports.recoverBidSignerViem = recoverBidSignerViem;
2081
2591
  exports.setAggregationFetch = setAggregationFetch;
2592
+ exports.splitBidSignature = splitBidSignature;
2082
2593
  exports.weightedMedian = weightedMedian;
2083
2594
  //# sourceMappingURL=intents-helpers.cjs.map
2084
2595
  //# sourceMappingURL=intents-helpers.cjs.map