@hyperbridge/sdk 2.2.0 → 2.2.2

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.
@@ -2579,7 +2579,7 @@ var chainConfigs = {
2579
2579
  USDT: { balanceSlot: 151, allowanceSlot: 152 }
2580
2580
  },
2581
2581
  addresses: {
2582
- IntentGateway: "0xFbF50B2b32768127603cC9eF4b871574b881b8eD",
2582
+ IntentGateway: "0xE13fB34CAe12505ae51BaC8C405AF8EB27AC8058",
2583
2583
  TokenGateway: "0xFcDa26cA021d5535C3059547390E6cCd8De7acA6",
2584
2584
  Host: "0xEB944071A9Bf22810757C5BcFf7a2aE9663a311D",
2585
2585
  UniswapRouter02: "0x9639379819420704457B07A0C33B678D9E0F8Df0",
@@ -2589,7 +2589,7 @@ var chainConfigs = {
2589
2589
  UniswapV3Quoter: "0x0000000000000000000000000000000000000000",
2590
2590
  UniswapV4Quoter: "0x0000000000000000000000000000000000000000",
2591
2591
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2592
- SolverAccount: "0xCDFcFeD7A14154846808FddC8Ba971A2f8a830a3"
2592
+ SolverAccount: "0x0b5cfBc16ef60AD6930ba5A90Bb09475B7BF3815"
2593
2593
  },
2594
2594
  rpcEnvKey: "BSC_CHAPEL",
2595
2595
  defaultRpcUrl: "https://bnb-testnet.api.onfinality.io/public",
@@ -2696,8 +2696,8 @@ var chainConfigs = {
2696
2696
  DAI: { balanceSlot: 0, allowanceSlot: 0 }
2697
2697
  },
2698
2698
  addresses: {
2699
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
2700
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
2699
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2700
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
2701
2701
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2702
2702
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2703
2703
  UniswapRouter02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
@@ -2751,8 +2751,8 @@ var chainConfigs = {
2751
2751
  DAI: { balanceSlot: 0, allowanceSlot: 0 }
2752
2752
  },
2753
2753
  addresses: {
2754
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
2755
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
2754
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2755
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
2756
2756
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2757
2757
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2758
2758
  UniswapRouter02: "0x10ED43C718714eb63d5aA57B78B54704E256024E",
@@ -2807,8 +2807,8 @@ var chainConfigs = {
2807
2807
  DAI: { balanceSlot: 0, allowanceSlot: 0 }
2808
2808
  },
2809
2809
  addresses: {
2810
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
2811
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
2810
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2811
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
2812
2812
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2813
2813
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2814
2814
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2864,8 +2864,8 @@ var chainConfigs = {
2864
2864
  DAI: { balanceSlot: 0, allowanceSlot: 0 }
2865
2865
  },
2866
2866
  addresses: {
2867
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
2868
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
2867
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2868
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
2869
2869
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2870
2870
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2871
2871
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2889,6 +2889,7 @@ var chainConfigs = {
2889
2889
  consensusStateId: "ETH0",
2890
2890
  coingeckoId: "base",
2891
2891
  layerZeroEid: 30184,
2892
+ uniswapV4Pools: [{ tokens: ["USDC", "cNGN"], fee: 1500, tickSpacing: 30 }],
2892
2893
  popularTokens: [
2893
2894
  "0x4200000000000000000000000000000000000006",
2894
2895
  "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2922,8 +2923,8 @@ var chainConfigs = {
2922
2923
  DAI: { balanceSlot: 0, allowanceSlot: 0 }
2923
2924
  },
2924
2925
  addresses: {
2925
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
2926
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
2926
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2927
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
2927
2928
  TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E",
2928
2929
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2929
2930
  UniswapRouter02: "0xd2f9496824951D5237cC71245D659E48d0d5f9E8",
@@ -2993,7 +2994,7 @@ var chainConfigs = {
2993
2994
  //wmatic, change it to wpol
2994
2995
  DAI: "0x0000000000000000000000000000000000000000",
2995
2996
  USDC: "0x693b854d6965ffeaae21c74049dea644b56fcacb",
2996
- USDT: "0x693b854d6965ffeaae21c74049dea644b56fcacb"
2997
+ USDT: "0x0000000000000000000000000000000000000000"
2997
2998
  },
2998
2999
  tokenDecimals: {
2999
3000
  USDC: 18,
@@ -3004,13 +3005,13 @@ var chainConfigs = {
3004
3005
  USDC: { balanceSlot: 1, allowanceSlot: 2 }
3005
3006
  },
3006
3007
  addresses: {
3007
- IntentGateway: "0xFbF50B2b32768127603cC9eF4b871574b881b8eD",
3008
+ IntentGateway: "0xE13fB34CAe12505ae51BaC8C405AF8EB27AC8058",
3008
3009
  TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E",
3009
3010
  Host: "0xEB944071A9Bf22810757C5BcFf7a2aE9663a311D",
3010
3011
  Calldispatcher: "0x876F1891982E260026630c233A4897160A281Fb8",
3011
3012
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
3012
3013
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
3013
- SolverAccount: "0xCDFcFeD7A14154846808FddC8Ba971A2f8a830a3"
3014
+ SolverAccount: "0x0b5cfBc16ef60AD6930ba5A90Bb09475B7BF3815"
3014
3015
  },
3015
3016
  rpcEnvKey: "POLYGON_AMOY",
3016
3017
  defaultRpcUrl: "https://rpc-amoy.polygon.technology",
@@ -3033,8 +3034,8 @@ var chainConfigs = {
3033
3034
  USDT: 6
3034
3035
  },
3035
3036
  addresses: {
3036
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
3037
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
3037
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3038
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
3038
3039
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3039
3040
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3040
3041
  UniswapRouter02: "0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2",
@@ -3068,8 +3069,8 @@ var chainConfigs = {
3068
3069
  USDT: 6
3069
3070
  },
3070
3071
  addresses: {
3071
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
3072
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
3072
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3073
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
3073
3074
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3074
3075
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3075
3076
  UniswapRouter02: "0xB2e26652e4BAd1e56055A051f922E06760cA0BFE",
@@ -3223,8 +3224,8 @@ var chainConfigs = {
3223
3224
  USDT: 6
3224
3225
  },
3225
3226
  addresses: {
3226
- IntentGateway: "0x16F9E57f735bBfF9f6c4E5276330f9c437d0e9E0",
3227
- SolverAccount: "0xB92A51A609e85f8316004a6da9feaB4421c01b43",
3227
+ IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3228
+ SolverAccount: "0x975e80B476cB1d4Cd06c292ce36898f2bE4159ea",
3228
3229
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3229
3230
  Calldispatcher: "0xE2C7e576E26E0bE7aC97c6fE925bcDAbD87c4bEd"
3230
3231
  },
@@ -5102,6 +5103,9 @@ var ChainConfigService = class {
5102
5103
  getDaiAsset(chain) {
5103
5104
  return this.getConfig(chain)?.assets?.DAI ?? "0x";
5104
5105
  }
5106
+ getAssetAddress(chain, symbol) {
5107
+ return this.getConfig(chain)?.assets?.[symbol];
5108
+ }
5105
5109
  getUsdtAsset(chain) {
5106
5110
  return this.getConfig(chain)?.assets?.USDT ?? "0x";
5107
5111
  }
@@ -5170,6 +5174,9 @@ var ChainConfigService = class {
5170
5174
  getUniswapV4StateViewAddress(chain) {
5171
5175
  return this.getConfig(chain)?.addresses.UniswapV4StateView ?? "0x";
5172
5176
  }
5177
+ getUniswapV4PoolConfigs(chain) {
5178
+ return this.getConfig(chain)?.uniswapV4Pools ?? [];
5179
+ }
5173
5180
  getPermit2Address(chain) {
5174
5181
  return this.getConfig(chain)?.addresses.Permit2 ?? "0x";
5175
5182
  }
@@ -5180,7 +5187,7 @@ var ChainConfigService = class {
5180
5187
  return this.getConfig(chain)?.coingeckoId;
5181
5188
  }
5182
5189
  getEtherscanApiKey() {
5183
- return typeof process !== "undefined" ? process?.env?.ETHERSCAN_API_KEY : void 0;
5190
+ return typeof process !== "undefined" ? process.env?.ETHERSCAN_API_KEY : void 0;
5184
5191
  }
5185
5192
  getCalldispatcherAddress(chain) {
5186
5193
  return this.getConfig(chain)?.addresses.Calldispatcher ?? "0x";
@@ -9549,7 +9556,28 @@ function adjustDecimals(feeInFeeToken, fromDecimals, toDecimals) {
9549
9556
  }
9550
9557
  }
9551
9558
  var USE_ETHERSCAN_CHAINS = /* @__PURE__ */ new Set(["EVM-137", "EVM-56", "EVM-1"]);
9552
- var TESTNET_CHAINS = /* @__PURE__ */ new Set(["EVM-10200", "EVM-97"]);
9559
+ var TESTNET_CHAINS = /* @__PURE__ */ new Set([
9560
+ "EVM-97",
9561
+ // BSC Chapel
9562
+ "EVM-10200",
9563
+ // Gnosis Chiado
9564
+ "EVM-11155111",
9565
+ // Sepolia
9566
+ "EVM-421614",
9567
+ // Arbitrum Sepolia
9568
+ "EVM-84532",
9569
+ // Base Sepolia
9570
+ "EVM-11155420",
9571
+ // Optimism Sepolia
9572
+ "EVM-80002",
9573
+ // Polygon Amoy
9574
+ "EVM-420420417",
9575
+ // Polkadot Asset Hub Paseo
9576
+ "EVM-3448148188",
9577
+ // Tron Nile
9578
+ "EVM-688689"
9579
+ // Pharos Atlantic
9580
+ ]);
9553
9581
  function collectCallInputsByAddress(call, targetContractAddress, acc) {
9554
9582
  const normalizedTarget = targetContractAddress.toLowerCase();
9555
9583
  if (call.calls && Array.isArray(call.calls)) {
@@ -9864,6 +9892,48 @@ var GetRequestClient = class {
9864
9892
  async buildFinalized(request, hyperbridgeDelivered, response) {
9865
9893
  const sourceChain = this.ctx.config.source;
9866
9894
  const hyperbridge = this.ctx.config.hyperbridge;
9895
+ const { config } = hyperbridge;
9896
+ const finality = await this.queries.queryStateMachineUpdateByHeight({
9897
+ statemachineId: config.stateMachineId,
9898
+ height: hyperbridgeDelivered.metadata.blockNumber,
9899
+ chain: config.stateMachineId
9900
+ });
9901
+ if (finality) {
9902
+ const proof = await hyperbridge.queryProof(
9903
+ { Responses: [response.commitment] },
9904
+ request.source,
9905
+ BigInt(finality.height)
9906
+ );
9907
+ const calldata = sourceChain.encode({
9908
+ kind: "GetResponse",
9909
+ proof: {
9910
+ stateMachine: config.stateMachineId,
9911
+ consensusStateId: config.consensusStateId,
9912
+ proof,
9913
+ height: BigInt(finality.height)
9914
+ },
9915
+ responses: [
9916
+ {
9917
+ get: request,
9918
+ values: request.keys.map((key, index) => ({
9919
+ key,
9920
+ value: response.values[index] || "0x"
9921
+ }))
9922
+ }
9923
+ ],
9924
+ signer: viem.pad("0x")
9925
+ });
9926
+ return {
9927
+ status: RequestStatus.HYPERBRIDGE_FINALIZED,
9928
+ metadata: {
9929
+ blockHash: finality.blockHash,
9930
+ blockNumber: finality.height,
9931
+ transactionHash: finality.transactionHash,
9932
+ timestamp: finality.timestamp,
9933
+ calldata
9934
+ }
9935
+ };
9936
+ }
9867
9937
  if (sourceChain instanceof EvmChain) {
9868
9938
  const hyperbridgeSubstrate = hyperbridge;
9869
9939
  const currentEpoch = await sourceChain.currentEpoch();
@@ -9872,18 +9942,18 @@ var GetRequestClient = class {
9872
9942
  currentEpoch
9873
9943
  );
9874
9944
  if (!consensusResult) return void 0;
9875
- const proof2 = await hyperbridge.queryProof(
9945
+ const proof = await hyperbridge.queryProof(
9876
9946
  { Responses: [response.commitment] },
9877
9947
  request.source,
9878
9948
  consensusResult.provenHeight
9879
9949
  );
9880
- const calldata2 = sourceChain.encode({
9950
+ const calldata = sourceChain.encode({
9881
9951
  kind: "BatchConsensusAndGetResponse",
9882
9952
  consensusProofs: consensusResult.proofs,
9883
9953
  proof: {
9884
- stateMachine: this.ctx.config.hyperbridge.config.stateMachineId,
9885
- consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
9886
- proof: proof2,
9954
+ stateMachine: config.stateMachineId,
9955
+ consensusStateId: config.consensusStateId,
9956
+ proof,
9887
9957
  height: consensusResult.provenHeight
9888
9958
  },
9889
9959
  responses: [
@@ -9905,50 +9975,11 @@ var GetRequestClient = class {
9905
9975
  transactionHash: hyperbridgeDelivered.metadata.transactionHash,
9906
9976
  // @ts-ignore
9907
9977
  timestamp: hyperbridgeDelivered.metadata.timestamp,
9908
- calldata: calldata2
9978
+ calldata
9909
9979
  }
9910
9980
  };
9911
9981
  }
9912
- const hyperbridgeFinality = await this.queries.queryStateMachineUpdateByHeight({
9913
- statemachineId: this.ctx.config.hyperbridge.config.stateMachineId,
9914
- height: hyperbridgeDelivered.metadata.blockNumber,
9915
- chain: request.source
9916
- });
9917
- if (!hyperbridgeFinality) return void 0;
9918
- const proof = await hyperbridge.queryProof(
9919
- { Responses: [response.commitment] },
9920
- request.source,
9921
- BigInt(hyperbridgeFinality.height)
9922
- );
9923
- const calldata = sourceChain.encode({
9924
- kind: "GetResponse",
9925
- proof: {
9926
- stateMachine: this.ctx.config.hyperbridge.config.stateMachineId,
9927
- consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
9928
- proof,
9929
- height: BigInt(hyperbridgeFinality.height)
9930
- },
9931
- responses: [
9932
- {
9933
- get: request,
9934
- values: request.keys.map((key, index) => ({
9935
- key,
9936
- value: response.values[index] || "0x"
9937
- }))
9938
- }
9939
- ],
9940
- signer: viem.pad("0x")
9941
- });
9942
- return {
9943
- status: RequestStatus.HYPERBRIDGE_FINALIZED,
9944
- metadata: {
9945
- blockHash: hyperbridgeFinality.blockHash,
9946
- blockNumber: hyperbridgeFinality.height,
9947
- transactionHash: hyperbridgeFinality.transactionHash,
9948
- timestamp: hyperbridgeFinality.timestamp,
9949
- calldata
9950
- }
9951
- };
9982
+ return void 0;
9952
9983
  }
9953
9984
  /**
9954
9985
  * Streaming helper: waits (via `waitOrAbort`) for the consensus proof or
@@ -9963,7 +9994,12 @@ var GetRequestClient = class {
9963
9994
  const hyperbridge = this.ctx.config.hyperbridge;
9964
9995
  const stateMachineId = this.ctx.config.hyperbridge.config.stateMachineId;
9965
9996
  const neededHeight = BigInt(request.statuses[hyperbridgeDeliveredIndex].metadata.blockNumber);
9966
- if (sourceChain instanceof EvmChain) {
9997
+ let finality = await this.queries.queryStateMachineUpdateByHeight({
9998
+ statemachineId: stateMachineId,
9999
+ height: Number(neededHeight),
10000
+ chain: stateMachineId
10001
+ });
10002
+ if (!finality && sourceChain instanceof EvmChain) {
9967
10003
  const hyperbridgeSubstrate = hyperbridge;
9968
10004
  const currentEpoch = await sourceChain.currentEpoch();
9969
10005
  const consensusResult = await waitOrAbort(this.ctx, {
@@ -10007,18 +10043,20 @@ var GetRequestClient = class {
10007
10043
  }
10008
10044
  };
10009
10045
  }
10010
- const hyperbridgeFinalized = await waitOrAbort(this.ctx, {
10011
- signal,
10012
- promise: () => this.queries.queryStateMachineUpdateByHeight({
10013
- statemachineId: stateMachineId,
10014
- height: Number(neededHeight),
10015
- chain: request.source
10016
- })
10017
- });
10046
+ if (!finality) {
10047
+ finality = await waitOrAbort(this.ctx, {
10048
+ signal,
10049
+ promise: () => this.queries.queryStateMachineUpdateByHeight({
10050
+ statemachineId: stateMachineId,
10051
+ height: Number(neededHeight),
10052
+ chain: stateMachineId
10053
+ })
10054
+ });
10055
+ }
10018
10056
  const proof = await hyperbridge.queryProof(
10019
10057
  { Responses: [response?.commitment] },
10020
10058
  request.source,
10021
- BigInt(hyperbridgeFinalized.height)
10059
+ BigInt(finality.height)
10022
10060
  );
10023
10061
  const calldata = sourceChain.encode({
10024
10062
  kind: "GetResponse",
@@ -10026,7 +10064,7 @@ var GetRequestClient = class {
10026
10064
  stateMachine: stateMachineId,
10027
10065
  consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10028
10066
  proof,
10029
- height: BigInt(hyperbridgeFinalized.height)
10067
+ height: BigInt(finality.height)
10030
10068
  },
10031
10069
  responses: [
10032
10070
  {
@@ -10042,10 +10080,10 @@ var GetRequestClient = class {
10042
10080
  return {
10043
10081
  status: RequestStatus.HYPERBRIDGE_FINALIZED,
10044
10082
  metadata: {
10045
- blockHash: hyperbridgeFinalized.blockHash,
10046
- blockNumber: hyperbridgeFinalized.height,
10047
- transactionHash: hyperbridgeFinalized.transactionHash,
10048
- timestamp: hyperbridgeFinalized.timestamp,
10083
+ blockHash: finality.blockHash,
10084
+ blockNumber: finality.height,
10085
+ transactionHash: finality.transactionHash,
10086
+ timestamp: finality.timestamp,
10049
10087
  calldata
10050
10088
  }
10051
10089
  };
@@ -10544,6 +10582,40 @@ var PostRequestClient = class {
10544
10582
  async buildFinalized(request, hyperbridgeDelivered) {
10545
10583
  const destChain = this.ctx.config.dest;
10546
10584
  const hyperbridge = this.ctx.config.hyperbridge;
10585
+ const { config } = hyperbridge;
10586
+ const finality = await this.queries.queryStateMachineUpdateByHeight({
10587
+ statemachineId: config.stateMachineId,
10588
+ height: hyperbridgeDelivered.metadata.blockNumber,
10589
+ chain: config.stateMachineId
10590
+ });
10591
+ if (finality) {
10592
+ const proof = await hyperbridge.queryProof(
10593
+ { Requests: [postRequestCommitment(request).commitment] },
10594
+ request.dest,
10595
+ BigInt(finality.height)
10596
+ );
10597
+ const calldata = destChain.encode({
10598
+ kind: "PostRequest",
10599
+ proof: {
10600
+ stateMachine: config.stateMachineId,
10601
+ consensusStateId: config.consensusStateId,
10602
+ proof,
10603
+ height: BigInt(finality.height)
10604
+ },
10605
+ requests: [request],
10606
+ signer: viem.pad("0x")
10607
+ });
10608
+ return {
10609
+ status: RequestStatus.HYPERBRIDGE_FINALIZED,
10610
+ metadata: {
10611
+ blockHash: finality.blockHash,
10612
+ blockNumber: finality.height,
10613
+ transactionHash: finality.transactionHash,
10614
+ timestamp: finality.timestamp,
10615
+ calldata
10616
+ }
10617
+ };
10618
+ }
10547
10619
  if (destChain instanceof EvmChain) {
10548
10620
  const hyperbridgeSubstrate = hyperbridge;
10549
10621
  const currentEpoch = await destChain.currentEpoch();
@@ -10552,18 +10624,18 @@ var PostRequestClient = class {
10552
10624
  currentEpoch
10553
10625
  );
10554
10626
  if (!consensusResult) return void 0;
10555
- const proof2 = await hyperbridge.queryProof(
10627
+ const proof = await hyperbridge.queryProof(
10556
10628
  { Requests: [postRequestCommitment(request).commitment] },
10557
10629
  request.dest,
10558
10630
  consensusResult.provenHeight
10559
10631
  );
10560
- const calldata2 = destChain.encode({
10632
+ const calldata = destChain.encode({
10561
10633
  kind: "BatchConsensusAndPostRequest",
10562
10634
  consensusProofs: consensusResult.proofs,
10563
10635
  proof: {
10564
- stateMachine: this.ctx.config.hyperbridge.config.stateMachineId,
10565
- consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10566
- proof: proof2,
10636
+ stateMachine: config.stateMachineId,
10637
+ consensusStateId: config.consensusStateId,
10638
+ proof,
10567
10639
  height: consensusResult.provenHeight
10568
10640
  },
10569
10641
  requests: [request],
@@ -10577,42 +10649,11 @@ var PostRequestClient = class {
10577
10649
  transactionHash: hyperbridgeDelivered.metadata.transactionHash,
10578
10650
  // @ts-ignore
10579
10651
  timestamp: hyperbridgeDelivered.metadata.timestamp,
10580
- calldata: calldata2
10652
+ calldata
10581
10653
  }
10582
10654
  };
10583
10655
  }
10584
- const hyperbridgeFinality = await this.queries.queryStateMachineUpdateByHeight({
10585
- statemachineId: this.ctx.config.hyperbridge.config.stateMachineId,
10586
- height: hyperbridgeDelivered.metadata.blockNumber,
10587
- chain: request.dest
10588
- });
10589
- if (!hyperbridgeFinality) return void 0;
10590
- const proof = await hyperbridge.queryProof(
10591
- { Requests: [postRequestCommitment(request).commitment] },
10592
- request.dest,
10593
- BigInt(hyperbridgeFinality.height)
10594
- );
10595
- const calldata = destChain.encode({
10596
- kind: "PostRequest",
10597
- proof: {
10598
- stateMachine: this.ctx.config.hyperbridge.config.stateMachineId,
10599
- consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10600
- proof,
10601
- height: BigInt(hyperbridgeFinality.height)
10602
- },
10603
- requests: [request],
10604
- signer: viem.pad("0x")
10605
- });
10606
- return {
10607
- status: RequestStatus.HYPERBRIDGE_FINALIZED,
10608
- metadata: {
10609
- blockHash: hyperbridgeFinality.blockHash,
10610
- blockNumber: hyperbridgeFinality.height,
10611
- transactionHash: hyperbridgeFinality.transactionHash,
10612
- timestamp: hyperbridgeFinality.timestamp,
10613
- calldata
10614
- }
10615
- };
10656
+ return void 0;
10616
10657
  }
10617
10658
  /**
10618
10659
  * Streaming helper: waits for the consensus proof, fetches the messaging
@@ -10626,14 +10667,19 @@ var PostRequestClient = class {
10626
10667
  const stateMachineId = this.ctx.config.hyperbridge.config.stateMachineId;
10627
10668
  const neededHeight = BigInt(request.statuses[hyperbridgeDeliveredIndex].metadata.blockNumber);
10628
10669
  this.logger.trace(`[streamFinalized] neededHeight=${neededHeight}`);
10629
- if (destChain instanceof EvmChain) {
10670
+ const commitment = postRequestCommitment(request).commitment;
10671
+ let finality = await this.queries.queryStateMachineUpdateByHeight({
10672
+ statemachineId: stateMachineId,
10673
+ height: Number(neededHeight),
10674
+ chain: stateMachineId
10675
+ });
10676
+ if (!finality && destChain instanceof EvmChain) {
10630
10677
  const hyperbridgeSubstrate = hyperbridge;
10631
10678
  const currentEpoch = await destChain.currentEpoch();
10632
10679
  const consensusResult = await waitOrAbort(this.ctx, {
10633
10680
  signal,
10634
10681
  promise: () => hyperbridgeSubstrate.queryConsensusProofs(neededHeight, currentEpoch)
10635
10682
  });
10636
- const commitment = postRequestCommitment(request).commitment;
10637
10683
  this.logger.trace(
10638
10684
  `[streamFinalized] consensusProofs found (${consensusResult.proofs.length} proofs), provenHeight=${consensusResult.provenHeight}, commitment=${commitment}, dest=${request.dest}`
10639
10685
  );
@@ -10665,45 +10711,45 @@ var PostRequestClient = class {
10665
10711
  }
10666
10712
  };
10667
10713
  }
10668
- const hyperbridgeFinalized = await waitOrAbort(this.ctx, {
10669
- signal,
10670
- promise: () => this.queries.queryStateMachineUpdateByHeight({
10671
- statemachineId: stateMachineId,
10672
- height: Number(neededHeight),
10673
- chain: request.dest
10674
- })
10675
- });
10714
+ if (!finality) {
10715
+ finality = await waitOrAbort(this.ctx, {
10716
+ signal,
10717
+ promise: () => this.queries.queryStateMachineUpdateByHeight({
10718
+ statemachineId: stateMachineId,
10719
+ height: Number(neededHeight),
10720
+ chain: stateMachineId
10721
+ })
10722
+ });
10723
+ }
10676
10724
  const proof = await this.fetchProofWithRetry(
10677
10725
  signal,
10678
- () => hyperbridge.queryProof(
10679
- { Requests: [postRequestCommitment(request).commitment] },
10680
- request.dest,
10681
- BigInt(hyperbridgeFinalized.height)
10682
- )
10726
+ () => hyperbridge.queryProof({ Requests: [commitment] }, request.dest, BigInt(finality.height))
10683
10727
  );
10728
+ if (!(destChain instanceof EvmChain)) {
10729
+ const { stateId } = parseStateMachineId(stateMachineId);
10730
+ await waitForChallengePeriod(destChain, {
10731
+ height: BigInt(finality.height),
10732
+ id: { stateId, consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId }
10733
+ });
10734
+ }
10684
10735
  const calldata = destChain.encode({
10685
10736
  kind: "PostRequest",
10686
10737
  proof: {
10687
10738
  stateMachine: stateMachineId,
10688
10739
  consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10689
10740
  proof,
10690
- height: BigInt(hyperbridgeFinalized.height)
10741
+ height: BigInt(finality.height)
10691
10742
  },
10692
10743
  requests: [request],
10693
10744
  signer: viem.pad("0x")
10694
10745
  });
10695
- const { stateId } = parseStateMachineId(stateMachineId);
10696
- await waitForChallengePeriod(destChain, {
10697
- height: BigInt(hyperbridgeFinalized.height),
10698
- id: { stateId, consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId }
10699
- });
10700
10746
  return {
10701
10747
  status: RequestStatus.HYPERBRIDGE_FINALIZED,
10702
10748
  metadata: {
10703
- blockHash: hyperbridgeFinalized.blockHash,
10704
- blockNumber: hyperbridgeFinalized.height,
10705
- transactionHash: hyperbridgeFinalized.transactionHash,
10706
- timestamp: hyperbridgeFinalized.timestamp,
10749
+ blockHash: finality.blockHash,
10750
+ blockNumber: finality.height,
10751
+ transactionHash: finality.transactionHash,
10752
+ timestamp: finality.timestamp,
10707
10753
  calldata
10708
10754
  }
10709
10755
  };
@@ -14626,6 +14672,7 @@ function orderCommitment(order) {
14626
14672
  return viem.keccak256(encoded);
14627
14673
  }
14628
14674
  async function convertGasToFeeToken(ctx, gasEstimate, gasEstimateIn, evmChainID, gasPriceOverride) {
14675
+ if (TESTNET_CHAINS.has(evmChainID)) return 1n;
14629
14676
  const chain = ctx[gasEstimateIn];
14630
14677
  const client = chain.client;
14631
14678
  const gasPrice = gasPriceOverride ?? await retryPromise(() => client.getGasPrice(), {
@@ -14660,6 +14707,7 @@ async function convertGasToFeeToken(ctx, gasEstimate, gasEstimateIn, evmChainID,
14660
14707
  }
14661
14708
  }
14662
14709
  async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID) {
14710
+ if (TESTNET_CHAINS.has(evmChainID)) return 1n;
14663
14711
  const chain = ctx[feeTokenIn];
14664
14712
  const client = chain.client;
14665
14713
  const wethAddr = chain.configService.getWrappedNativeAssetWithDecimals(evmChainID).asset;
@@ -14775,16 +14823,16 @@ var OrderPlacer = class {
14775
14823
  return { order, receipt };
14776
14824
  }
14777
14825
  };
14826
+
14827
+ // src/protocols/intents/OrderExecutor.ts
14778
14828
  var USED_USEROPS_STORAGE_KEY = (commitment) => `used-userops:${commitment.toLowerCase()}`;
14779
14829
  var OrderExecutor = class {
14780
- constructor(ctx, bidManager, crypto) {
14830
+ constructor(ctx, bidManager) {
14781
14831
  this.ctx = ctx;
14782
14832
  this.bidManager = bidManager;
14783
- this.crypto = crypto;
14784
14833
  }
14785
14834
  ctx;
14786
14835
  bidManager;
14787
- crypto;
14788
14836
  /**
14789
14837
  * Sleeps until the order's block deadline is reached, then yields EXPIRED.
14790
14838
  * Uses the chain's block time to calculate the sleep duration.
@@ -14855,17 +14903,6 @@ var OrderExecutor = class {
14855
14903
  }
14856
14904
  return fetchedBids;
14857
14905
  }
14858
- /**
14859
- * Selects the best bid from the provided candidates, submits the
14860
- * UserOperation, and persists the dedup entry to prevent resubmission.
14861
- */
14862
- async submitBid(params) {
14863
- const { order, freshBids, sessionPrivateKey, commitment, usedUserOps, userOpHashKey } = params;
14864
- const result = await this.bidManager.selectBid(order, freshBids, sessionPrivateKey);
14865
- usedUserOps.add(userOpHashKey(result.userOp));
14866
- await this.persistUsedUserOps(commitment, usedUserOps);
14867
- return result;
14868
- }
14869
14906
  /**
14870
14907
  * Processes a fill result and returns updated fill accumulators,
14871
14908
  * the status update to yield (if any), and whether the order is
@@ -14933,7 +14970,15 @@ var OrderExecutor = class {
14933
14970
  }
14934
14971
  /**
14935
14972
  * Executes an intent order by racing bid polling against the order's
14936
- * block deadline. Yields status updates at each lifecycle stage.
14973
+ * block deadline. Yields status updates at each lifecycle stage and hands
14974
+ * bid selection to the consumer.
14975
+ *
14976
+ * This is a **bidirectional** generator: when it yields `BIDS_RECEIVED`, the
14977
+ * consumer picks a bid, calls `bid.execute()`, and feeds the resulting
14978
+ * {@link SelectBidResult} back via `gen.next(result)`. The generator then
14979
+ * records the dedup entry, emits `BID_SELECTED`, tracks the fill, and either
14980
+ * terminates or continues polling for the remaining amount. Feeding back
14981
+ * `undefined` (no bid executed this round) causes it to keep polling.
14937
14982
  *
14938
14983
  * **Same-chain:** `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED`
14939
14984
  * → (`FILLED` | `PARTIAL_FILL`)* → (`FILLED` | `EXPIRED`)
@@ -14944,7 +14989,6 @@ var OrderExecutor = class {
14944
14989
  async *executeOrder(options) {
14945
14990
  const { order, sessionPrivateKey, auctionTimeMs, pollIntervalMs = DEFAULT_POLL_INTERVAL, solver } = options;
14946
14991
  const commitment = order.id;
14947
- order.source === order.destination;
14948
14992
  if (!this.ctx.intentsCoprocessor) {
14949
14993
  yield { status: "FAILED", error: "IntentsCoprocessor required for order execution" };
14950
14994
  return;
@@ -14972,11 +15016,24 @@ var OrderExecutor = class {
14972
15016
  remainingAssets
14973
15017
  });
14974
15018
  const deadlineTimeout = this.deadlineStream(order.deadline, commitment);
14975
- const combined = mergeRace__default.default(deadlineTimeout, executionStream);
15019
+ const deadlinePromise = deadlineTimeout.next();
14976
15020
  try {
14977
- for await (const update of combined) {
14978
- yield update;
14979
- if (update.status === "EXPIRED" || update.status === "FILLED") return;
15021
+ let input;
15022
+ while (true) {
15023
+ const winner = input !== void 0 ? { from: "exec", r: await executionStream.next(input) } : await Promise.race([
15024
+ executionStream.next(void 0).then((r) => ({ from: "exec", r })),
15025
+ deadlinePromise.then((r) => ({ from: "deadline", r }))
15026
+ ]);
15027
+ input = void 0;
15028
+ if (winner.from === "deadline") {
15029
+ if (!winner.r.done && winner.r.value) yield winner.r.value;
15030
+ return;
15031
+ }
15032
+ const { value, done } = winner.r;
15033
+ if (done) return;
15034
+ const fed = yield value;
15035
+ if (value.status === "BIDS_RECEIVED") input = fed;
15036
+ if (value.status === "EXPIRED" || value.status === "FILLED") return;
14980
15037
  }
14981
15038
  } finally {
14982
15039
  console.log(`[OrderExecutor] Tearing down streams for commitment=${commitment}`);
@@ -14985,9 +15042,16 @@ var OrderExecutor = class {
14985
15042
  }
14986
15043
  }
14987
15044
  /**
14988
- * Core execution loop that polls for bids, submits UserOperations,
14989
- * and tracks fill progress. Yields between each poll iteration so
14990
- * that `mergeRace` can interleave the deadline stream.
15045
+ * Core execution loop that polls for bids and tracks fill progress. Builds
15046
+ * first-class {@link Bid} objects from the raw filler bids and yields them to
15047
+ * the consumer, which picks one, calls `bid.execute()`, and feeds the result
15048
+ * back via `gen.next(result)`. The loop then records the dedup entry, emits
15049
+ * `BID_SELECTED`, processes the fill, and continues polling for the remaining
15050
+ * amount on partial fills.
15051
+ *
15052
+ * Bidirectional: the value passed to `.next()` after a `BIDS_RECEIVED` yield is
15053
+ * the {@link SelectBidResult} from the executed bid (or `undefined` to skip the
15054
+ * round and keep polling).
14991
15055
  */
14992
15056
  async *executionStream(params) {
14993
15057
  const {
@@ -15002,12 +15066,7 @@ var OrderExecutor = class {
15002
15066
  targetAssets
15003
15067
  } = params;
15004
15068
  let { totalFilledAssets, remainingAssets } = params;
15005
- const MAX_BID_ATTEMPTS = 2;
15006
- const bidFailCounts = /* @__PURE__ */ new Map();
15007
- const isFreshBid = (bid) => {
15008
- const key = userOpHashKey(bid.userOp);
15009
- return !usedUserOps.has(key) && (bidFailCounts.get(key) ?? 0) < MAX_BID_ATTEMPTS;
15010
- };
15069
+ const isFreshBid = (bid) => !usedUserOps.has(userOpHashKey(bid.userOp));
15011
15070
  const solverLockStartTime = Date.now();
15012
15071
  yield { status: "AWAITING_BIDS", commitment, totalFilledAssets, remainingAssets };
15013
15072
  try {
@@ -15016,13 +15075,13 @@ var OrderExecutor = class {
15016
15075
  while (Date.now() < auctionEnd) {
15017
15076
  try {
15018
15077
  const bids = await this.fetchBids({ commitment, solver, solverLockStartTime });
15019
- const freshBids = bids.filter((bid) => !usedUserOps.has(userOpHashKey(bid.userOp)));
15020
- const newBids = freshBids.filter((bid) => !auctionSeenBids.has(userOpHashKey(bid.userOp)));
15021
- if (newBids.length > 0) {
15022
- for (const bid of newBids) {
15023
- auctionSeenBids.add(userOpHashKey(bid.userOp));
15024
- }
15025
- yield { status: "NEW_BID", commitment, bidCount: freshBids.length, bids: freshBids };
15078
+ const newBids = bids.filter(
15079
+ (bid) => isFreshBid(bid) && !auctionSeenBids.has(userOpHashKey(bid.userOp))
15080
+ );
15081
+ for (const fillerBid of newBids) {
15082
+ auctionSeenBids.add(userOpHashKey(fillerBid.userOp));
15083
+ const [bid] = this.bidManager.buildBids(order, [fillerBid], sessionPrivateKey);
15084
+ if (bid) yield { status: "NEW_BID", commitment, bid };
15026
15085
  }
15027
15086
  } catch {
15028
15087
  }
@@ -15034,8 +15093,8 @@ var OrderExecutor = class {
15034
15093
  while (true) {
15035
15094
  let freshBids;
15036
15095
  try {
15037
- const bids = await this.fetchBids({ commitment, solver, solverLockStartTime });
15038
- freshBids = bids.filter(isFreshBid);
15096
+ const bids2 = await this.fetchBids({ commitment, solver, solverLockStartTime });
15097
+ freshBids = bids2.filter(isFreshBid);
15039
15098
  } catch {
15040
15099
  await sleep(pollIntervalMs);
15041
15100
  continue;
@@ -15044,56 +15103,33 @@ var OrderExecutor = class {
15044
15103
  await sleep(pollIntervalMs);
15045
15104
  continue;
15046
15105
  }
15047
- yield { status: "BIDS_RECEIVED", commitment, bidCount: freshBids.length, bids: freshBids };
15048
- let submitResult;
15049
- try {
15050
- submitResult = await this.submitBid({
15051
- order,
15052
- freshBids,
15053
- sessionPrivateKey,
15054
- commitment,
15055
- usedUserOps,
15056
- userOpHashKey
15057
- });
15058
- } catch (err) {
15059
- yield {
15060
- status: "FAILED",
15061
- commitment,
15062
- totalFilledAssets,
15063
- remainingAssets,
15064
- error: `Failed to select bid and submit: ${err instanceof Error ? err.message : String(err)}`
15065
- };
15066
- try {
15067
- const sorted = await this.bidManager.validateAndSortBids(freshBids, order);
15068
- if (sorted.length > 0) {
15069
- const key = userOpHashKey(sorted[0].bid.userOp);
15070
- bidFailCounts.set(key, (bidFailCounts.get(key) ?? 0) + 1);
15071
- }
15072
- } catch {
15073
- }
15106
+ const bids = this.bidManager.buildBids(order, freshBids, sessionPrivateKey);
15107
+ if (bids.length === 0) {
15108
+ await sleep(pollIntervalMs);
15109
+ continue;
15110
+ }
15111
+ const result = yield { status: "BIDS_RECEIVED", commitment, bidCount: bids.length, bids };
15112
+ if (!result) {
15074
15113
  await sleep(pollIntervalMs);
15075
15114
  continue;
15076
15115
  }
15116
+ usedUserOps.add(userOpHashKey(result.userOp));
15117
+ await this.persistUsedUserOps(commitment, usedUserOps);
15077
15118
  yield {
15078
15119
  status: "BID_SELECTED",
15079
15120
  commitment,
15080
- selectedSolver: submitResult.solverAddress,
15081
- userOpHash: submitResult.userOpHash,
15082
- userOp: submitResult.userOp,
15083
- transactionHash: submitResult.txnHash
15121
+ selectedSolver: result.solverAddress,
15122
+ userOpHash: result.userOpHash,
15123
+ userOp: result.userOp,
15124
+ transactionHash: result.txnHash
15084
15125
  };
15085
- const fill = this.processFillResult(
15086
- submitResult,
15087
- commitment,
15088
- targetAssets,
15089
- totalFilledAssets,
15090
- remainingAssets
15091
- );
15126
+ const fill = this.processFillResult(result, commitment, targetAssets, totalFilledAssets, remainingAssets);
15092
15127
  totalFilledAssets = fill.totalFilledAssets;
15093
15128
  remainingAssets = fill.remainingAssets;
15094
15129
  if (fill.update) {
15095
15130
  yield fill.update;
15096
15131
  }
15132
+ if (fill.done) return;
15097
15133
  }
15098
15134
  } catch (err) {
15099
15135
  yield {
@@ -15568,183 +15604,148 @@ var OrderCanceller = class {
15568
15604
  return feeInDestFeeToken * 1005n / 1000n;
15569
15605
  }
15570
15606
  };
15571
- var BidManager = class {
15572
- /**
15573
- * @param ctx - Shared IntentsV2 context providing the destination chain
15574
- * client, coprocessor, bundler URL, and session-key storage.
15575
- * @param crypto - Crypto utilities used for gas packing, UserOp hashing,
15576
- * EIP-712 signing, and bundler calls.
15577
- */
15578
- constructor(ctx, crypto) {
15579
- this.ctx = ctx;
15580
- this.crypto = crypto;
15581
- }
15607
+ var BidImpl = class {
15608
+ solverAddress;
15609
+ outputs;
15610
+ relayerFee;
15611
+ nativeDispatchFee;
15612
+ userOp;
15582
15613
  ctx;
15583
15614
  crypto;
15584
- /**
15585
- * Constructs a signed `PackedUserOperation` that a solver can submit to the
15586
- * Hyperbridge coprocessor as a bid to fill an order.
15587
- *
15588
- * The solver's signature covers a hash that binds the UserOperation to the
15589
- * order commitment and the session key address, so the IntentGatewayV2
15590
- * contract can verify the solver's intent on-chain.
15591
- *
15592
- * @param options - Parameters describing the solver account, gas limits, fee
15593
- * market values, and pre-built `callData` for the fill operation.
15594
- * @returns A `PackedUserOperation` with the solver's signature prepended
15595
- * with the order commitment.
15596
- */
15597
- async prepareSubmitBid(options) {
15598
- const {
15599
- order,
15600
- solverAccount,
15601
- solverSigner,
15602
- nonce,
15603
- entryPointAddress,
15604
- callGasLimit,
15605
- verificationGasLimit,
15606
- preVerificationGas,
15607
- maxFeePerGas,
15608
- maxPriorityFeePerGas,
15609
- callData,
15610
- paymasterAndData = "0x"
15611
- } = options;
15612
- const chainId = BigInt(
15615
+ order;
15616
+ fillOptions;
15617
+ priceOutputs;
15618
+ sessionPrivateKey;
15619
+ intentGatewayV2Address;
15620
+ domainSeparator;
15621
+ /** Cached session-key signature over the `SelectSolver` message. */
15622
+ cachedSignature;
15623
+ constructor(params) {
15624
+ this.ctx = params.ctx;
15625
+ this.crypto = params.crypto;
15626
+ this.order = params.order;
15627
+ this.fillOptions = params.fillOptions;
15628
+ this.priceOutputs = params.priceOutputs;
15629
+ this.sessionPrivateKey = params.sessionPrivateKey;
15630
+ this.solverAddress = params.fillerBid.userOp.sender;
15631
+ this.outputs = params.fillOptions.outputs;
15632
+ this.relayerFee = params.fillOptions.relayerFee;
15633
+ this.nativeDispatchFee = params.fillOptions.nativeDispatchFee;
15634
+ this.userOp = params.fillerBid.userOp;
15635
+ this.intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
15636
+ normalizeStateMachineId(this.order.destination)
15637
+ );
15638
+ this.domainSeparator = CryptoUtils.getDomainSeparator(
15639
+ "IntentGateway",
15640
+ "2",
15641
+ this.chainId(),
15642
+ this.intentGatewayV2Address
15643
+ );
15644
+ }
15645
+ /** Resolves the destination chain id from the client or the state-machine id. */
15646
+ chainId() {
15647
+ return BigInt(
15613
15648
  this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15614
15649
  );
15615
- const accountGasLimits = CryptoUtils.packGasLimits(verificationGasLimit, callGasLimit);
15616
- const gasFees = CryptoUtils.packGasFees(maxPriorityFeePerGas, maxFeePerGas);
15617
- const userOp = {
15618
- sender: solverAccount,
15619
- nonce,
15620
- initCode: "0x",
15621
- callData,
15622
- accountGasLimits,
15623
- preVerificationGas,
15624
- gasFees,
15625
- paymasterAndData,
15626
- signature: "0x"
15627
- };
15628
- const userOpHash = CryptoUtils.computeUserOpHash(userOp, entryPointAddress, chainId);
15629
- const sessionKey = order.session;
15630
- const messageHash = viem.keccak256(viem.concat([userOpHash, order.id, sessionKey]));
15631
- const solverSignature = await solverSigner.signMessage(messageHash, Number(chainId));
15632
- const signature = viem.concat([order.id, solverSignature]);
15633
- return { ...userOp, signature };
15634
15650
  }
15635
15651
  /**
15636
- * Selects the best available bid, simulates it on-chain, signs the
15637
- * solver-selection EIP-712 message with the session key, and submits the
15638
- * UserOperation to the bundler.
15652
+ * Resolves the session key, signs the `SelectSolver` message for this bid's
15653
+ * solver, and caches the signature. Signs at most once per bid.
15639
15654
  *
15640
- * **Selection algorithm:**
15641
- * 1. Decodes `fillOrder` calldata from each bid's `callData`.
15642
- * 2. Sorts bids by output value (single-output: amount; all-stables: normalised
15643
- * USD; mixed: DEX-quoted USD; fallback: raw amount).
15644
- * 3. Iterates sorted bids, simulating each with `eth_call` until one passes.
15645
- * 4. Appends the session-key's `SelectSolver` signature to the solver's
15646
- * existing signature and submits via `eth_sendUserOperation`.
15647
- * 5. For same-chain orders, waits for the transaction receipt and reads
15648
- * `OrderFilled` / `PartialFill` events to determine fill status.
15649
- *
15650
- * @param order - The placed order for which to select a bid.
15651
- * @param bids - Raw bids fetched from the Hyperbridge coprocessor.
15652
- * @param sessionPrivateKey - Optional override; if omitted, the key is
15653
- * looked up from `sessionKeyStorage` using `order.session`.
15654
- * @returns A {@link SelectBidResult} containing the submitted UserOperation,
15655
- * its hash, the winning solver address, transaction hash, and fill status.
15656
- * @throws If the session key is not found, no valid bids exist, all
15657
- * simulations fail, or the bundler rejects the UserOperation.
15655
+ * @throws If the session key is missing or signing fails.
15658
15656
  */
15659
- async selectBid(order, bids, sessionPrivateKey) {
15660
- const commitment = order.id;
15661
- const sessionKeyAddress = order.session;
15662
- console.log(`[BidManager] selectBid called for commitment=${commitment}, received ${bids.length} bid(s)`);
15663
- const sessionKeyData = sessionPrivateKey ? { privateKey: sessionPrivateKey } : await this.ctx.sessionKeyStorage.getSessionKeyByAddress(sessionKeyAddress);
15657
+ async signSelection() {
15658
+ if (this.cachedSignature) return this.cachedSignature;
15659
+ const commitment = this.order.id;
15660
+ const sessionKeyAddress = this.order.session;
15661
+ const sessionKeyData = this.sessionPrivateKey ? { privateKey: this.sessionPrivateKey } : await this.ctx.sessionKeyStorage.getSessionKeyByAddress(sessionKeyAddress);
15664
15662
  if (!sessionKeyData) {
15665
15663
  throw new Error("SessionKey not found for commitment: " + commitment);
15666
15664
  }
15667
- if (!this.ctx.bundlerUrl) {
15668
- throw new Error("Bundler URL not configured");
15669
- }
15670
- if (!this.ctx.intentsCoprocessor) {
15671
- throw new Error("IntentsCoprocessor required");
15672
- }
15673
- const sortedBids = await this.validateAndSortBids(bids, order);
15674
- console.log(`[BidManager] ${sortedBids.length}/${bids.length} bid(s) passed validation and sorting`);
15675
- if (sortedBids.length === 0) {
15676
- throw new Error("No valid bids found");
15677
- }
15678
- const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
15679
- normalizeStateMachineId(order.destination)
15680
- );
15681
- const domainSeparator = CryptoUtils.getDomainSeparator(
15682
- "IntentGateway",
15683
- "2",
15684
- BigInt(
15685
- this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15686
- ),
15687
- intentGatewayV2Address
15665
+ const signature = await CryptoUtils.signSolverSelection(
15666
+ commitment,
15667
+ this.solverAddress,
15668
+ this.domainSeparator,
15669
+ sessionKeyData.privateKey
15688
15670
  );
15689
- let selectedBid = null;
15690
- let sessionSignature = null;
15691
- console.log(`[BidManager] Simulating ${sortedBids.length} sorted bid(s) to find a valid one`);
15692
- for (let idx = 0; idx < sortedBids.length; idx++) {
15693
- const bidWithOptions = sortedBids[idx];
15694
- const solverAddress2 = bidWithOptions.bid.userOp.sender;
15695
- console.log(`[BidManager] Simulating bid ${idx + 1}/${sortedBids.length} from solver=${solverAddress2}`);
15696
- const signature = await CryptoUtils.signSolverSelection(
15697
- commitment,
15698
- solverAddress2,
15699
- domainSeparator,
15700
- sessionKeyData.privateKey
15701
- );
15702
- if (!signature) {
15703
- console.warn(`[BidManager] Bid ${idx + 1}: failed to sign solver selection, skipping`);
15704
- continue;
15705
- }
15706
- const selectOptions = {
15707
- commitment,
15708
- solver: solverAddress2,
15709
- signature
15710
- };
15711
- try {
15712
- await this.simulate(bidWithOptions.bid, bidWithOptions.options, selectOptions, intentGatewayV2Address);
15713
- console.log(`[BidManager] Bid ${idx + 1} from solver=${solverAddress2}: simulation PASSED`);
15714
- selectedBid = bidWithOptions;
15715
- sessionSignature = signature;
15716
- break;
15717
- } catch (err) {
15718
- console.warn(
15719
- `[BidManager] Bid ${idx + 1} from solver=${solverAddress2}: simulation FAILED: ${err instanceof Error ? err.message : String(err)}`
15720
- );
15721
- continue;
15722
- }
15671
+ if (!signature) {
15672
+ throw new Error("Failed to sign solver selection");
15723
15673
  }
15724
- if (!selectedBid || !sessionSignature) {
15725
- console.error(`[BidManager] All ${sortedBids.length} bid(s) failed simulation for commitment=${commitment}`);
15726
- throw new Error("No bids passed simulation");
15674
+ this.cachedSignature = signature;
15675
+ return signature;
15676
+ }
15677
+ /**
15678
+ * Simulates this bid on-chain by batching the `select` and `fillOrder` calls
15679
+ * via `eth_call` from the solver's account, using the IntentGatewayV2 ERC-7821
15680
+ * batch-execute pattern.
15681
+ *
15682
+ * The native value forwarded to the simulation is the sum of any native-token
15683
+ * (`address(0)`) output amounts plus the Hyperbridge dispatch fee.
15684
+ *
15685
+ * @throws If the `eth_call` simulation reverts or errors.
15686
+ */
15687
+ async simulate() {
15688
+ const signature = await this.signSelection();
15689
+ const selectOptions = {
15690
+ commitment: this.order.id,
15691
+ solver: this.solverAddress,
15692
+ signature
15693
+ };
15694
+ const nativeOutputs = this.fillOptions.outputs.reduce(
15695
+ (acc, o) => bytes32ToBytes20(o.token) === ADDRESS_ZERO2 ? acc + o.amount : acc,
15696
+ 0n
15697
+ );
15698
+ const simulationValue = nativeOutputs + this.fillOptions.nativeDispatchFee;
15699
+ const selectCalldata = viem.encodeFunctionData({
15700
+ abi: ABI3,
15701
+ functionName: "select",
15702
+ args: [selectOptions]
15703
+ });
15704
+ const calls = [
15705
+ { target: this.intentGatewayV2Address, value: 0n, data: selectCalldata },
15706
+ { target: this.solverAddress, value: simulationValue, data: this.userOp.callData }
15707
+ ];
15708
+ const batchedCalldata = this.crypto.encodeERC7821Execute(calls);
15709
+ try {
15710
+ await this.ctx.dest.client.call({
15711
+ account: this.solverAddress,
15712
+ to: this.solverAddress,
15713
+ data: batchedCalldata,
15714
+ value: simulationValue
15715
+ });
15716
+ } catch (e) {
15717
+ throw new Error(`Simulation failed: ${e instanceof Error ? e.message : String(e)}`);
15727
15718
  }
15728
- const solverAddress = selectedBid.bid.userOp.sender;
15729
- const finalSignature = viem.concat([
15730
- selectedBid.bid.userOp.signature,
15731
- sessionSignature
15732
- ]);
15719
+ }
15720
+ /**
15721
+ * Signs the `SelectSolver` message with the session key, appends it to the
15722
+ * solver's existing UserOp signature, and submits the UserOperation to the
15723
+ * bundler. For same-chain orders, waits for the receipt and reads
15724
+ * `OrderFilled` / `PartialFill` logs to determine fill status.
15725
+ *
15726
+ * @returns A {@link SelectBidResult} with the submitted UserOperation, its hash,
15727
+ * the solver address, transaction hash, and fill status.
15728
+ * @throws If the bundler is not configured, the session key is missing, or the
15729
+ * bundler rejects the UserOperation.
15730
+ */
15731
+ async execute() {
15732
+ const commitment = this.order.id;
15733
+ if (!this.ctx.bundlerUrl) {
15734
+ throw new Error("Bundler URL not configured");
15735
+ }
15736
+ const sessionSignature = await this.signSelection();
15737
+ const finalSignature = viem.concat([this.userOp.signature, sessionSignature]);
15733
15738
  const signedUserOp = {
15734
- ...selectedBid.bid.userOp,
15739
+ ...this.userOp,
15735
15740
  signature: finalSignature
15736
15741
  };
15737
15742
  const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(
15738
- normalizeStateMachineId(order.destination)
15743
+ normalizeStateMachineId(this.order.destination)
15739
15744
  );
15740
- BigInt(
15741
- this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15742
- );
15743
- const bundlerResult = await this.crypto.sendBundler(BundlerMethod.ETH_SEND_USER_OPERATION, [
15745
+ const userOpHash = await this.crypto.sendBundler(BundlerMethod.ETH_SEND_USER_OPERATION, [
15744
15746
  CryptoUtils.prepareBundlerCall(signedUserOp),
15745
15747
  entryPointAddress
15746
15748
  ]);
15747
- const userOpHash = bundlerResult;
15748
15749
  let txnHash;
15749
15750
  let fillStatus;
15750
15751
  let filledAssets;
@@ -15760,7 +15761,7 @@ var BidManager = class {
15760
15761
  { maxRetries: 5, backoffMs: 2e3, logMessage: "Fetching user operation receipt" }
15761
15762
  );
15762
15763
  txnHash = receipt.receipt.transactionHash;
15763
- if (order.source === order.destination) {
15764
+ if (this.order.source === this.order.destination) {
15764
15765
  try {
15765
15766
  const chainReceipt = await this.ctx.dest.client.waitForTransactionReceipt({
15766
15767
  hash: txnHash,
@@ -15789,12 +15790,12 @@ var BidManager = class {
15789
15790
  }
15790
15791
  }
15791
15792
  } catch (err) {
15792
- throw new Error(`Failed to select bid: ${err instanceof Error ? err.message : String(err)}`);
15793
+ throw new Error(`Failed to execute bid: ${err instanceof Error ? err.message : String(err)}`);
15793
15794
  }
15794
15795
  return {
15795
15796
  userOp: signedUserOp,
15796
15797
  userOpHash,
15797
- solverAddress,
15798
+ solverAddress: this.solverAddress,
15798
15799
  commitment,
15799
15800
  txnHash,
15800
15801
  fillStatus,
@@ -15802,7 +15803,177 @@ var BidManager = class {
15802
15803
  };
15803
15804
  }
15804
15805
  /**
15805
- * Validates and sorts a list of raw bids for the given order.
15806
+ * Prices this bid's outputs in USD using the destination chain's DEX-quote
15807
+ * helpers. Returns `null` when any output token cannot be priced.
15808
+ */
15809
+ async outputUsdValue() {
15810
+ return this.priceOutputs(this.outputs);
15811
+ }
15812
+ };
15813
+ var BidManager = class {
15814
+ /**
15815
+ * @param ctx - Shared IntentsV2 context providing the destination chain
15816
+ * client, coprocessor, bundler URL, and session-key storage.
15817
+ * @param crypto - Crypto utilities used for gas packing, UserOp hashing,
15818
+ * EIP-712 signing, and bundler calls.
15819
+ */
15820
+ constructor(ctx, crypto) {
15821
+ this.ctx = ctx;
15822
+ this.crypto = crypto;
15823
+ }
15824
+ ctx;
15825
+ crypto;
15826
+ /**
15827
+ * Constructs a signed `PackedUserOperation` that a solver can submit to the
15828
+ * Hyperbridge coprocessor as a bid to fill an order.
15829
+ *
15830
+ * The solver's signature covers a hash that binds the UserOperation to the
15831
+ * order commitment and the session key address, so the IntentGatewayV2
15832
+ * contract can verify the solver's intent on-chain.
15833
+ *
15834
+ * @param options - Parameters describing the solver account, gas limits, fee
15835
+ * market values, and pre-built `callData` for the fill operation.
15836
+ * @returns A `PackedUserOperation` with the solver's signature prepended
15837
+ * with the order commitment.
15838
+ */
15839
+ async prepareSubmitBid(options) {
15840
+ const {
15841
+ order,
15842
+ solverAccount,
15843
+ solverSigner,
15844
+ nonce,
15845
+ entryPointAddress,
15846
+ callGasLimit,
15847
+ verificationGasLimit,
15848
+ preVerificationGas,
15849
+ maxFeePerGas,
15850
+ maxPriorityFeePerGas,
15851
+ callData,
15852
+ paymasterAndData = "0x"
15853
+ } = options;
15854
+ const chainId = BigInt(
15855
+ this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15856
+ );
15857
+ const accountGasLimits = CryptoUtils.packGasLimits(verificationGasLimit, callGasLimit);
15858
+ const gasFees = CryptoUtils.packGasFees(maxPriorityFeePerGas, maxFeePerGas);
15859
+ const userOp = {
15860
+ sender: solverAccount,
15861
+ nonce,
15862
+ initCode: "0x",
15863
+ callData,
15864
+ accountGasLimits,
15865
+ preVerificationGas,
15866
+ gasFees,
15867
+ paymasterAndData,
15868
+ signature: "0x"
15869
+ };
15870
+ const userOpHash = CryptoUtils.computeUserOpHash(userOp, entryPointAddress, chainId);
15871
+ const sessionKey = order.session;
15872
+ const messageHash = viem.keccak256(viem.concat([userOpHash, order.id, sessionKey]));
15873
+ const solverSignature = await solverSigner.signMessage(messageHash, Number(chainId));
15874
+ const signature = viem.concat([order.id, solverSignature]);
15875
+ return { ...userOp, signature };
15876
+ }
15877
+ /**
15878
+ * Decodes raw filler bids into first-class {@link Bid} objects.
15879
+ *
15880
+ * Each bid's `fillOrder` fill-options are decoded from its ERC-7821 calldata;
15881
+ * bids whose calldata cannot be decoded into a valid `fillOrder` call are
15882
+ * silently dropped with a warning. The returned `Bid` instances are ready to
15883
+ * be ranked, simulated, and executed by the consumer.
15884
+ *
15885
+ * @param order - The placed order the bids are competing to fill.
15886
+ * @param bids - Raw filler bids fetched from the coprocessor.
15887
+ * @param sessionPrivateKey - Optional session-key override; looked up from
15888
+ * storage by `order.session` if omitted.
15889
+ * @returns Array of executable `Bid` objects (one per successfully decoded bid).
15890
+ */
15891
+ buildBids(order, bids, sessionPrivateKey) {
15892
+ const chainId = this.ctx.dest.config.stateMachineId;
15893
+ const priceOutputs = (outputs) => this.computeOutputsUsdValue(outputs, chainId);
15894
+ const result = [];
15895
+ for (const fillerBid of bids) {
15896
+ const fillOptions = this.decodeBidFillOptions(fillerBid);
15897
+ if (!fillOptions) {
15898
+ console.warn(`[BidManager] Failed to decode fillOptions from bid by solver=${fillerBid.userOp.sender}`);
15899
+ continue;
15900
+ }
15901
+ result.push(
15902
+ new BidImpl({
15903
+ ctx: this.ctx,
15904
+ crypto: this.crypto,
15905
+ order,
15906
+ fillerBid,
15907
+ fillOptions,
15908
+ priceOutputs,
15909
+ sessionPrivateKey
15910
+ })
15911
+ );
15912
+ }
15913
+ console.log(`[BidManager] Built ${result.length}/${bids.length} bid(s) successfully`);
15914
+ return result;
15915
+ }
15916
+ /**
15917
+ * Decodes raw filler bids, sorts them, simulates each until one passes, signs
15918
+ * the `SelectSolver` message, and submits — all with no per-bid input from the
15919
+ * caller.
15920
+ *
15921
+ * Equivalent to `selectAndExecuteBest(order, buildBids(order, bids, key))`.
15922
+ *
15923
+ * @param order - The placed order to fill.
15924
+ * @param bids - Raw filler bids fetched from the coprocessor.
15925
+ * @param sessionPrivateKey - Optional session-key override; looked up from
15926
+ * storage by `order.session` if omitted.
15927
+ * @returns A {@link SelectBidResult} for the executed bid.
15928
+ */
15929
+ async selectBid(order, bids, sessionPrivateKey) {
15930
+ return this.selectAndExecuteBest(order, this.buildBids(order, bids, sessionPrivateKey));
15931
+ }
15932
+ /**
15933
+ * Autopilot bid selection: sorts the given bids by output value, simulates
15934
+ * each in order until one passes, then executes that bid. For consumers that
15935
+ * do not need custom selection logic.
15936
+ *
15937
+ * @param order - The placed order to fill.
15938
+ * @param bids - Candidate bids (from {@link buildBids}).
15939
+ * @returns A {@link SelectBidResult} for the executed bid.
15940
+ * @throws If no valid bids exist, all simulations fail, or the bundler rejects
15941
+ * the UserOperation.
15942
+ */
15943
+ async selectAndExecuteBest(order, bids) {
15944
+ const commitment = order.id;
15945
+ console.log(`[BidManager] selectAndExecuteBest called for commitment=${commitment}, ${bids.length} bid(s)`);
15946
+ if (!this.ctx.bundlerUrl) {
15947
+ throw new Error("Bundler URL not configured");
15948
+ }
15949
+ if (!this.ctx.intentsCoprocessor) {
15950
+ throw new Error("IntentsCoprocessor required");
15951
+ }
15952
+ const sortedBids = await this.sortBids(order, bids);
15953
+ console.log(`[BidManager] ${sortedBids.length}/${bids.length} bid(s) passed validation and sorting`);
15954
+ if (sortedBids.length === 0) {
15955
+ throw new Error("No valid bids found");
15956
+ }
15957
+ console.log(`[BidManager] Simulating ${sortedBids.length} sorted bid(s) to find a valid one`);
15958
+ for (let idx = 0; idx < sortedBids.length; idx++) {
15959
+ const bid = sortedBids[idx];
15960
+ console.log(`[BidManager] Simulating bid ${idx + 1}/${sortedBids.length} from solver=${bid.solverAddress}`);
15961
+ try {
15962
+ await bid.simulate();
15963
+ } catch (err) {
15964
+ console.warn(
15965
+ `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation FAILED: ${err instanceof Error ? err.message : String(err)}`
15966
+ );
15967
+ continue;
15968
+ }
15969
+ console.log(`[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation PASSED`);
15970
+ return bid.execute();
15971
+ }
15972
+ console.error(`[BidManager] All ${sortedBids.length} bid(s) failed simulation for commitment=${commitment}`);
15973
+ throw new Error("No bids passed simulation");
15974
+ }
15975
+ /**
15976
+ * Sorts a list of bids for the given order by output value.
15806
15977
  *
15807
15978
  * Delegates to one of three strategies based on the order's output token
15808
15979
  * composition:
@@ -15811,47 +15982,26 @@ var BidManager = class {
15811
15982
  * - Mixed outputs: sort by DEX-quoted USD value descending, with a raw-amount
15812
15983
  * fallback if pricing fails.
15813
15984
  *
15814
- * @param bids - Raw filler bids from the coprocessor.
15985
+ * Bids that cannot satisfy the order's token set are dropped.
15986
+ *
15815
15987
  * @param order - The placed order whose output spec drives sorting logic.
15816
- * @returns Sorted array of `{ bid, options }` pairs ready for simulation.
15988
+ * @param bids - Executable bids to sort (from {@link buildBids}).
15989
+ * @returns Sorted array of `Bid` objects ready for simulation.
15817
15990
  */
15818
- async validateAndSortBids(bids, order) {
15991
+ async sortBids(order, bids) {
15819
15992
  const outputs = order.output.assets;
15820
- const decodedBids = this.decodeBids(bids);
15821
15993
  if (outputs.length <= 1) {
15822
15994
  console.log(`[BidManager] Using single-output sorting (1 output asset)`);
15823
- return this.sortSingleOutput(decodedBids, outputs[0]);
15995
+ return this.sortSingleOutput(bids, outputs[0]);
15824
15996
  }
15825
15997
  const chainId = this.ctx.dest.config.stateMachineId;
15826
15998
  const allStables = outputs.every((o) => this.isStableToken(bytes32ToBytes20(o.token), chainId));
15827
15999
  if (allStables) {
15828
16000
  console.log(`[BidManager] Using all-stables sorting (${outputs.length} stable output assets)`);
15829
- return this.sortAllStables(decodedBids, outputs, chainId);
16001
+ return this.sortAllStables(bids, outputs, chainId);
15830
16002
  }
15831
16003
  console.log(`[BidManager] Using mixed-output sorting (${outputs.length} output assets, some non-stable)`);
15832
- return this.sortMixedOutputs(decodedBids, outputs, chainId);
15833
- }
15834
- /**
15835
- * Decodes the `fillOrder` fill-options from each bid's ERC-7821 calldata.
15836
- *
15837
- * Bids whose calldata cannot be decoded or do not contain a valid
15838
- * `fillOrder` call are silently dropped with a warning.
15839
- *
15840
- * @param bids - Raw bids to decode.
15841
- * @returns Array of successfully decoded `{ bid, options }` pairs.
15842
- */
15843
- decodeBids(bids) {
15844
- const result = [];
15845
- for (const bid of bids) {
15846
- const fillOptions = this.decodeBidFillOptions(bid);
15847
- if (fillOptions) {
15848
- result.push({ bid, options: fillOptions });
15849
- } else {
15850
- console.warn(`[BidManager] Failed to decode fillOptions from bid by solver=${bid.userOp.sender}`);
15851
- }
15852
- }
15853
- console.log(`[BidManager] Decoded ${result.length}/${bids.length} bid(s) successfully`);
15854
- return result;
16004
+ return this.sortMixedOutputs(bids, outputs, chainId);
15855
16005
  }
15856
16006
  /**
15857
16007
  * Extracts the `FillOptions` struct from a single bid's ERC-7821
@@ -15884,149 +16034,103 @@ var BidManager = class {
15884
16034
  }
15885
16035
  return null;
15886
16036
  }
15887
- /**
15888
- * Simulates a bid on-chain by batching the `select` and `fillOrder` calls
15889
- * via `eth_call` from the solver's account, using the IntentGatewayV2
15890
- * ERC-7821 batch-execute pattern.
15891
- *
15892
- * The native value forwarded to the simulation is computed from the fill options:
15893
- * sum of any native-token (address(0)) output amounts plus the dispatch fee.
15894
- *
15895
- * @param bid - The filler bid to simulate.
15896
- * @param fillOptions - Decoded fill options from the bid's calldata.
15897
- * @param selectOptions - The signed solver-selection parameters.
15898
- * @param intentGatewayV2Address - Address of the IntentGatewayV2 contract on the destination chain.
15899
- * @throws If the `eth_call` simulation reverts or errors.
15900
- */
15901
- async simulate(bid, fillOptions, selectOptions, intentGatewayV2Address) {
15902
- const solverAddress = bid.userOp.sender;
15903
- const nativeOutputs = fillOptions.outputs.reduce(
15904
- (acc, o) => bytes32ToBytes20(o.token) === ADDRESS_ZERO2 ? acc + o.amount : acc,
15905
- 0n
15906
- );
15907
- const simulationValue = nativeOutputs + fillOptions.nativeDispatchFee;
15908
- const selectCalldata = viem.encodeFunctionData({
15909
- abi: ABI3,
15910
- functionName: "select",
15911
- args: [selectOptions]
15912
- });
15913
- const calls = [
15914
- { target: intentGatewayV2Address, value: 0n, data: selectCalldata },
15915
- { target: solverAddress, value: simulationValue, data: bid.userOp.callData }
15916
- ];
15917
- const batchedCalldata = this.crypto.encodeERC7821Execute(calls);
15918
- try {
15919
- await this.ctx.dest.client.call({
15920
- account: solverAddress,
15921
- to: solverAddress,
15922
- data: batchedCalldata,
15923
- value: simulationValue
15924
- });
15925
- } catch (e) {
15926
- throw new Error(`Simulation failed: ${e instanceof Error ? e.message : String(e)}`);
15927
- }
15928
- }
15929
16037
  /**
15930
16038
  * Case A: single output token.
15931
16039
  * Filter bids by token match only, sort descending by amount.
15932
16040
  * Partial fill bids are allowed — the contract determines fill status.
15933
16041
  */
15934
- sortSingleOutput(decodedBids, requiredAsset) {
16042
+ sortSingleOutput(bids, requiredAsset) {
15935
16043
  const requiredAmount = new Decimal2__default.default(requiredAsset.amount.toString());
15936
16044
  console.log(
15937
16045
  `[BidManager] sortSingleOutput: required token=${requiredAsset.token}, amount=${requiredAmount.toString()}`
15938
16046
  );
15939
16047
  const validBids = [];
15940
- for (const { bid, options } of decodedBids) {
15941
- const bidOutput = options.outputs[0];
16048
+ for (const bid of bids) {
16049
+ const bidOutput = bid.outputs[0];
15942
16050
  const bidAmount = new Decimal2__default.default(bidOutput.amount.toString());
15943
16051
  if (bidOutput.token.toLowerCase() !== requiredAsset.token.toLowerCase()) {
15944
16052
  console.warn(
15945
- `[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: token mismatch (bid=${bidOutput.token}, required=${requiredAsset.token})`
16053
+ `[BidManager] Bid from solver=${bid.solverAddress} REJECTED: token mismatch (bid=${bidOutput.token}, required=${requiredAsset.token})`
15946
16054
  );
15947
16055
  continue;
15948
16056
  }
15949
16057
  if (bidAmount.lt(requiredAmount)) {
15950
16058
  console.log(
15951
- `[BidManager] Bid from solver=${bid.userOp.sender}: partial fill candidate (bid=${bidAmount.toString()}, required=${requiredAmount.toString()}, covers=${bidAmount.div(requiredAmount).mul(100).toFixed(2)}%)`
16059
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidAmount.toString()}, required=${requiredAmount.toString()}, covers=${bidAmount.div(requiredAmount).mul(100).toFixed(2)}%)`
15952
16060
  );
15953
16061
  } else {
15954
16062
  console.log(
15955
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: amount=${bidAmount.toString()} (surplus=${bidAmount.minus(requiredAmount).toString()})`
16063
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: amount=${bidAmount.toString()} (surplus=${bidAmount.minus(requiredAmount).toString()})`
15956
16064
  );
15957
16065
  }
15958
- validBids.push({ bid, options, amount: bidOutput.amount });
16066
+ validBids.push({ bid, amount: bidOutput.amount });
15959
16067
  }
15960
16068
  validBids.sort((a, b) => {
15961
16069
  const aAmt = new Decimal2__default.default(a.amount.toString());
15962
16070
  const bAmt = new Decimal2__default.default(b.amount.toString());
15963
16071
  return bAmt.comparedTo(aAmt);
15964
16072
  });
15965
- return validBids.map(({ amount: _, ...rest }) => rest);
16073
+ return validBids.map(({ bid }) => bid);
15966
16074
  }
15967
16075
  /**
15968
16076
  * Case B: all outputs are USDC/USDT.
15969
16077
  * Sum normalised USD values (treating each stable as $1) and sort descending.
15970
16078
  * Partial fill bids are allowed.
15971
16079
  */
15972
- sortAllStables(decodedBids, orderOutputs, chainId) {
16080
+ sortAllStables(bids, orderOutputs, chainId) {
15973
16081
  const requiredUsd = this.computeStablesUsdValue(orderOutputs, chainId);
15974
16082
  console.log(`[BidManager] sortAllStables: required USD value=${requiredUsd.toString()}`);
15975
16083
  const validBids = [];
15976
- for (const { bid, options } of decodedBids) {
15977
- const bidUsd = this.computeStablesUsdValue(options.outputs, chainId);
16084
+ for (const bid of bids) {
16085
+ const bidUsd = this.computeStablesUsdValue(bid.outputs, chainId);
15978
16086
  if (bidUsd === null) {
15979
- console.warn(`[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: unable to compute USD value`);
16087
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to compute USD value`);
15980
16088
  continue;
15981
16089
  }
15982
16090
  if (bidUsd.lt(requiredUsd)) {
15983
16091
  console.log(
15984
- `[BidManager] Bid from solver=${bid.userOp.sender}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
16092
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
15985
16093
  );
15986
16094
  } else {
15987
- console.log(
15988
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: USD value=${bidUsd.toString()}`
15989
- );
16095
+ console.log(`[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: USD value=${bidUsd.toString()}`);
15990
16096
  }
15991
- validBids.push({ bid, options, usdValue: bidUsd });
16097
+ validBids.push({ bid, usdValue: bidUsd });
15992
16098
  }
15993
16099
  validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
15994
- return validBids.map(({ usdValue: _, ...rest }) => rest);
16100
+ return validBids.map(({ bid }) => bid);
15995
16101
  }
15996
16102
  /**
15997
16103
  * Case C: mixed output tokens (at least one non-stable).
15998
16104
  * Price every token via on-chain DEX quotes, fall back to raw amounts
15999
16105
  * if pricing is unavailable. Partial fill bids are allowed.
16000
16106
  */
16001
- async sortMixedOutputs(decodedBids, orderOutputs, chainId) {
16107
+ async sortMixedOutputs(bids, orderOutputs, chainId) {
16002
16108
  const requiredUsd = await this.computeOutputsUsdValue(orderOutputs, chainId);
16003
16109
  if (requiredUsd === null) {
16004
16110
  console.warn("[BidManager] sortMixedOutputs: output tokens unpriceable, falling back to raw-amount sort");
16005
- return this.sortByRawAmountFallback(decodedBids, orderOutputs);
16111
+ return this.sortByRawAmountFallback(bids, orderOutputs);
16006
16112
  }
16007
16113
  console.log(`[BidManager] sortMixedOutputs: required USD value=${requiredUsd.toString()}`);
16008
16114
  const validBids = [];
16009
- for (const { bid, options } of decodedBids) {
16010
- const bidUsd = await this.computeOutputsUsdValue(options.outputs, chainId);
16115
+ for (const bid of bids) {
16116
+ const bidUsd = await this.computeOutputsUsdValue(bid.outputs, chainId);
16011
16117
  if (bidUsd === null) {
16012
- console.warn(
16013
- `[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: unable to price mixed outputs`
16014
- );
16118
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to price mixed outputs`);
16015
16119
  continue;
16016
16120
  }
16017
16121
  if (bidUsd.lt(requiredUsd)) {
16018
16122
  console.log(
16019
- `[BidManager] Bid from solver=${bid.userOp.sender}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
16123
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
16020
16124
  );
16021
16125
  } else {
16022
16126
  console.log(
16023
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: mixed USD value=${bidUsd.toString()}`
16127
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: mixed USD value=${bidUsd.toString()}`
16024
16128
  );
16025
16129
  }
16026
- validBids.push({ bid, options, usdValue: bidUsd });
16130
+ validBids.push({ bid, usdValue: bidUsd });
16027
16131
  }
16028
16132
  validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
16029
- return validBids.map(({ usdValue: _, ...rest }) => rest);
16133
+ return validBids.map(({ bid }) => bid);
16030
16134
  }
16031
16135
  /**
16032
16136
  * Fallback when DEX pricing is unavailable.
@@ -16034,17 +16138,17 @@ var BidManager = class {
16034
16138
  * Bids offering less than required for a token are allowed (partial fill).
16035
16139
  * Sorted by total offered amount descending.
16036
16140
  */
16037
- sortByRawAmountFallback(decodedBids, orderOutputs) {
16141
+ sortByRawAmountFallback(bids, orderOutputs) {
16038
16142
  console.log(
16039
- `[BidManager] sortByRawAmountFallback: checking ${decodedBids.length} bid(s) against ${orderOutputs.length} required output(s)`
16143
+ `[BidManager] sortByRawAmountFallback: checking ${bids.length} bid(s) against ${orderOutputs.length} required output(s)`
16040
16144
  );
16041
16145
  const validBids = [];
16042
- for (const { bid, options } of decodedBids) {
16146
+ for (const bid of bids) {
16043
16147
  let valid = true;
16044
16148
  let totalOffered = new Decimal2__default.default(0);
16045
16149
  let rejectReason = "";
16046
16150
  for (const required of orderOutputs) {
16047
- const matching = options.outputs.find((o) => o.token.toLowerCase() === required.token.toLowerCase());
16151
+ const matching = bid.outputs.find((o) => o.token.toLowerCase() === required.token.toLowerCase());
16048
16152
  if (!matching) {
16049
16153
  valid = false;
16050
16154
  rejectReason = `missing output token=${required.token}`;
@@ -16053,7 +16157,7 @@ var BidManager = class {
16053
16157
  totalOffered = totalOffered.plus(new Decimal2__default.default(matching.amount.toString()));
16054
16158
  }
16055
16159
  if (!valid) {
16056
- console.warn(`[BidManager] Bid from solver=${bid.userOp.sender} REJECTED (fallback): ${rejectReason}`);
16160
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED (fallback): ${rejectReason}`);
16057
16161
  continue;
16058
16162
  }
16059
16163
  const totalRequired = orderOutputs.reduce(
@@ -16062,17 +16166,17 @@ var BidManager = class {
16062
16166
  );
16063
16167
  if (totalOffered.lt(totalRequired)) {
16064
16168
  console.log(
16065
- `[BidManager] Bid from solver=${bid.userOp.sender}: partial fill candidate (fallback) (offered=${totalOffered.toString()}, required=${totalRequired.toString()}, covers=${totalOffered.div(totalRequired).mul(100).toFixed(2)}%)`
16169
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (fallback) (offered=${totalOffered.toString()}, required=${totalRequired.toString()}, covers=${totalOffered.div(totalRequired).mul(100).toFixed(2)}%)`
16066
16170
  );
16067
16171
  } else {
16068
16172
  console.log(
16069
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED (fallback): totalOffered=${totalOffered.toString()}`
16173
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED (fallback): totalOffered=${totalOffered.toString()}`
16070
16174
  );
16071
16175
  }
16072
- validBids.push({ bid, options, totalOffered });
16176
+ validBids.push({ bid, totalOffered });
16073
16177
  }
16074
16178
  validBids.sort((a, b) => b.totalOffered.comparedTo(a.totalOffered));
16075
- return validBids.map(({ totalOffered: _, ...rest }) => rest);
16179
+ return validBids.map(({ bid }) => bid);
16076
16180
  }
16077
16181
  // ── Token classification helpers ──────────────────────────────────
16078
16182
  /**
@@ -16681,6 +16785,262 @@ var OrderStatusChecker = class {
16681
16785
  }
16682
16786
  };
16683
16787
 
16788
+ // src/protocols/intents/quote/types.ts
16789
+ var UnsupportedIntentQuoteStrategyError = class extends Error {
16790
+ constructor(strategy) {
16791
+ super(`Unsupported intent quote strategy: ${strategy}`);
16792
+ this.name = "UnsupportedIntentQuoteStrategyError";
16793
+ }
16794
+ };
16795
+ var UnsupportedIntentQuotePairError = class extends Error {
16796
+ constructor(params) {
16797
+ super(
16798
+ `No Uniswap v4 pool config found for ${params.tokenIn.symbol ?? params.tokenIn.address} -> ${params.tokenOut.symbol ?? params.tokenOut.address} on ${params.source} -> ${params.destination}`
16799
+ );
16800
+ this.name = "UnsupportedIntentQuotePairError";
16801
+ }
16802
+ };
16803
+
16804
+ // src/protocols/intents/quote/uniswapV4.ts
16805
+ var UNISWAP_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
16806
+ var BPS_DENOMINATOR = 10000n;
16807
+ var UniswapV4IntentQuoteStrategy = class {
16808
+ constructor(chainConfigService) {
16809
+ this.chainConfigService = chainConfigService;
16810
+ }
16811
+ chainConfigService;
16812
+ baseQuoteClient;
16813
+ async quote(params, source, destination) {
16814
+ this.validateQuoteParams(params);
16815
+ const protocolFeeBps = await this.readProtocolFeeBps(source.client, source.stateMachineId);
16816
+ const quoteClient = this.resolveQuoteClient(source, destination);
16817
+ const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, UNISWAP_INTENT_QUOTE_CHAIN);
16818
+ return params.amountIn !== void 0 ? this.quoteExactInput({ params, client: quoteClient, protocolFeeBps, poolConfig }) : this.quoteExactOutput({ params, client: quoteClient, protocolFeeBps, poolConfig });
16819
+ }
16820
+ resolveQuoteClient(source, destination) {
16821
+ if (source.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return source.client;
16822
+ if (destination.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return destination.client;
16823
+ if (this.baseQuoteClient) return this.baseQuoteClient;
16824
+ const rpcUrl = this.chainConfigService.getRpcUrl(UNISWAP_INTENT_QUOTE_CHAIN);
16825
+ if (!rpcUrl) throw new Error(`RPC URL is not configured for ${UNISWAP_INTENT_QUOTE_CHAIN}`);
16826
+ const baseQuoteClient = viem.createPublicClient({
16827
+ chain: chains$1.base,
16828
+ transport: viem.http(rpcUrl)
16829
+ });
16830
+ this.baseQuoteClient = baseQuoteClient;
16831
+ return baseQuoteClient;
16832
+ }
16833
+ validateQuoteParams(params) {
16834
+ const hasAmountIn = params.amountIn !== void 0;
16835
+ const hasAmountOut = params.amountOut !== void 0;
16836
+ if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
16837
+ if (hasAmountIn && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
16838
+ if (hasAmountOut && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
16839
+ if (params.tokenIn.address.toLowerCase() === params.tokenOut.address.toLowerCase()) {
16840
+ throw new Error("tokenIn and tokenOut cannot be the same");
16841
+ }
16842
+ }
16843
+ async readProtocolFeeBps(client, chain) {
16844
+ const gatewayAddress = this.chainConfigService.getIntentGatewayAddress(chain);
16845
+ if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === viem.zeroAddress) {
16846
+ throw new Error(`IntentGatewayV2 is not configured for chain ${chain}`);
16847
+ }
16848
+ const gatewayParams = await client.readContract({
16849
+ address: gatewayAddress,
16850
+ abi: IntentGatewayV2_default.ABI,
16851
+ functionName: "params"
16852
+ });
16853
+ if (isGatewayParamsTuple(gatewayParams)) return BigInt(gatewayParams[4]);
16854
+ return BigInt(gatewayParams.protocolFeeBps ?? 0);
16855
+ }
16856
+ resolvePoolConfig(params, source, destination) {
16857
+ const override = params.uniswapV4?.poolKey;
16858
+ if (override) {
16859
+ const poolKey = normalizePoolKey(override);
16860
+ const tokenInForQuote = viem.getAddress(override.currencyIn ?? params.tokenIn.address);
16861
+ if (tokenInForQuote !== poolKey.currency0 && tokenInForQuote !== poolKey.currency1) {
16862
+ throw new Error(
16863
+ `Input currency ${tokenInForQuote} is not part of the override pool (${poolKey.currency0}, ${poolKey.currency1}). For cross-chain quotes pass uniswapV4.poolKey.currencyIn with the Base-side input currency address.`
16864
+ );
16865
+ }
16866
+ return {
16867
+ poolKey,
16868
+ quoterAddress: this.resolveQuoterAddress(destination, override.quoterAddress),
16869
+ tokenInForQuote
16870
+ };
16871
+ }
16872
+ const poolConfig = this.resolveConfiguredPool(params, destination);
16873
+ if (poolConfig) return poolConfig;
16874
+ throw new UnsupportedIntentQuotePairError({
16875
+ source,
16876
+ destination,
16877
+ tokenIn: params.tokenIn,
16878
+ tokenOut: params.tokenOut
16879
+ });
16880
+ }
16881
+ resolveConfiguredPool(params, chain) {
16882
+ for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(chain)) {
16883
+ const resolvedPool = this.resolveConfiguredPoolTokens(chain, pool);
16884
+ if (!resolvedPool) continue;
16885
+ const tokenInForQuote = matchPoolToken(params.tokenIn, resolvedPool);
16886
+ const tokenOutForQuote = matchPoolToken(params.tokenOut, resolvedPool);
16887
+ if (!tokenInForQuote || !tokenOutForQuote || tokenInForQuote.symbol === tokenOutForQuote.symbol) continue;
16888
+ const { currency0, currency1 } = sortCurrencies(resolvedPool[0].address, resolvedPool[1].address);
16889
+ return {
16890
+ poolKey: {
16891
+ currency0,
16892
+ currency1,
16893
+ fee: pool.fee,
16894
+ tickSpacing: pool.tickSpacing,
16895
+ hooks: viem.getAddress(pool.hooks ?? viem.zeroAddress)
16896
+ },
16897
+ quoterAddress: this.resolveQuoterAddress(chain),
16898
+ tokenInForQuote: tokenInForQuote.address
16899
+ };
16900
+ }
16901
+ return null;
16902
+ }
16903
+ resolveQuoterAddress(chain, override) {
16904
+ const address = override ?? this.chainConfigService.getUniswapV4QuoterAddress(chain);
16905
+ if (!address || address === "0x" || address === viem.zeroAddress) {
16906
+ throw new Error(`Uniswap V4 quoter is not configured for chain ${chain}`);
16907
+ }
16908
+ return viem.getAddress(address);
16909
+ }
16910
+ resolveConfiguredPoolTokens(chain, pool) {
16911
+ const first = this.resolveConfiguredPoolToken(chain, pool.tokens[0]);
16912
+ const second = this.resolveConfiguredPoolToken(chain, pool.tokens[1]);
16913
+ return first && second ? [first, second] : null;
16914
+ }
16915
+ resolveConfiguredPoolToken(chain, symbol) {
16916
+ const address = this.chainConfigService.getAssetAddress(chain, symbol);
16917
+ if (!address || address === "0x") return null;
16918
+ return { symbol, address: viem.getAddress(address) };
16919
+ }
16920
+ async quoteExactInput(args) {
16921
+ const amountIn = args.params.amountIn;
16922
+ const swapAmountIn = deductProtocolFee(amountIn, args.protocolFeeBps);
16923
+ const amountOut = await this.readV4QuoteExactInput(args.client, args.poolConfig, swapAmountIn);
16924
+ return {
16925
+ strategy: "uniswap_v4",
16926
+ tradeType: "EXACT_INPUT",
16927
+ amountIn,
16928
+ amountOut,
16929
+ quoteMetadata: {
16930
+ quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
16931
+ poolKey: args.poolConfig.poolKey,
16932
+ quoterAddress: args.poolConfig.quoterAddress,
16933
+ protocolFeeBps: args.protocolFeeBps
16934
+ }
16935
+ };
16936
+ }
16937
+ async quoteExactOutput(args) {
16938
+ const amountOut = args.params.amountOut;
16939
+ const swapAmountIn = await this.readV4QuoteExactOutput(args.client, args.poolConfig, amountOut);
16940
+ const amountIn = grossUpForProtocolFee(swapAmountIn, args.protocolFeeBps);
16941
+ return {
16942
+ strategy: "uniswap_v4",
16943
+ tradeType: "EXACT_OUTPUT",
16944
+ amountIn,
16945
+ amountOut,
16946
+ quoteMetadata: {
16947
+ quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
16948
+ poolKey: args.poolConfig.poolKey,
16949
+ quoterAddress: args.poolConfig.quoterAddress,
16950
+ protocolFeeBps: args.protocolFeeBps
16951
+ }
16952
+ };
16953
+ }
16954
+ async readV4QuoteExactInput(client, poolConfig, amountIn) {
16955
+ const data = viem.encodeFunctionData({
16956
+ abi: UNISWAP_V4_QUOTER_ABI,
16957
+ functionName: "quoteExactInputSingle",
16958
+ args: [
16959
+ {
16960
+ poolKey: poolConfig.poolKey,
16961
+ zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
16962
+ exactAmount: amountIn,
16963
+ hookData: "0x"
16964
+ }
16965
+ ]
16966
+ });
16967
+ const response = await client.call({ to: poolConfig.quoterAddress, data });
16968
+ if (!response.data || response.data === "0x") {
16969
+ throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
16970
+ }
16971
+ const [amountOut] = viem.decodeFunctionResult({
16972
+ abi: UNISWAP_V4_QUOTER_ABI,
16973
+ functionName: "quoteExactInputSingle",
16974
+ data: response.data
16975
+ });
16976
+ return amountOut;
16977
+ }
16978
+ async readV4QuoteExactOutput(client, poolConfig, amountOut) {
16979
+ const data = viem.encodeFunctionData({
16980
+ abi: UNISWAP_V4_QUOTER_ABI,
16981
+ functionName: "quoteExactOutputSingle",
16982
+ args: [
16983
+ {
16984
+ poolKey: poolConfig.poolKey,
16985
+ zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
16986
+ exactAmount: amountOut,
16987
+ hookData: "0x"
16988
+ }
16989
+ ]
16990
+ });
16991
+ const response = await client.call({ to: poolConfig.quoterAddress, data });
16992
+ if (!response.data || response.data === "0x") {
16993
+ throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
16994
+ }
16995
+ const [amountIn] = viem.decodeFunctionResult({
16996
+ abi: UNISWAP_V4_QUOTER_ABI,
16997
+ functionName: "quoteExactOutputSingle",
16998
+ data: response.data
16999
+ });
17000
+ return amountIn;
17001
+ }
17002
+ };
17003
+ function normalizePoolKey(poolKey) {
17004
+ return {
17005
+ currency0: viem.getAddress(poolKey.currency0),
17006
+ currency1: viem.getAddress(poolKey.currency1),
17007
+ fee: poolKey.fee,
17008
+ tickSpacing: poolKey.tickSpacing,
17009
+ hooks: viem.getAddress(poolKey.hooks)
17010
+ };
17011
+ }
17012
+ function sortCurrencies(tokenIn, tokenOut) {
17013
+ const input = viem.getAddress(tokenIn);
17014
+ const output = viem.getAddress(tokenOut);
17015
+ return BigInt(input) < BigInt(output) ? { currency0: input, currency1: output } : { currency0: output, currency1: input };
17016
+ }
17017
+ function matchPoolToken(token, poolTokens) {
17018
+ const tokenAddress = token.address.toLowerCase();
17019
+ const addressMatch = poolTokens.find((poolToken) => poolToken.address.toLowerCase() === tokenAddress);
17020
+ if (addressMatch) return addressMatch;
17021
+ const tokenSymbol = token.symbol?.toUpperCase();
17022
+ if (!tokenSymbol) return null;
17023
+ return poolTokens.find((poolToken) => poolToken.symbol.toUpperCase() === tokenSymbol) ?? null;
17024
+ }
17025
+ function getZeroForOne(tokenIn, poolKey) {
17026
+ return viem.getAddress(tokenIn).toLowerCase() === viem.getAddress(poolKey.currency0).toLowerCase();
17027
+ }
17028
+ function isGatewayParamsTuple(value) {
17029
+ return Array.isArray(value);
17030
+ }
17031
+ function deductProtocolFee(amount, protocolFeeBps) {
17032
+ if (protocolFeeBps <= 0n) return amount;
17033
+ const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
17034
+ return amount - fee;
17035
+ }
17036
+ function grossUpForProtocolFee(netAmount, protocolFeeBps) {
17037
+ if (protocolFeeBps <= 0n) return netAmount;
17038
+ return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
17039
+ }
17040
+ function divCeil(numerator, denominator) {
17041
+ return (numerator + denominator - 1n) / denominator;
17042
+ }
17043
+
16684
17044
  // src/protocols/intents/IntentGateway.ts
16685
17045
  var IntentGateway = class _IntentGateway {
16686
17046
  /** EVM chain on which orders are placed and escrowed. */
@@ -16707,6 +17067,8 @@ var IntentGateway = class _IntentGateway {
16707
17067
  bidManager;
16708
17068
  /** Estimates gas costs for filling an order and converts them to fee-token amounts. */
16709
17069
  gasEstimator;
17070
+ /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17071
+ quoteStrategies;
16710
17072
  /**
16711
17073
  * Private constructor — use {@link IntentGateway.create} instead.
16712
17074
  *
@@ -16744,12 +17106,15 @@ var IntentGateway = class _IntentGateway {
16744
17106
  const bidManager = new BidManager(this.ctx, crypto);
16745
17107
  const gasEstimator = new GasEstimator(this.ctx, crypto);
16746
17108
  this.orderPlacer = new OrderPlacer(this.ctx);
16747
- this.orderExecutor = new OrderExecutor(this.ctx, bidManager, crypto);
17109
+ this.orderExecutor = new OrderExecutor(this.ctx, bidManager);
16748
17110
  this.orderCanceller = new OrderCanceller(this.ctx);
16749
17111
  this.orderStatusChecker = new OrderStatusChecker(this.ctx);
16750
17112
  this.bidManager = bidManager;
16751
17113
  this.gasEstimator = gasEstimator;
16752
17114
  this._crypto = crypto;
17115
+ this.quoteStrategies = {
17116
+ uniswap_v4: new UniswapV4IntentQuoteStrategy(dest.configService)
17117
+ };
16753
17118
  }
16754
17119
  /**
16755
17120
  * Creates an initialized IntentGateway instance.
@@ -16794,6 +17159,33 @@ var IntentGateway = class _IntentGateway {
16794
17159
  }
16795
17160
  }
16796
17161
  }
17162
+ /**
17163
+ * Quotes an intent between this gateway's source and destination chains.
17164
+ *
17165
+ * `strategy` defaults to `uniswap_v4`, currently the only supported
17166
+ * strategy. Provide exactly one of `amountIn` or `amountOut`.
17167
+ *
17168
+ * The Uniswap quote strategy always prices against the configured Base
17169
+ * pool, regardless of this gateway's destination chain. Returned
17170
+ * `amountIn`/`amountOut` already account for the gateway's protocol fee
17171
+ * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
17172
+ * inputs; apply only your own slippage tolerance before placing the order.
17173
+ *
17174
+ * @param params - Token pair, amount, and optional strategy/pool overrides.
17175
+ * @returns The quoted amounts plus strategy-specific metadata.
17176
+ * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
17177
+ * @throws {UnsupportedIntentQuotePairError} When no pool is configured for the pair.
17178
+ */
17179
+ async quoteIntent(params) {
17180
+ const strategy = params.strategy ?? "uniswap_v4";
17181
+ const handler = this.quoteStrategies[strategy];
17182
+ if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17183
+ return handler.quote(
17184
+ { ...params, strategy },
17185
+ { stateMachineId: this.source.config.stateMachineId, client: this.source.client },
17186
+ { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client }
17187
+ );
17188
+ }
16797
17189
  /**
16798
17190
  * Bidirectional async generator that orchestrates the full order lifecycle:
16799
17191
  * placement, fee estimation, bid collection, and execution.
@@ -16848,17 +17240,38 @@ var IntentGateway = class _IntentGateway {
16848
17240
  }
16849
17241
  const { order: finalizedOrder, receipt: placementReceipt } = placeOrderSecond.value;
16850
17242
  yield { status: "ORDER_PLACED", order: finalizedOrder, receipt: placementReceipt };
16851
- for await (const status of this.orderExecutor.executeOrder({
16852
- order: finalizedOrder,
16853
- sessionPrivateKey,
16854
- auctionTimeMs: options.auctionTimeMs,
16855
- pollIntervalMs: options.pollIntervalMs,
16856
- solver: options.solver
16857
- })) {
16858
- yield status;
16859
- }
17243
+ yield* this.driveExecution(
17244
+ this.orderExecutor.executeOrder({
17245
+ order: finalizedOrder,
17246
+ sessionPrivateKey,
17247
+ auctionTimeMs: options.auctionTimeMs,
17248
+ pollIntervalMs: options.pollIntervalMs,
17249
+ solver: options.solver
17250
+ })
17251
+ );
16860
17252
  return;
16861
17253
  }
17254
+ /**
17255
+ * Forwards updates from the executor's bidirectional generator to the caller,
17256
+ * threading the {@link SelectBidResult} the caller feeds back after a
17257
+ * `BIDS_RECEIVED` yield into the executor's `.next()`. Other yields expect no
17258
+ * feedback. This is what lets the consumer own `bid.execute()` while the
17259
+ * executor keeps tracking fills and continuing the auction.
17260
+ */
17261
+ async *driveExecution(execGen) {
17262
+ try {
17263
+ let input;
17264
+ while (true) {
17265
+ const { value, done } = await execGen.next(input);
17266
+ input = void 0;
17267
+ if (done) break;
17268
+ const fed = yield value;
17269
+ if (value.status === "BIDS_RECEIVED") input = fed;
17270
+ }
17271
+ } finally {
17272
+ await execGen.return();
17273
+ }
17274
+ }
16862
17275
  /**
16863
17276
  * Validates that an order has the minimum fields required for post-placement
16864
17277
  * resume (i.e. it was previously placed and has an on-chain identity).
@@ -16895,14 +17308,99 @@ var IntentGateway = class _IntentGateway {
16895
17308
  */
16896
17309
  async *resume(order, options) {
16897
17310
  this.assertOrderCanResume(order);
16898
- for await (const status of this.orderExecutor.executeOrder({
16899
- order,
16900
- sessionPrivateKey: options.sessionPrivateKey,
16901
- auctionTimeMs: options.auctionTimeMs,
16902
- pollIntervalMs: options.pollIntervalMs,
16903
- solver: options.solver
16904
- })) {
16905
- yield status;
17311
+ yield* this.driveExecution(
17312
+ this.orderExecutor.executeOrder({
17313
+ order,
17314
+ sessionPrivateKey: options.sessionPrivateKey,
17315
+ auctionTimeMs: options.auctionTimeMs,
17316
+ pollIntervalMs: options.pollIntervalMs,
17317
+ solver: options.solver
17318
+ })
17319
+ );
17320
+ }
17321
+ /**
17322
+ * Batteries-included variant of {@link execute}: places the order and then
17323
+ * auto-selects the best bid each round via {@link selectAndExecuteBest}, with
17324
+ * no bid-selection input from the caller.
17325
+ *
17326
+ * The caller still signs the placement transaction: this generator yields
17327
+ * `AWAITING_PLACE_ORDER` and the caller must hand the signed tx back via
17328
+ * `gen.next(signedTx)` exactly as with {@link execute}. Every other stage
17329
+ * (`BIDS_RECEIVED`, `BID_SELECTED`, `FILLED`, …) is handled automatically and
17330
+ * surfaced for observation, so the rest of the loop needs no feedback.
17331
+ *
17332
+ * @param order - The order to place and execute.
17333
+ * @param graffiti - Optional orderflow-attribution tag.
17334
+ * @param options - Same tuning parameters as {@link execute}.
17335
+ * @yields {@link IntentOrderStatusUpdate} at each lifecycle stage.
17336
+ */
17337
+ async *executeBest(order, graffiti = DEFAULT_GRAFFITI, options) {
17338
+ const gen = this.execute(order, graffiti, options);
17339
+ try {
17340
+ let input;
17341
+ while (true) {
17342
+ const { value, done } = await gen.next(input);
17343
+ input = void 0;
17344
+ if (done) break;
17345
+ if (value.status === "BIDS_RECEIVED") {
17346
+ yield value;
17347
+ input = await this.autoSelect(order, value.bids);
17348
+ } else if (value.status === "AWAITING_PLACE_ORDER") {
17349
+ input = yield value;
17350
+ } else {
17351
+ yield value;
17352
+ }
17353
+ }
17354
+ } finally {
17355
+ await gen.return();
17356
+ }
17357
+ }
17358
+ /**
17359
+ * Batteries-included variant of {@link resume}: auto-selects the best bid each
17360
+ * round via {@link selectAndExecuteBest}, with no bid-selection input from the
17361
+ * caller. A plain `for await` loop is sufficient — there is no placement step.
17362
+ *
17363
+ * @param order - A previously placed order with a valid `id` and `session`.
17364
+ * @param options - Optional tuning parameters for bid collection and execution.
17365
+ * @yields {@link IntentOrderStatusUpdate} at each execution stage.
17366
+ */
17367
+ async *resumeBest(order, options) {
17368
+ const gen = this.resume(order, options);
17369
+ try {
17370
+ let input;
17371
+ while (true) {
17372
+ const { value, done } = await gen.next(input);
17373
+ input = void 0;
17374
+ if (done) break;
17375
+ yield value;
17376
+ if (value.status === "BIDS_RECEIVED") {
17377
+ input = await this.autoSelect(order, value.bids);
17378
+ }
17379
+ }
17380
+ } finally {
17381
+ await gen.return();
17382
+ }
17383
+ }
17384
+ /**
17385
+ * Auto-select wrapper used by {@link executeBest} / {@link resumeBest}.
17386
+ *
17387
+ * Runs {@link selectAndExecuteBest} and returns the {@link SelectBidResult} to
17388
+ * feed back to the executor. If selection fails this round — all bids fail
17389
+ * simulation, no valid bids, or the bundler rejects the UserOp — it swallows
17390
+ * the error and returns `undefined`, which tells the executor to keep polling
17391
+ * for fresh bids until the deadline rather than aborting the order. Swallowing
17392
+ * the error here (rather than letting it propagate) also keeps the executor's
17393
+ * `finally` teardown intact, since nothing throws across the suspended
17394
+ * generators.
17395
+ */
17396
+ async autoSelect(order, bids) {
17397
+ try {
17398
+ return await this.selectAndExecuteBest(order, bids);
17399
+ } catch (err) {
17400
+ console.warn(
17401
+ `[IntentGateway] autoSelect: bid selection failed this round, continuing to poll: ${err instanceof Error ? err.message : String(err)}`
17402
+ );
17403
+ return void 0;
16906
17404
  }
16907
17405
  }
16908
17406
  /**
@@ -16958,10 +17456,52 @@ var IntentGateway = class _IntentGateway {
16958
17456
  return this.bidManager.prepareSubmitBid(options);
16959
17457
  }
16960
17458
  /**
16961
- * Selects the best available bid, simulates it, and submits the UserOperation
16962
- * to the bundler.
17459
+ * Decodes raw filler bids into first-class {@link Bid} objects that can be
17460
+ * ranked, simulated, and executed by the consumer.
17461
+ *
17462
+ * Delegates to {@link BidManager.buildBids}.
17463
+ *
17464
+ * @param order - The placed order the bids are competing to fill.
17465
+ * @param bids - Raw filler bids fetched from the coprocessor.
17466
+ * @param sessionPrivateKey - Optional session key override; looked up from
17467
+ * storage by `order.session` if omitted.
17468
+ * @returns Array of executable {@link Bid} objects.
17469
+ */
17470
+ buildBids(order, bids, sessionPrivateKey) {
17471
+ return this.bidManager.buildBids(order, bids, sessionPrivateKey);
17472
+ }
17473
+ /**
17474
+ * Sorts bids by output value using the same strategy the autopilot uses.
17475
+ *
17476
+ * Delegates to {@link BidManager.sortBids}.
17477
+ *
17478
+ * @param order - The placed order whose output spec drives sorting.
17479
+ * @param bids - Bids to sort (from {@link buildBids}).
17480
+ * @returns Sorted array of {@link Bid} objects.
17481
+ */
17482
+ async sortBids(order, bids) {
17483
+ return this.bidManager.sortBids(order, bids);
17484
+ }
17485
+ /**
17486
+ * Autopilot bid selection: sorts the given bids, simulates each until one
17487
+ * passes, then executes it.
17488
+ *
17489
+ * Delegates to {@link BidManager.selectAndExecuteBest}.
17490
+ *
17491
+ * @param order - The placed order to fill.
17492
+ * @param bids - Candidate bids (from {@link buildBids}).
17493
+ * @returns A {@link SelectBidResult} with the submitted UserOperation, hashes,
17494
+ * and fill status.
17495
+ */
17496
+ async selectAndExecuteBest(order, bids) {
17497
+ return this.bidManager.selectAndExecuteBest(order, bids);
17498
+ }
17499
+ /**
17500
+ * Decodes, sorts, simulates, signs, and submits the best of the given raw
17501
+ * filler bids with no per-bid input from the caller.
16963
17502
  *
16964
- * Delegates to {@link BidManager.selectBid}.
17503
+ * Delegates to {@link BidManager.selectBid}. Prefer {@link buildBids} +
17504
+ * {@link selectAndExecuteBest} (or {@link executeBest}) for new code.
16965
17505
  *
16966
17506
  * @param order - The placed order to fill.
16967
17507
  * @param bids - Raw filler bids fetched from the coprocessor.
@@ -17881,7 +18421,7 @@ var HyperFungibleTokenABI = [
17881
18421
  internalType: "uint256"
17882
18422
  }
17883
18423
  ],
17884
- stateMutability: "view"
18424
+ stateMutability: "nonpayable"
17885
18425
  },
17886
18426
  {
17887
18427
  type: "function",
@@ -17932,7 +18472,7 @@ var HyperFungibleTokenABI = [
17932
18472
  internalType: "uint256"
17933
18473
  }
17934
18474
  ],
17935
- stateMutability: "view"
18475
+ stateMutability: "nonpayable"
17936
18476
  },
17937
18477
  {
17938
18478
  type: "function",
@@ -17983,7 +18523,7 @@ var HyperFungibleTokenABI = [
17983
18523
  internalType: "uint256"
17984
18524
  }
17985
18525
  ],
17986
- stateMutability: "view"
18526
+ stateMutability: "nonpayable"
17987
18527
  },
17988
18528
  {
17989
18529
  type: "function",
@@ -19002,7 +19542,7 @@ var WrappedHyperFungibleTokenABI = [
19002
19542
  internalType: "uint256"
19003
19543
  }
19004
19544
  ],
19005
- stateMutability: "view"
19545
+ stateMutability: "nonpayable"
19006
19546
  },
19007
19547
  {
19008
19548
  type: "function",
@@ -19053,7 +19593,7 @@ var WrappedHyperFungibleTokenABI = [
19053
19593
  internalType: "uint256"
19054
19594
  }
19055
19595
  ],
19056
- stateMutability: "view"
19596
+ stateMutability: "nonpayable"
19057
19597
  },
19058
19598
  {
19059
19599
  type: "function",
@@ -19104,7 +19644,7 @@ var WrappedHyperFungibleTokenABI = [
19104
19644
  internalType: "uint256"
19105
19645
  }
19106
19646
  ],
19107
- stateMutability: "view"
19647
+ stateMutability: "nonpayable"
19108
19648
  },
19109
19649
  {
19110
19650
  type: "function",
@@ -19646,13 +20186,13 @@ var HyperFungibleToken = class {
19646
20186
  };
19647
20187
  let totalNativeCost = 0n;
19648
20188
  try {
19649
- totalNativeCost = await this.source.client.readContract({
20189
+ const { result } = await this.source.client.simulateContract({
19650
20190
  address: params.token,
19651
20191
  abi: HyperFungibleTokenABI,
19652
20192
  functionName: "quote",
19653
20193
  args: [sendParams]
19654
20194
  });
19655
- totalNativeCost = totalNativeCost * 101n / 100n;
20195
+ totalNativeCost = result * 101n / 100n;
19656
20196
  } catch {
19657
20197
  }
19658
20198
  return {
@@ -21606,6 +22146,8 @@ exports.TimeoutStatus = TimeoutStatus;
21606
22146
  exports.TokenGateway = TokenGateway;
21607
22147
  exports.TronChain = TronChain;
21608
22148
  exports.USE_ETHERSCAN_CHAINS = USE_ETHERSCAN_CHAINS;
22149
+ exports.UnsupportedIntentQuotePairError = UnsupportedIntentQuotePairError;
22150
+ exports.UnsupportedIntentQuoteStrategyError = UnsupportedIntentQuoteStrategyError;
21609
22151
  exports.WrappedHyperFungibleTokenABI = WrappedHyperFungibleTokenABI;
21610
22152
  exports.__test = __test;
21611
22153
  exports.adjustDecimals = adjustDecimals;