@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.
- package/dist/browser/index.d.ts +87 -12
- package/dist/browser/index.js +662 -92
- package/dist/browser/index.js.map +1 -1
- package/dist/node/{IntentGatewayV2-PzbhFn_7.d.cts → IntentGatewayV2-tYGohDdW.d.cts} +62 -5
- package/dist/node/{IntentGatewayV2-PzbhFn_7.d.ts → IntentGatewayV2-tYGohDdW.d.ts} +62 -5
- package/dist/node/index.cjs +661 -90
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +28 -10
- package/dist/node/index.d.ts +28 -10
- package/dist/node/index.js +662 -92
- package/dist/node/index.js.map +1 -1
- package/dist/node/intents-helpers.cjs +518 -7
- package/dist/node/intents-helpers.cjs.map +1 -1
- package/dist/node/intents-helpers.d.cts +52 -10
- package/dist/node/intents-helpers.d.ts +52 -10
- package/dist/node/intents-helpers.js +516 -9
- package/dist/node/intents-helpers.js.map +1 -1
- package/package.json +1 -1
|
@@ -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, {
|
|
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, {
|
|
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
|
|
2569
|
+
const medianPrice = weightedMedian(quotes);
|
|
2063
2570
|
return {
|
|
2064
|
-
lowestPrice:
|
|
2065
|
-
highestPrice:
|
|
2066
|
-
medianPrice
|
|
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
|