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