@hyperbridge/sdk 2.2.0 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  };
@@ -10234,26 +10272,27 @@ var PostRequestClient = class {
10234
10272
  * accompanying timeout-proof calldata.
10235
10273
  */
10236
10274
  async addTimeoutFinalityEvents(request) {
10237
- const destChain = this.ctx.config.dest;
10238
- const hyperbridge = this.ctx.config.hyperbridge;
10239
10275
  const events = [];
10240
- const commitment = postRequestCommitment(request).commitment;
10241
- const receipt = await destChain.queryRequestReceipt(commitment);
10242
- const destTimestamp = await destChain.timestamp();
10243
10276
  const commit = (req) => {
10244
10277
  this.logger.trace(`Added ${events.length} timeout events`, events);
10245
10278
  request.statuses = [...req.statuses, ...events];
10246
10279
  return request;
10247
10280
  };
10248
10281
  if (request.timeoutTimestamp === 0n) return commit(request);
10282
+ if (request.statuses.some(
10283
+ (item) => item.status === RequestStatus.DESTINATION || item.status === TimeoutStatus.TIMED_OUT
10284
+ ))
10285
+ return commit(request);
10286
+ const destChain = this.ctx.config.dest;
10287
+ const hyperbridge = this.ctx.config.hyperbridge;
10288
+ const commitment = postRequestCommitment(request).commitment;
10289
+ const receipt = await destChain.queryRequestReceipt(commitment);
10290
+ const destTimestamp = await destChain.timestamp();
10249
10291
  if (receipt || request.timeoutTimestamp > destTimestamp) return commit(request);
10250
- const is_finished = request.statuses.find((item) => item.status === RequestStatus.DESTINATION);
10251
- if (!is_finished) {
10252
- events.push({
10253
- status: TimeoutStatus.PENDING_TIMEOUT,
10254
- metadata: { blockHash: "0x", blockNumber: 0, transactionHash: "0x" }
10255
- });
10256
- }
10292
+ events.push({
10293
+ status: TimeoutStatus.PENDING_TIMEOUT,
10294
+ metadata: { blockHash: "0x", blockNumber: 0, transactionHash: "0x" }
10295
+ });
10257
10296
  const delivered = request.statuses.find((item) => item.status === RequestStatus.HYPERBRIDGE_DELIVERED);
10258
10297
  let hyperbridgeFinalized;
10259
10298
  if (!delivered) {
@@ -10544,6 +10583,40 @@ var PostRequestClient = class {
10544
10583
  async buildFinalized(request, hyperbridgeDelivered) {
10545
10584
  const destChain = this.ctx.config.dest;
10546
10585
  const hyperbridge = this.ctx.config.hyperbridge;
10586
+ const { config } = hyperbridge;
10587
+ const finality = await this.queries.queryStateMachineUpdateByHeight({
10588
+ statemachineId: config.stateMachineId,
10589
+ height: hyperbridgeDelivered.metadata.blockNumber,
10590
+ chain: config.stateMachineId
10591
+ });
10592
+ if (finality) {
10593
+ const proof = await hyperbridge.queryProof(
10594
+ { Requests: [postRequestCommitment(request).commitment] },
10595
+ request.dest,
10596
+ BigInt(finality.height)
10597
+ );
10598
+ const calldata = destChain.encode({
10599
+ kind: "PostRequest",
10600
+ proof: {
10601
+ stateMachine: config.stateMachineId,
10602
+ consensusStateId: config.consensusStateId,
10603
+ proof,
10604
+ height: BigInt(finality.height)
10605
+ },
10606
+ requests: [request],
10607
+ signer: viem.pad("0x")
10608
+ });
10609
+ return {
10610
+ status: RequestStatus.HYPERBRIDGE_FINALIZED,
10611
+ metadata: {
10612
+ blockHash: finality.blockHash,
10613
+ blockNumber: finality.height,
10614
+ transactionHash: finality.transactionHash,
10615
+ timestamp: finality.timestamp,
10616
+ calldata
10617
+ }
10618
+ };
10619
+ }
10547
10620
  if (destChain instanceof EvmChain) {
10548
10621
  const hyperbridgeSubstrate = hyperbridge;
10549
10622
  const currentEpoch = await destChain.currentEpoch();
@@ -10552,18 +10625,18 @@ var PostRequestClient = class {
10552
10625
  currentEpoch
10553
10626
  );
10554
10627
  if (!consensusResult) return void 0;
10555
- const proof2 = await hyperbridge.queryProof(
10628
+ const proof = await hyperbridge.queryProof(
10556
10629
  { Requests: [postRequestCommitment(request).commitment] },
10557
10630
  request.dest,
10558
10631
  consensusResult.provenHeight
10559
10632
  );
10560
- const calldata2 = destChain.encode({
10633
+ const calldata = destChain.encode({
10561
10634
  kind: "BatchConsensusAndPostRequest",
10562
10635
  consensusProofs: consensusResult.proofs,
10563
10636
  proof: {
10564
- stateMachine: this.ctx.config.hyperbridge.config.stateMachineId,
10565
- consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10566
- proof: proof2,
10637
+ stateMachine: config.stateMachineId,
10638
+ consensusStateId: config.consensusStateId,
10639
+ proof,
10567
10640
  height: consensusResult.provenHeight
10568
10641
  },
10569
10642
  requests: [request],
@@ -10577,42 +10650,11 @@ var PostRequestClient = class {
10577
10650
  transactionHash: hyperbridgeDelivered.metadata.transactionHash,
10578
10651
  // @ts-ignore
10579
10652
  timestamp: hyperbridgeDelivered.metadata.timestamp,
10580
- calldata: calldata2
10653
+ calldata
10581
10654
  }
10582
10655
  };
10583
10656
  }
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
- };
10657
+ return void 0;
10616
10658
  }
10617
10659
  /**
10618
10660
  * Streaming helper: waits for the consensus proof, fetches the messaging
@@ -10626,14 +10668,19 @@ var PostRequestClient = class {
10626
10668
  const stateMachineId = this.ctx.config.hyperbridge.config.stateMachineId;
10627
10669
  const neededHeight = BigInt(request.statuses[hyperbridgeDeliveredIndex].metadata.blockNumber);
10628
10670
  this.logger.trace(`[streamFinalized] neededHeight=${neededHeight}`);
10629
- if (destChain instanceof EvmChain) {
10671
+ const commitment = postRequestCommitment(request).commitment;
10672
+ let finality = await this.queries.queryStateMachineUpdateByHeight({
10673
+ statemachineId: stateMachineId,
10674
+ height: Number(neededHeight),
10675
+ chain: stateMachineId
10676
+ });
10677
+ if (!finality && destChain instanceof EvmChain) {
10630
10678
  const hyperbridgeSubstrate = hyperbridge;
10631
10679
  const currentEpoch = await destChain.currentEpoch();
10632
10680
  const consensusResult = await waitOrAbort(this.ctx, {
10633
10681
  signal,
10634
10682
  promise: () => hyperbridgeSubstrate.queryConsensusProofs(neededHeight, currentEpoch)
10635
10683
  });
10636
- const commitment = postRequestCommitment(request).commitment;
10637
10684
  this.logger.trace(
10638
10685
  `[streamFinalized] consensusProofs found (${consensusResult.proofs.length} proofs), provenHeight=${consensusResult.provenHeight}, commitment=${commitment}, dest=${request.dest}`
10639
10686
  );
@@ -10665,45 +10712,45 @@ var PostRequestClient = class {
10665
10712
  }
10666
10713
  };
10667
10714
  }
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
- });
10715
+ if (!finality) {
10716
+ finality = await waitOrAbort(this.ctx, {
10717
+ signal,
10718
+ promise: () => this.queries.queryStateMachineUpdateByHeight({
10719
+ statemachineId: stateMachineId,
10720
+ height: Number(neededHeight),
10721
+ chain: stateMachineId
10722
+ })
10723
+ });
10724
+ }
10676
10725
  const proof = await this.fetchProofWithRetry(
10677
10726
  signal,
10678
- () => hyperbridge.queryProof(
10679
- { Requests: [postRequestCommitment(request).commitment] },
10680
- request.dest,
10681
- BigInt(hyperbridgeFinalized.height)
10682
- )
10727
+ () => hyperbridge.queryProof({ Requests: [commitment] }, request.dest, BigInt(finality.height))
10683
10728
  );
10729
+ if (!(destChain instanceof EvmChain)) {
10730
+ const { stateId } = parseStateMachineId(stateMachineId);
10731
+ await waitForChallengePeriod(destChain, {
10732
+ height: BigInt(finality.height),
10733
+ id: { stateId, consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId }
10734
+ });
10735
+ }
10684
10736
  const calldata = destChain.encode({
10685
10737
  kind: "PostRequest",
10686
10738
  proof: {
10687
10739
  stateMachine: stateMachineId,
10688
10740
  consensusStateId: this.ctx.config.hyperbridge.config.consensusStateId,
10689
10741
  proof,
10690
- height: BigInt(hyperbridgeFinalized.height)
10742
+ height: BigInt(finality.height)
10691
10743
  },
10692
10744
  requests: [request],
10693
10745
  signer: viem.pad("0x")
10694
10746
  });
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
10747
  return {
10701
10748
  status: RequestStatus.HYPERBRIDGE_FINALIZED,
10702
10749
  metadata: {
10703
- blockHash: hyperbridgeFinalized.blockHash,
10704
- blockNumber: hyperbridgeFinalized.height,
10705
- transactionHash: hyperbridgeFinalized.transactionHash,
10706
- timestamp: hyperbridgeFinalized.timestamp,
10750
+ blockHash: finality.blockHash,
10751
+ blockNumber: finality.height,
10752
+ transactionHash: finality.transactionHash,
10753
+ timestamp: finality.timestamp,
10707
10754
  calldata
10708
10755
  }
10709
10756
  };
@@ -14626,6 +14673,7 @@ function orderCommitment(order) {
14626
14673
  return viem.keccak256(encoded);
14627
14674
  }
14628
14675
  async function convertGasToFeeToken(ctx, gasEstimate, gasEstimateIn, evmChainID, gasPriceOverride) {
14676
+ if (TESTNET_CHAINS.has(evmChainID)) return 1n;
14629
14677
  const chain = ctx[gasEstimateIn];
14630
14678
  const client = chain.client;
14631
14679
  const gasPrice = gasPriceOverride ?? await retryPromise(() => client.getGasPrice(), {
@@ -14660,6 +14708,7 @@ async function convertGasToFeeToken(ctx, gasEstimate, gasEstimateIn, evmChainID,
14660
14708
  }
14661
14709
  }
14662
14710
  async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID) {
14711
+ if (TESTNET_CHAINS.has(evmChainID)) return 1n;
14663
14712
  const chain = ctx[feeTokenIn];
14664
14713
  const client = chain.client;
14665
14714
  const wethAddr = chain.configService.getWrappedNativeAssetWithDecimals(evmChainID).asset;
@@ -14775,16 +14824,16 @@ var OrderPlacer = class {
14775
14824
  return { order, receipt };
14776
14825
  }
14777
14826
  };
14827
+
14828
+ // src/protocols/intents/OrderExecutor.ts
14778
14829
  var USED_USEROPS_STORAGE_KEY = (commitment) => `used-userops:${commitment.toLowerCase()}`;
14779
14830
  var OrderExecutor = class {
14780
- constructor(ctx, bidManager, crypto) {
14831
+ constructor(ctx, bidManager) {
14781
14832
  this.ctx = ctx;
14782
14833
  this.bidManager = bidManager;
14783
- this.crypto = crypto;
14784
14834
  }
14785
14835
  ctx;
14786
14836
  bidManager;
14787
- crypto;
14788
14837
  /**
14789
14838
  * Sleeps until the order's block deadline is reached, then yields EXPIRED.
14790
14839
  * Uses the chain's block time to calculate the sleep duration.
@@ -14855,17 +14904,6 @@ var OrderExecutor = class {
14855
14904
  }
14856
14905
  return fetchedBids;
14857
14906
  }
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
14907
  /**
14870
14908
  * Processes a fill result and returns updated fill accumulators,
14871
14909
  * the status update to yield (if any), and whether the order is
@@ -14933,7 +14971,15 @@ var OrderExecutor = class {
14933
14971
  }
14934
14972
  /**
14935
14973
  * Executes an intent order by racing bid polling against the order's
14936
- * block deadline. Yields status updates at each lifecycle stage.
14974
+ * block deadline. Yields status updates at each lifecycle stage and hands
14975
+ * bid selection to the consumer.
14976
+ *
14977
+ * This is a **bidirectional** generator: when it yields `BIDS_RECEIVED`, the
14978
+ * consumer picks a bid, calls `bid.execute()`, and feeds the resulting
14979
+ * {@link SelectBidResult} back via `gen.next(result)`. The generator then
14980
+ * records the dedup entry, emits `BID_SELECTED`, tracks the fill, and either
14981
+ * terminates or continues polling for the remaining amount. Feeding back
14982
+ * `undefined` (no bid executed this round) causes it to keep polling.
14937
14983
  *
14938
14984
  * **Same-chain:** `AWAITING_BIDS` → `BIDS_RECEIVED` → `BID_SELECTED`
14939
14985
  * → (`FILLED` | `PARTIAL_FILL`)* → (`FILLED` | `EXPIRED`)
@@ -14944,7 +14990,6 @@ var OrderExecutor = class {
14944
14990
  async *executeOrder(options) {
14945
14991
  const { order, sessionPrivateKey, auctionTimeMs, pollIntervalMs = DEFAULT_POLL_INTERVAL, solver } = options;
14946
14992
  const commitment = order.id;
14947
- order.source === order.destination;
14948
14993
  if (!this.ctx.intentsCoprocessor) {
14949
14994
  yield { status: "FAILED", error: "IntentsCoprocessor required for order execution" };
14950
14995
  return;
@@ -14972,11 +15017,24 @@ var OrderExecutor = class {
14972
15017
  remainingAssets
14973
15018
  });
14974
15019
  const deadlineTimeout = this.deadlineStream(order.deadline, commitment);
14975
- const combined = mergeRace__default.default(deadlineTimeout, executionStream);
15020
+ const deadlinePromise = deadlineTimeout.next();
14976
15021
  try {
14977
- for await (const update of combined) {
14978
- yield update;
14979
- if (update.status === "EXPIRED" || update.status === "FILLED") return;
15022
+ let input;
15023
+ while (true) {
15024
+ const winner = input !== void 0 ? { from: "exec", r: await executionStream.next(input) } : await Promise.race([
15025
+ executionStream.next(void 0).then((r) => ({ from: "exec", r })),
15026
+ deadlinePromise.then((r) => ({ from: "deadline", r }))
15027
+ ]);
15028
+ input = void 0;
15029
+ if (winner.from === "deadline") {
15030
+ if (!winner.r.done && winner.r.value) yield winner.r.value;
15031
+ return;
15032
+ }
15033
+ const { value, done } = winner.r;
15034
+ if (done) return;
15035
+ const fed = yield value;
15036
+ if (value.status === "BIDS_RECEIVED") input = fed;
15037
+ if (value.status === "EXPIRED" || value.status === "FILLED") return;
14980
15038
  }
14981
15039
  } finally {
14982
15040
  console.log(`[OrderExecutor] Tearing down streams for commitment=${commitment}`);
@@ -14985,9 +15043,16 @@ var OrderExecutor = class {
14985
15043
  }
14986
15044
  }
14987
15045
  /**
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.
15046
+ * Core execution loop that polls for bids and tracks fill progress. Builds
15047
+ * first-class {@link Bid} objects from the raw filler bids and yields them to
15048
+ * the consumer, which picks one, calls `bid.execute()`, and feeds the result
15049
+ * back via `gen.next(result)`. The loop then records the dedup entry, emits
15050
+ * `BID_SELECTED`, processes the fill, and continues polling for the remaining
15051
+ * amount on partial fills.
15052
+ *
15053
+ * Bidirectional: the value passed to `.next()` after a `BIDS_RECEIVED` yield is
15054
+ * the {@link SelectBidResult} from the executed bid (or `undefined` to skip the
15055
+ * round and keep polling).
14991
15056
  */
14992
15057
  async *executionStream(params) {
14993
15058
  const {
@@ -15002,12 +15067,7 @@ var OrderExecutor = class {
15002
15067
  targetAssets
15003
15068
  } = params;
15004
15069
  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
- };
15070
+ const isFreshBid = (bid) => !usedUserOps.has(userOpHashKey(bid.userOp));
15011
15071
  const solverLockStartTime = Date.now();
15012
15072
  yield { status: "AWAITING_BIDS", commitment, totalFilledAssets, remainingAssets };
15013
15073
  try {
@@ -15016,13 +15076,13 @@ var OrderExecutor = class {
15016
15076
  while (Date.now() < auctionEnd) {
15017
15077
  try {
15018
15078
  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 };
15079
+ const newBids = bids.filter(
15080
+ (bid) => isFreshBid(bid) && !auctionSeenBids.has(userOpHashKey(bid.userOp))
15081
+ );
15082
+ for (const fillerBid of newBids) {
15083
+ auctionSeenBids.add(userOpHashKey(fillerBid.userOp));
15084
+ const [bid] = this.bidManager.buildBids(order, [fillerBid], sessionPrivateKey);
15085
+ if (bid) yield { status: "NEW_BID", commitment, bid };
15026
15086
  }
15027
15087
  } catch {
15028
15088
  }
@@ -15034,8 +15094,8 @@ var OrderExecutor = class {
15034
15094
  while (true) {
15035
15095
  let freshBids;
15036
15096
  try {
15037
- const bids = await this.fetchBids({ commitment, solver, solverLockStartTime });
15038
- freshBids = bids.filter(isFreshBid);
15097
+ const bids2 = await this.fetchBids({ commitment, solver, solverLockStartTime });
15098
+ freshBids = bids2.filter(isFreshBid);
15039
15099
  } catch {
15040
15100
  await sleep(pollIntervalMs);
15041
15101
  continue;
@@ -15044,56 +15104,33 @@ var OrderExecutor = class {
15044
15104
  await sleep(pollIntervalMs);
15045
15105
  continue;
15046
15106
  }
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
- }
15107
+ const bids = this.bidManager.buildBids(order, freshBids, sessionPrivateKey);
15108
+ if (bids.length === 0) {
15109
+ await sleep(pollIntervalMs);
15110
+ continue;
15111
+ }
15112
+ const result = yield { status: "BIDS_RECEIVED", commitment, bidCount: bids.length, bids };
15113
+ if (!result) {
15074
15114
  await sleep(pollIntervalMs);
15075
15115
  continue;
15076
15116
  }
15117
+ usedUserOps.add(userOpHashKey(result.userOp));
15118
+ await this.persistUsedUserOps(commitment, usedUserOps);
15077
15119
  yield {
15078
15120
  status: "BID_SELECTED",
15079
15121
  commitment,
15080
- selectedSolver: submitResult.solverAddress,
15081
- userOpHash: submitResult.userOpHash,
15082
- userOp: submitResult.userOp,
15083
- transactionHash: submitResult.txnHash
15122
+ selectedSolver: result.solverAddress,
15123
+ userOpHash: result.userOpHash,
15124
+ userOp: result.userOp,
15125
+ transactionHash: result.txnHash
15084
15126
  };
15085
- const fill = this.processFillResult(
15086
- submitResult,
15087
- commitment,
15088
- targetAssets,
15089
- totalFilledAssets,
15090
- remainingAssets
15091
- );
15127
+ const fill = this.processFillResult(result, commitment, targetAssets, totalFilledAssets, remainingAssets);
15092
15128
  totalFilledAssets = fill.totalFilledAssets;
15093
15129
  remainingAssets = fill.remainingAssets;
15094
15130
  if (fill.update) {
15095
15131
  yield fill.update;
15096
15132
  }
15133
+ if (fill.done) return;
15097
15134
  }
15098
15135
  } catch (err) {
15099
15136
  yield {
@@ -15568,183 +15605,148 @@ var OrderCanceller = class {
15568
15605
  return feeInDestFeeToken * 1005n / 1000n;
15569
15606
  }
15570
15607
  };
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
- }
15608
+ var BidImpl = class {
15609
+ solverAddress;
15610
+ outputs;
15611
+ relayerFee;
15612
+ nativeDispatchFee;
15613
+ userOp;
15582
15614
  ctx;
15583
15615
  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(
15616
+ order;
15617
+ fillOptions;
15618
+ priceOutputs;
15619
+ sessionPrivateKey;
15620
+ intentGatewayV2Address;
15621
+ domainSeparator;
15622
+ /** Cached session-key signature over the `SelectSolver` message. */
15623
+ cachedSignature;
15624
+ constructor(params) {
15625
+ this.ctx = params.ctx;
15626
+ this.crypto = params.crypto;
15627
+ this.order = params.order;
15628
+ this.fillOptions = params.fillOptions;
15629
+ this.priceOutputs = params.priceOutputs;
15630
+ this.sessionPrivateKey = params.sessionPrivateKey;
15631
+ this.solverAddress = params.fillerBid.userOp.sender;
15632
+ this.outputs = params.fillOptions.outputs;
15633
+ this.relayerFee = params.fillOptions.relayerFee;
15634
+ this.nativeDispatchFee = params.fillOptions.nativeDispatchFee;
15635
+ this.userOp = params.fillerBid.userOp;
15636
+ this.intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
15637
+ normalizeStateMachineId(this.order.destination)
15638
+ );
15639
+ this.domainSeparator = CryptoUtils.getDomainSeparator(
15640
+ "IntentGateway",
15641
+ "2",
15642
+ this.chainId(),
15643
+ this.intentGatewayV2Address
15644
+ );
15645
+ }
15646
+ /** Resolves the destination chain id from the client or the state-machine id. */
15647
+ chainId() {
15648
+ return BigInt(
15613
15649
  this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15614
15650
  );
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
15651
  }
15635
15652
  /**
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.
15639
- *
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.
15653
+ * Resolves the session key, signs the `SelectSolver` message for this bid's
15654
+ * solver, and caches the signature. Signs at most once per bid.
15649
15655
  *
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.
15656
+ * @throws If the session key is missing or signing fails.
15658
15657
  */
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);
15658
+ async signSelection() {
15659
+ if (this.cachedSignature) return this.cachedSignature;
15660
+ const commitment = this.order.id;
15661
+ const sessionKeyAddress = this.order.session;
15662
+ const sessionKeyData = this.sessionPrivateKey ? { privateKey: this.sessionPrivateKey } : await this.ctx.sessionKeyStorage.getSessionKeyByAddress(sessionKeyAddress);
15664
15663
  if (!sessionKeyData) {
15665
15664
  throw new Error("SessionKey not found for commitment: " + commitment);
15666
15665
  }
15667
- if (!this.ctx.bundlerUrl) {
15668
- throw new Error("Bundler URL not configured");
15666
+ const signature = await CryptoUtils.signSolverSelection(
15667
+ commitment,
15668
+ this.solverAddress,
15669
+ this.domainSeparator,
15670
+ sessionKeyData.privateKey
15671
+ );
15672
+ if (!signature) {
15673
+ throw new Error("Failed to sign solver selection");
15669
15674
  }
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
15675
+ this.cachedSignature = signature;
15676
+ return signature;
15677
+ }
15678
+ /**
15679
+ * Simulates this bid on-chain by batching the `select` and `fillOrder` calls
15680
+ * via `eth_call` from the solver's account, using the IntentGatewayV2 ERC-7821
15681
+ * batch-execute pattern.
15682
+ *
15683
+ * The native value forwarded to the simulation is the sum of any native-token
15684
+ * (`address(0)`) output amounts plus the Hyperbridge dispatch fee.
15685
+ *
15686
+ * @throws If the `eth_call` simulation reverts or errors.
15687
+ */
15688
+ async simulate() {
15689
+ const signature = await this.signSelection();
15690
+ const selectOptions = {
15691
+ commitment: this.order.id,
15692
+ solver: this.solverAddress,
15693
+ signature
15694
+ };
15695
+ const nativeOutputs = this.fillOptions.outputs.reduce(
15696
+ (acc, o) => bytes32ToBytes20(o.token) === ADDRESS_ZERO2 ? acc + o.amount : acc,
15697
+ 0n
15688
15698
  );
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
- }
15699
+ const simulationValue = nativeOutputs + this.fillOptions.nativeDispatchFee;
15700
+ const selectCalldata = viem.encodeFunctionData({
15701
+ abi: ABI3,
15702
+ functionName: "select",
15703
+ args: [selectOptions]
15704
+ });
15705
+ const calls = [
15706
+ { target: this.intentGatewayV2Address, value: 0n, data: selectCalldata },
15707
+ { target: this.solverAddress, value: simulationValue, data: this.userOp.callData }
15708
+ ];
15709
+ const batchedCalldata = this.crypto.encodeERC7821Execute(calls);
15710
+ try {
15711
+ await this.ctx.dest.client.call({
15712
+ account: this.solverAddress,
15713
+ to: this.solverAddress,
15714
+ data: batchedCalldata,
15715
+ value: simulationValue
15716
+ });
15717
+ } catch (e) {
15718
+ throw new Error(`Simulation failed: ${e instanceof Error ? e.message : String(e)}`);
15723
15719
  }
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");
15720
+ }
15721
+ /**
15722
+ * Signs the `SelectSolver` message with the session key, appends it to the
15723
+ * solver's existing UserOp signature, and submits the UserOperation to the
15724
+ * bundler. For same-chain orders, waits for the receipt and reads
15725
+ * `OrderFilled` / `PartialFill` logs to determine fill status.
15726
+ *
15727
+ * @returns A {@link SelectBidResult} with the submitted UserOperation, its hash,
15728
+ * the solver address, transaction hash, and fill status.
15729
+ * @throws If the bundler is not configured, the session key is missing, or the
15730
+ * bundler rejects the UserOperation.
15731
+ */
15732
+ async execute() {
15733
+ const commitment = this.order.id;
15734
+ if (!this.ctx.bundlerUrl) {
15735
+ throw new Error("Bundler URL not configured");
15727
15736
  }
15728
- const solverAddress = selectedBid.bid.userOp.sender;
15729
- const finalSignature = viem.concat([
15730
- selectedBid.bid.userOp.signature,
15731
- sessionSignature
15732
- ]);
15737
+ const sessionSignature = await this.signSelection();
15738
+ const finalSignature = viem.concat([this.userOp.signature, sessionSignature]);
15733
15739
  const signedUserOp = {
15734
- ...selectedBid.bid.userOp,
15740
+ ...this.userOp,
15735
15741
  signature: finalSignature
15736
15742
  };
15737
15743
  const entryPointAddress = this.ctx.dest.configService.getEntryPointV08Address(
15738
- normalizeStateMachineId(order.destination)
15744
+ normalizeStateMachineId(this.order.destination)
15739
15745
  );
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, [
15746
+ const userOpHash = await this.crypto.sendBundler(BundlerMethod.ETH_SEND_USER_OPERATION, [
15744
15747
  CryptoUtils.prepareBundlerCall(signedUserOp),
15745
15748
  entryPointAddress
15746
15749
  ]);
15747
- const userOpHash = bundlerResult;
15748
15750
  let txnHash;
15749
15751
  let fillStatus;
15750
15752
  let filledAssets;
@@ -15760,7 +15762,7 @@ var BidManager = class {
15760
15762
  { maxRetries: 5, backoffMs: 2e3, logMessage: "Fetching user operation receipt" }
15761
15763
  );
15762
15764
  txnHash = receipt.receipt.transactionHash;
15763
- if (order.source === order.destination) {
15765
+ if (this.order.source === this.order.destination) {
15764
15766
  try {
15765
15767
  const chainReceipt = await this.ctx.dest.client.waitForTransactionReceipt({
15766
15768
  hash: txnHash,
@@ -15789,12 +15791,12 @@ var BidManager = class {
15789
15791
  }
15790
15792
  }
15791
15793
  } catch (err) {
15792
- throw new Error(`Failed to select bid: ${err instanceof Error ? err.message : String(err)}`);
15794
+ throw new Error(`Failed to execute bid: ${err instanceof Error ? err.message : String(err)}`);
15793
15795
  }
15794
15796
  return {
15795
15797
  userOp: signedUserOp,
15796
15798
  userOpHash,
15797
- solverAddress,
15799
+ solverAddress: this.solverAddress,
15798
15800
  commitment,
15799
15801
  txnHash,
15800
15802
  fillStatus,
@@ -15802,7 +15804,177 @@ var BidManager = class {
15802
15804
  };
15803
15805
  }
15804
15806
  /**
15805
- * Validates and sorts a list of raw bids for the given order.
15807
+ * Prices this bid's outputs in USD using the destination chain's DEX-quote
15808
+ * helpers. Returns `null` when any output token cannot be priced.
15809
+ */
15810
+ async outputUsdValue() {
15811
+ return this.priceOutputs(this.outputs);
15812
+ }
15813
+ };
15814
+ var BidManager = class {
15815
+ /**
15816
+ * @param ctx - Shared IntentsV2 context providing the destination chain
15817
+ * client, coprocessor, bundler URL, and session-key storage.
15818
+ * @param crypto - Crypto utilities used for gas packing, UserOp hashing,
15819
+ * EIP-712 signing, and bundler calls.
15820
+ */
15821
+ constructor(ctx, crypto) {
15822
+ this.ctx = ctx;
15823
+ this.crypto = crypto;
15824
+ }
15825
+ ctx;
15826
+ crypto;
15827
+ /**
15828
+ * Constructs a signed `PackedUserOperation` that a solver can submit to the
15829
+ * Hyperbridge coprocessor as a bid to fill an order.
15830
+ *
15831
+ * The solver's signature covers a hash that binds the UserOperation to the
15832
+ * order commitment and the session key address, so the IntentGatewayV2
15833
+ * contract can verify the solver's intent on-chain.
15834
+ *
15835
+ * @param options - Parameters describing the solver account, gas limits, fee
15836
+ * market values, and pre-built `callData` for the fill operation.
15837
+ * @returns A `PackedUserOperation` with the solver's signature prepended
15838
+ * with the order commitment.
15839
+ */
15840
+ async prepareSubmitBid(options) {
15841
+ const {
15842
+ order,
15843
+ solverAccount,
15844
+ solverSigner,
15845
+ nonce,
15846
+ entryPointAddress,
15847
+ callGasLimit,
15848
+ verificationGasLimit,
15849
+ preVerificationGas,
15850
+ maxFeePerGas,
15851
+ maxPriorityFeePerGas,
15852
+ callData,
15853
+ paymasterAndData = "0x"
15854
+ } = options;
15855
+ const chainId = BigInt(
15856
+ this.ctx.dest.client.chain?.id ?? Number.parseInt(this.ctx.dest.config.stateMachineId.split("-")[1])
15857
+ );
15858
+ const accountGasLimits = CryptoUtils.packGasLimits(verificationGasLimit, callGasLimit);
15859
+ const gasFees = CryptoUtils.packGasFees(maxPriorityFeePerGas, maxFeePerGas);
15860
+ const userOp = {
15861
+ sender: solverAccount,
15862
+ nonce,
15863
+ initCode: "0x",
15864
+ callData,
15865
+ accountGasLimits,
15866
+ preVerificationGas,
15867
+ gasFees,
15868
+ paymasterAndData,
15869
+ signature: "0x"
15870
+ };
15871
+ const userOpHash = CryptoUtils.computeUserOpHash(userOp, entryPointAddress, chainId);
15872
+ const sessionKey = order.session;
15873
+ const messageHash = viem.keccak256(viem.concat([userOpHash, order.id, sessionKey]));
15874
+ const solverSignature = await solverSigner.signMessage(messageHash, Number(chainId));
15875
+ const signature = viem.concat([order.id, solverSignature]);
15876
+ return { ...userOp, signature };
15877
+ }
15878
+ /**
15879
+ * Decodes raw filler bids into first-class {@link Bid} objects.
15880
+ *
15881
+ * Each bid's `fillOrder` fill-options are decoded from its ERC-7821 calldata;
15882
+ * bids whose calldata cannot be decoded into a valid `fillOrder` call are
15883
+ * silently dropped with a warning. The returned `Bid` instances are ready to
15884
+ * be ranked, simulated, and executed by the consumer.
15885
+ *
15886
+ * @param order - The placed order the bids are competing to fill.
15887
+ * @param bids - Raw filler bids fetched from the coprocessor.
15888
+ * @param sessionPrivateKey - Optional session-key override; looked up from
15889
+ * storage by `order.session` if omitted.
15890
+ * @returns Array of executable `Bid` objects (one per successfully decoded bid).
15891
+ */
15892
+ buildBids(order, bids, sessionPrivateKey) {
15893
+ const chainId = this.ctx.dest.config.stateMachineId;
15894
+ const priceOutputs = (outputs) => this.computeOutputsUsdValue(outputs, chainId);
15895
+ const result = [];
15896
+ for (const fillerBid of bids) {
15897
+ const fillOptions = this.decodeBidFillOptions(fillerBid);
15898
+ if (!fillOptions) {
15899
+ console.warn(`[BidManager] Failed to decode fillOptions from bid by solver=${fillerBid.userOp.sender}`);
15900
+ continue;
15901
+ }
15902
+ result.push(
15903
+ new BidImpl({
15904
+ ctx: this.ctx,
15905
+ crypto: this.crypto,
15906
+ order,
15907
+ fillerBid,
15908
+ fillOptions,
15909
+ priceOutputs,
15910
+ sessionPrivateKey
15911
+ })
15912
+ );
15913
+ }
15914
+ console.log(`[BidManager] Built ${result.length}/${bids.length} bid(s) successfully`);
15915
+ return result;
15916
+ }
15917
+ /**
15918
+ * Decodes raw filler bids, sorts them, simulates each until one passes, signs
15919
+ * the `SelectSolver` message, and submits — all with no per-bid input from the
15920
+ * caller.
15921
+ *
15922
+ * Equivalent to `selectAndExecuteBest(order, buildBids(order, bids, key))`.
15923
+ *
15924
+ * @param order - The placed order to fill.
15925
+ * @param bids - Raw filler bids fetched from the coprocessor.
15926
+ * @param sessionPrivateKey - Optional session-key override; looked up from
15927
+ * storage by `order.session` if omitted.
15928
+ * @returns A {@link SelectBidResult} for the executed bid.
15929
+ */
15930
+ async selectBid(order, bids, sessionPrivateKey) {
15931
+ return this.selectAndExecuteBest(order, this.buildBids(order, bids, sessionPrivateKey));
15932
+ }
15933
+ /**
15934
+ * Autopilot bid selection: sorts the given bids by output value, simulates
15935
+ * each in order until one passes, then executes that bid. For consumers that
15936
+ * do not need custom selection logic.
15937
+ *
15938
+ * @param order - The placed order to fill.
15939
+ * @param bids - Candidate bids (from {@link buildBids}).
15940
+ * @returns A {@link SelectBidResult} for the executed bid.
15941
+ * @throws If no valid bids exist, all simulations fail, or the bundler rejects
15942
+ * the UserOperation.
15943
+ */
15944
+ async selectAndExecuteBest(order, bids) {
15945
+ const commitment = order.id;
15946
+ console.log(`[BidManager] selectAndExecuteBest called for commitment=${commitment}, ${bids.length} bid(s)`);
15947
+ if (!this.ctx.bundlerUrl) {
15948
+ throw new Error("Bundler URL not configured");
15949
+ }
15950
+ if (!this.ctx.intentsCoprocessor) {
15951
+ throw new Error("IntentsCoprocessor required");
15952
+ }
15953
+ const sortedBids = await this.sortBids(order, bids);
15954
+ console.log(`[BidManager] ${sortedBids.length}/${bids.length} bid(s) passed validation and sorting`);
15955
+ if (sortedBids.length === 0) {
15956
+ throw new Error("No valid bids found");
15957
+ }
15958
+ console.log(`[BidManager] Simulating ${sortedBids.length} sorted bid(s) to find a valid one`);
15959
+ for (let idx = 0; idx < sortedBids.length; idx++) {
15960
+ const bid = sortedBids[idx];
15961
+ console.log(`[BidManager] Simulating bid ${idx + 1}/${sortedBids.length} from solver=${bid.solverAddress}`);
15962
+ try {
15963
+ await bid.simulate();
15964
+ } catch (err) {
15965
+ console.warn(
15966
+ `[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation FAILED: ${err instanceof Error ? err.message : String(err)}`
15967
+ );
15968
+ continue;
15969
+ }
15970
+ console.log(`[BidManager] Bid ${idx + 1} from solver=${bid.solverAddress}: simulation PASSED`);
15971
+ return bid.execute();
15972
+ }
15973
+ console.error(`[BidManager] All ${sortedBids.length} bid(s) failed simulation for commitment=${commitment}`);
15974
+ throw new Error("No bids passed simulation");
15975
+ }
15976
+ /**
15977
+ * Sorts a list of bids for the given order by output value.
15806
15978
  *
15807
15979
  * Delegates to one of three strategies based on the order's output token
15808
15980
  * composition:
@@ -15811,47 +15983,26 @@ var BidManager = class {
15811
15983
  * - Mixed outputs: sort by DEX-quoted USD value descending, with a raw-amount
15812
15984
  * fallback if pricing fails.
15813
15985
  *
15814
- * @param bids - Raw filler bids from the coprocessor.
15986
+ * Bids that cannot satisfy the order's token set are dropped.
15987
+ *
15815
15988
  * @param order - The placed order whose output spec drives sorting logic.
15816
- * @returns Sorted array of `{ bid, options }` pairs ready for simulation.
15989
+ * @param bids - Executable bids to sort (from {@link buildBids}).
15990
+ * @returns Sorted array of `Bid` objects ready for simulation.
15817
15991
  */
15818
- async validateAndSortBids(bids, order) {
15992
+ async sortBids(order, bids) {
15819
15993
  const outputs = order.output.assets;
15820
- const decodedBids = this.decodeBids(bids);
15821
15994
  if (outputs.length <= 1) {
15822
15995
  console.log(`[BidManager] Using single-output sorting (1 output asset)`);
15823
- return this.sortSingleOutput(decodedBids, outputs[0]);
15996
+ return this.sortSingleOutput(bids, outputs[0]);
15824
15997
  }
15825
15998
  const chainId = this.ctx.dest.config.stateMachineId;
15826
15999
  const allStables = outputs.every((o) => this.isStableToken(bytes32ToBytes20(o.token), chainId));
15827
16000
  if (allStables) {
15828
16001
  console.log(`[BidManager] Using all-stables sorting (${outputs.length} stable output assets)`);
15829
- return this.sortAllStables(decodedBids, outputs, chainId);
16002
+ return this.sortAllStables(bids, outputs, chainId);
15830
16003
  }
15831
16004
  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;
16005
+ return this.sortMixedOutputs(bids, outputs, chainId);
15855
16006
  }
15856
16007
  /**
15857
16008
  * Extracts the `FillOptions` struct from a single bid's ERC-7821
@@ -15884,149 +16035,103 @@ var BidManager = class {
15884
16035
  }
15885
16036
  return null;
15886
16037
  }
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
16038
  /**
15930
16039
  * Case A: single output token.
15931
16040
  * Filter bids by token match only, sort descending by amount.
15932
16041
  * Partial fill bids are allowed — the contract determines fill status.
15933
16042
  */
15934
- sortSingleOutput(decodedBids, requiredAsset) {
16043
+ sortSingleOutput(bids, requiredAsset) {
15935
16044
  const requiredAmount = new Decimal2__default.default(requiredAsset.amount.toString());
15936
16045
  console.log(
15937
16046
  `[BidManager] sortSingleOutput: required token=${requiredAsset.token}, amount=${requiredAmount.toString()}`
15938
16047
  );
15939
16048
  const validBids = [];
15940
- for (const { bid, options } of decodedBids) {
15941
- const bidOutput = options.outputs[0];
16049
+ for (const bid of bids) {
16050
+ const bidOutput = bid.outputs[0];
15942
16051
  const bidAmount = new Decimal2__default.default(bidOutput.amount.toString());
15943
16052
  if (bidOutput.token.toLowerCase() !== requiredAsset.token.toLowerCase()) {
15944
16053
  console.warn(
15945
- `[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: token mismatch (bid=${bidOutput.token}, required=${requiredAsset.token})`
16054
+ `[BidManager] Bid from solver=${bid.solverAddress} REJECTED: token mismatch (bid=${bidOutput.token}, required=${requiredAsset.token})`
15946
16055
  );
15947
16056
  continue;
15948
16057
  }
15949
16058
  if (bidAmount.lt(requiredAmount)) {
15950
16059
  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)}%)`
16060
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidAmount.toString()}, required=${requiredAmount.toString()}, covers=${bidAmount.div(requiredAmount).mul(100).toFixed(2)}%)`
15952
16061
  );
15953
16062
  } else {
15954
16063
  console.log(
15955
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: amount=${bidAmount.toString()} (surplus=${bidAmount.minus(requiredAmount).toString()})`
16064
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: amount=${bidAmount.toString()} (surplus=${bidAmount.minus(requiredAmount).toString()})`
15956
16065
  );
15957
16066
  }
15958
- validBids.push({ bid, options, amount: bidOutput.amount });
16067
+ validBids.push({ bid, amount: bidOutput.amount });
15959
16068
  }
15960
16069
  validBids.sort((a, b) => {
15961
16070
  const aAmt = new Decimal2__default.default(a.amount.toString());
15962
16071
  const bAmt = new Decimal2__default.default(b.amount.toString());
15963
16072
  return bAmt.comparedTo(aAmt);
15964
16073
  });
15965
- return validBids.map(({ amount: _, ...rest }) => rest);
16074
+ return validBids.map(({ bid }) => bid);
15966
16075
  }
15967
16076
  /**
15968
16077
  * Case B: all outputs are USDC/USDT.
15969
16078
  * Sum normalised USD values (treating each stable as $1) and sort descending.
15970
16079
  * Partial fill bids are allowed.
15971
16080
  */
15972
- sortAllStables(decodedBids, orderOutputs, chainId) {
16081
+ sortAllStables(bids, orderOutputs, chainId) {
15973
16082
  const requiredUsd = this.computeStablesUsdValue(orderOutputs, chainId);
15974
16083
  console.log(`[BidManager] sortAllStables: required USD value=${requiredUsd.toString()}`);
15975
16084
  const validBids = [];
15976
- for (const { bid, options } of decodedBids) {
15977
- const bidUsd = this.computeStablesUsdValue(options.outputs, chainId);
16085
+ for (const bid of bids) {
16086
+ const bidUsd = this.computeStablesUsdValue(bid.outputs, chainId);
15978
16087
  if (bidUsd === null) {
15979
- console.warn(`[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: unable to compute USD value`);
16088
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to compute USD value`);
15980
16089
  continue;
15981
16090
  }
15982
16091
  if (bidUsd.lt(requiredUsd)) {
15983
16092
  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)}%)`
16093
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
15985
16094
  );
15986
16095
  } else {
15987
- console.log(
15988
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: USD value=${bidUsd.toString()}`
15989
- );
16096
+ console.log(`[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: USD value=${bidUsd.toString()}`);
15990
16097
  }
15991
- validBids.push({ bid, options, usdValue: bidUsd });
16098
+ validBids.push({ bid, usdValue: bidUsd });
15992
16099
  }
15993
16100
  validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
15994
- return validBids.map(({ usdValue: _, ...rest }) => rest);
16101
+ return validBids.map(({ bid }) => bid);
15995
16102
  }
15996
16103
  /**
15997
16104
  * Case C: mixed output tokens (at least one non-stable).
15998
16105
  * Price every token via on-chain DEX quotes, fall back to raw amounts
15999
16106
  * if pricing is unavailable. Partial fill bids are allowed.
16000
16107
  */
16001
- async sortMixedOutputs(decodedBids, orderOutputs, chainId) {
16108
+ async sortMixedOutputs(bids, orderOutputs, chainId) {
16002
16109
  const requiredUsd = await this.computeOutputsUsdValue(orderOutputs, chainId);
16003
16110
  if (requiredUsd === null) {
16004
16111
  console.warn("[BidManager] sortMixedOutputs: output tokens unpriceable, falling back to raw-amount sort");
16005
- return this.sortByRawAmountFallback(decodedBids, orderOutputs);
16112
+ return this.sortByRawAmountFallback(bids, orderOutputs);
16006
16113
  }
16007
16114
  console.log(`[BidManager] sortMixedOutputs: required USD value=${requiredUsd.toString()}`);
16008
16115
  const validBids = [];
16009
- for (const { bid, options } of decodedBids) {
16010
- const bidUsd = await this.computeOutputsUsdValue(options.outputs, chainId);
16116
+ for (const bid of bids) {
16117
+ const bidUsd = await this.computeOutputsUsdValue(bid.outputs, chainId);
16011
16118
  if (bidUsd === null) {
16012
- console.warn(
16013
- `[BidManager] Bid from solver=${bid.userOp.sender} REJECTED: unable to price mixed outputs`
16014
- );
16119
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED: unable to price mixed outputs`);
16015
16120
  continue;
16016
16121
  }
16017
16122
  if (bidUsd.lt(requiredUsd)) {
16018
16123
  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)}%)`
16124
+ `[BidManager] Bid from solver=${bid.solverAddress}: partial fill candidate (bid=${bidUsd.toString()}, required=${requiredUsd.toString()}, covers=${bidUsd.div(requiredUsd).mul(100).toFixed(2)}%)`
16020
16125
  );
16021
16126
  } else {
16022
16127
  console.log(
16023
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED: mixed USD value=${bidUsd.toString()}`
16128
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED: mixed USD value=${bidUsd.toString()}`
16024
16129
  );
16025
16130
  }
16026
- validBids.push({ bid, options, usdValue: bidUsd });
16131
+ validBids.push({ bid, usdValue: bidUsd });
16027
16132
  }
16028
16133
  validBids.sort((a, b) => b.usdValue.comparedTo(a.usdValue));
16029
- return validBids.map(({ usdValue: _, ...rest }) => rest);
16134
+ return validBids.map(({ bid }) => bid);
16030
16135
  }
16031
16136
  /**
16032
16137
  * Fallback when DEX pricing is unavailable.
@@ -16034,17 +16139,17 @@ var BidManager = class {
16034
16139
  * Bids offering less than required for a token are allowed (partial fill).
16035
16140
  * Sorted by total offered amount descending.
16036
16141
  */
16037
- sortByRawAmountFallback(decodedBids, orderOutputs) {
16142
+ sortByRawAmountFallback(bids, orderOutputs) {
16038
16143
  console.log(
16039
- `[BidManager] sortByRawAmountFallback: checking ${decodedBids.length} bid(s) against ${orderOutputs.length} required output(s)`
16144
+ `[BidManager] sortByRawAmountFallback: checking ${bids.length} bid(s) against ${orderOutputs.length} required output(s)`
16040
16145
  );
16041
16146
  const validBids = [];
16042
- for (const { bid, options } of decodedBids) {
16147
+ for (const bid of bids) {
16043
16148
  let valid = true;
16044
16149
  let totalOffered = new Decimal2__default.default(0);
16045
16150
  let rejectReason = "";
16046
16151
  for (const required of orderOutputs) {
16047
- const matching = options.outputs.find((o) => o.token.toLowerCase() === required.token.toLowerCase());
16152
+ const matching = bid.outputs.find((o) => o.token.toLowerCase() === required.token.toLowerCase());
16048
16153
  if (!matching) {
16049
16154
  valid = false;
16050
16155
  rejectReason = `missing output token=${required.token}`;
@@ -16053,7 +16158,7 @@ var BidManager = class {
16053
16158
  totalOffered = totalOffered.plus(new Decimal2__default.default(matching.amount.toString()));
16054
16159
  }
16055
16160
  if (!valid) {
16056
- console.warn(`[BidManager] Bid from solver=${bid.userOp.sender} REJECTED (fallback): ${rejectReason}`);
16161
+ console.warn(`[BidManager] Bid from solver=${bid.solverAddress} REJECTED (fallback): ${rejectReason}`);
16057
16162
  continue;
16058
16163
  }
16059
16164
  const totalRequired = orderOutputs.reduce(
@@ -16062,17 +16167,17 @@ var BidManager = class {
16062
16167
  );
16063
16168
  if (totalOffered.lt(totalRequired)) {
16064
16169
  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)}%)`
16170
+ `[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
16171
  );
16067
16172
  } else {
16068
16173
  console.log(
16069
- `[BidManager] Bid from solver=${bid.userOp.sender} ACCEPTED (fallback): totalOffered=${totalOffered.toString()}`
16174
+ `[BidManager] Bid from solver=${bid.solverAddress} ACCEPTED (fallback): totalOffered=${totalOffered.toString()}`
16070
16175
  );
16071
16176
  }
16072
- validBids.push({ bid, options, totalOffered });
16177
+ validBids.push({ bid, totalOffered });
16073
16178
  }
16074
16179
  validBids.sort((a, b) => b.totalOffered.comparedTo(a.totalOffered));
16075
- return validBids.map(({ totalOffered: _, ...rest }) => rest);
16180
+ return validBids.map(({ bid }) => bid);
16076
16181
  }
16077
16182
  // ── Token classification helpers ──────────────────────────────────
16078
16183
  /**
@@ -16681,6 +16786,262 @@ var OrderStatusChecker = class {
16681
16786
  }
16682
16787
  };
16683
16788
 
16789
+ // src/protocols/intents/quote/types.ts
16790
+ var UnsupportedIntentQuoteStrategyError = class extends Error {
16791
+ constructor(strategy) {
16792
+ super(`Unsupported intent quote strategy: ${strategy}`);
16793
+ this.name = "UnsupportedIntentQuoteStrategyError";
16794
+ }
16795
+ };
16796
+ var UnsupportedIntentQuotePairError = class extends Error {
16797
+ constructor(params) {
16798
+ super(
16799
+ `No Uniswap v4 pool config found for ${params.tokenIn.symbol ?? params.tokenIn.address} -> ${params.tokenOut.symbol ?? params.tokenOut.address} on ${params.source} -> ${params.destination}`
16800
+ );
16801
+ this.name = "UnsupportedIntentQuotePairError";
16802
+ }
16803
+ };
16804
+
16805
+ // src/protocols/intents/quote/uniswapV4.ts
16806
+ var UNISWAP_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
16807
+ var BPS_DENOMINATOR = 10000n;
16808
+ var UniswapV4IntentQuoteStrategy = class {
16809
+ constructor(chainConfigService) {
16810
+ this.chainConfigService = chainConfigService;
16811
+ }
16812
+ chainConfigService;
16813
+ baseQuoteClient;
16814
+ async quote(params, source, destination) {
16815
+ this.validateQuoteParams(params);
16816
+ const protocolFeeBps = await this.readProtocolFeeBps(source.client, source.stateMachineId);
16817
+ const quoteClient = this.resolveQuoteClient(source, destination);
16818
+ const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, UNISWAP_INTENT_QUOTE_CHAIN);
16819
+ return params.amountIn !== void 0 ? this.quoteExactInput({ params, client: quoteClient, protocolFeeBps, poolConfig }) : this.quoteExactOutput({ params, client: quoteClient, protocolFeeBps, poolConfig });
16820
+ }
16821
+ resolveQuoteClient(source, destination) {
16822
+ if (source.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return source.client;
16823
+ if (destination.stateMachineId === UNISWAP_INTENT_QUOTE_CHAIN) return destination.client;
16824
+ if (this.baseQuoteClient) return this.baseQuoteClient;
16825
+ const rpcUrl = this.chainConfigService.getRpcUrl(UNISWAP_INTENT_QUOTE_CHAIN);
16826
+ if (!rpcUrl) throw new Error(`RPC URL is not configured for ${UNISWAP_INTENT_QUOTE_CHAIN}`);
16827
+ const baseQuoteClient = viem.createPublicClient({
16828
+ chain: chains$1.base,
16829
+ transport: viem.http(rpcUrl)
16830
+ });
16831
+ this.baseQuoteClient = baseQuoteClient;
16832
+ return baseQuoteClient;
16833
+ }
16834
+ validateQuoteParams(params) {
16835
+ const hasAmountIn = params.amountIn !== void 0;
16836
+ const hasAmountOut = params.amountOut !== void 0;
16837
+ if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
16838
+ if (hasAmountIn && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
16839
+ if (hasAmountOut && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
16840
+ if (params.tokenIn.address.toLowerCase() === params.tokenOut.address.toLowerCase()) {
16841
+ throw new Error("tokenIn and tokenOut cannot be the same");
16842
+ }
16843
+ }
16844
+ async readProtocolFeeBps(client, chain) {
16845
+ const gatewayAddress = this.chainConfigService.getIntentGatewayAddress(chain);
16846
+ if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === viem.zeroAddress) {
16847
+ throw new Error(`IntentGatewayV2 is not configured for chain ${chain}`);
16848
+ }
16849
+ const gatewayParams = await client.readContract({
16850
+ address: gatewayAddress,
16851
+ abi: IntentGatewayV2_default.ABI,
16852
+ functionName: "params"
16853
+ });
16854
+ if (isGatewayParamsTuple(gatewayParams)) return BigInt(gatewayParams[4]);
16855
+ return BigInt(gatewayParams.protocolFeeBps ?? 0);
16856
+ }
16857
+ resolvePoolConfig(params, source, destination) {
16858
+ const override = params.uniswapV4?.poolKey;
16859
+ if (override) {
16860
+ const poolKey = normalizePoolKey(override);
16861
+ const tokenInForQuote = viem.getAddress(override.currencyIn ?? params.tokenIn.address);
16862
+ if (tokenInForQuote !== poolKey.currency0 && tokenInForQuote !== poolKey.currency1) {
16863
+ throw new Error(
16864
+ `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.`
16865
+ );
16866
+ }
16867
+ return {
16868
+ poolKey,
16869
+ quoterAddress: this.resolveQuoterAddress(destination, override.quoterAddress),
16870
+ tokenInForQuote
16871
+ };
16872
+ }
16873
+ const poolConfig = this.resolveConfiguredPool(params, destination);
16874
+ if (poolConfig) return poolConfig;
16875
+ throw new UnsupportedIntentQuotePairError({
16876
+ source,
16877
+ destination,
16878
+ tokenIn: params.tokenIn,
16879
+ tokenOut: params.tokenOut
16880
+ });
16881
+ }
16882
+ resolveConfiguredPool(params, chain) {
16883
+ for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(chain)) {
16884
+ const resolvedPool = this.resolveConfiguredPoolTokens(chain, pool);
16885
+ if (!resolvedPool) continue;
16886
+ const tokenInForQuote = matchPoolToken(params.tokenIn, resolvedPool);
16887
+ const tokenOutForQuote = matchPoolToken(params.tokenOut, resolvedPool);
16888
+ if (!tokenInForQuote || !tokenOutForQuote || tokenInForQuote.symbol === tokenOutForQuote.symbol) continue;
16889
+ const { currency0, currency1 } = sortCurrencies(resolvedPool[0].address, resolvedPool[1].address);
16890
+ return {
16891
+ poolKey: {
16892
+ currency0,
16893
+ currency1,
16894
+ fee: pool.fee,
16895
+ tickSpacing: pool.tickSpacing,
16896
+ hooks: viem.getAddress(pool.hooks ?? viem.zeroAddress)
16897
+ },
16898
+ quoterAddress: this.resolveQuoterAddress(chain),
16899
+ tokenInForQuote: tokenInForQuote.address
16900
+ };
16901
+ }
16902
+ return null;
16903
+ }
16904
+ resolveQuoterAddress(chain, override) {
16905
+ const address = override ?? this.chainConfigService.getUniswapV4QuoterAddress(chain);
16906
+ if (!address || address === "0x" || address === viem.zeroAddress) {
16907
+ throw new Error(`Uniswap V4 quoter is not configured for chain ${chain}`);
16908
+ }
16909
+ return viem.getAddress(address);
16910
+ }
16911
+ resolveConfiguredPoolTokens(chain, pool) {
16912
+ const first = this.resolveConfiguredPoolToken(chain, pool.tokens[0]);
16913
+ const second = this.resolveConfiguredPoolToken(chain, pool.tokens[1]);
16914
+ return first && second ? [first, second] : null;
16915
+ }
16916
+ resolveConfiguredPoolToken(chain, symbol) {
16917
+ const address = this.chainConfigService.getAssetAddress(chain, symbol);
16918
+ if (!address || address === "0x") return null;
16919
+ return { symbol, address: viem.getAddress(address) };
16920
+ }
16921
+ async quoteExactInput(args) {
16922
+ const amountIn = args.params.amountIn;
16923
+ const swapAmountIn = deductProtocolFee(amountIn, args.protocolFeeBps);
16924
+ const amountOut = await this.readV4QuoteExactInput(args.client, args.poolConfig, swapAmountIn);
16925
+ return {
16926
+ strategy: "uniswap_v4",
16927
+ tradeType: "EXACT_INPUT",
16928
+ amountIn,
16929
+ amountOut,
16930
+ quoteMetadata: {
16931
+ quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
16932
+ poolKey: args.poolConfig.poolKey,
16933
+ quoterAddress: args.poolConfig.quoterAddress,
16934
+ protocolFeeBps: args.protocolFeeBps
16935
+ }
16936
+ };
16937
+ }
16938
+ async quoteExactOutput(args) {
16939
+ const amountOut = args.params.amountOut;
16940
+ const swapAmountIn = await this.readV4QuoteExactOutput(args.client, args.poolConfig, amountOut);
16941
+ const amountIn = grossUpForProtocolFee(swapAmountIn, args.protocolFeeBps);
16942
+ return {
16943
+ strategy: "uniswap_v4",
16944
+ tradeType: "EXACT_OUTPUT",
16945
+ amountIn,
16946
+ amountOut,
16947
+ quoteMetadata: {
16948
+ quoteChain: UNISWAP_INTENT_QUOTE_CHAIN,
16949
+ poolKey: args.poolConfig.poolKey,
16950
+ quoterAddress: args.poolConfig.quoterAddress,
16951
+ protocolFeeBps: args.protocolFeeBps
16952
+ }
16953
+ };
16954
+ }
16955
+ async readV4QuoteExactInput(client, poolConfig, amountIn) {
16956
+ const data = viem.encodeFunctionData({
16957
+ abi: UNISWAP_V4_QUOTER_ABI,
16958
+ functionName: "quoteExactInputSingle",
16959
+ args: [
16960
+ {
16961
+ poolKey: poolConfig.poolKey,
16962
+ zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
16963
+ exactAmount: amountIn,
16964
+ hookData: "0x"
16965
+ }
16966
+ ]
16967
+ });
16968
+ const response = await client.call({ to: poolConfig.quoterAddress, data });
16969
+ if (!response.data || response.data === "0x") {
16970
+ throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
16971
+ }
16972
+ const [amountOut] = viem.decodeFunctionResult({
16973
+ abi: UNISWAP_V4_QUOTER_ABI,
16974
+ functionName: "quoteExactInputSingle",
16975
+ data: response.data
16976
+ });
16977
+ return amountOut;
16978
+ }
16979
+ async readV4QuoteExactOutput(client, poolConfig, amountOut) {
16980
+ const data = viem.encodeFunctionData({
16981
+ abi: UNISWAP_V4_QUOTER_ABI,
16982
+ functionName: "quoteExactOutputSingle",
16983
+ args: [
16984
+ {
16985
+ poolKey: poolConfig.poolKey,
16986
+ zeroForOne: getZeroForOne(poolConfig.tokenInForQuote, poolConfig.poolKey),
16987
+ exactAmount: amountOut,
16988
+ hookData: "0x"
16989
+ }
16990
+ ]
16991
+ });
16992
+ const response = await client.call({ to: poolConfig.quoterAddress, data });
16993
+ if (!response.data || response.data === "0x") {
16994
+ throw new Error(`Uniswap V4 quoter at ${poolConfig.quoterAddress} returned no data`);
16995
+ }
16996
+ const [amountIn] = viem.decodeFunctionResult({
16997
+ abi: UNISWAP_V4_QUOTER_ABI,
16998
+ functionName: "quoteExactOutputSingle",
16999
+ data: response.data
17000
+ });
17001
+ return amountIn;
17002
+ }
17003
+ };
17004
+ function normalizePoolKey(poolKey) {
17005
+ return {
17006
+ currency0: viem.getAddress(poolKey.currency0),
17007
+ currency1: viem.getAddress(poolKey.currency1),
17008
+ fee: poolKey.fee,
17009
+ tickSpacing: poolKey.tickSpacing,
17010
+ hooks: viem.getAddress(poolKey.hooks)
17011
+ };
17012
+ }
17013
+ function sortCurrencies(tokenIn, tokenOut) {
17014
+ const input = viem.getAddress(tokenIn);
17015
+ const output = viem.getAddress(tokenOut);
17016
+ return BigInt(input) < BigInt(output) ? { currency0: input, currency1: output } : { currency0: output, currency1: input };
17017
+ }
17018
+ function matchPoolToken(token, poolTokens) {
17019
+ const tokenAddress = token.address.toLowerCase();
17020
+ const addressMatch = poolTokens.find((poolToken) => poolToken.address.toLowerCase() === tokenAddress);
17021
+ if (addressMatch) return addressMatch;
17022
+ const tokenSymbol = token.symbol?.toUpperCase();
17023
+ if (!tokenSymbol) return null;
17024
+ return poolTokens.find((poolToken) => poolToken.symbol.toUpperCase() === tokenSymbol) ?? null;
17025
+ }
17026
+ function getZeroForOne(tokenIn, poolKey) {
17027
+ return viem.getAddress(tokenIn).toLowerCase() === viem.getAddress(poolKey.currency0).toLowerCase();
17028
+ }
17029
+ function isGatewayParamsTuple(value) {
17030
+ return Array.isArray(value);
17031
+ }
17032
+ function deductProtocolFee(amount, protocolFeeBps) {
17033
+ if (protocolFeeBps <= 0n) return amount;
17034
+ const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
17035
+ return amount - fee;
17036
+ }
17037
+ function grossUpForProtocolFee(netAmount, protocolFeeBps) {
17038
+ if (protocolFeeBps <= 0n) return netAmount;
17039
+ return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
17040
+ }
17041
+ function divCeil(numerator, denominator) {
17042
+ return (numerator + denominator - 1n) / denominator;
17043
+ }
17044
+
16684
17045
  // src/protocols/intents/IntentGateway.ts
16685
17046
  var IntentGateway = class _IntentGateway {
16686
17047
  /** EVM chain on which orders are placed and escrowed. */
@@ -16707,6 +17068,8 @@ var IntentGateway = class _IntentGateway {
16707
17068
  bidManager;
16708
17069
  /** Estimates gas costs for filling an order and converts them to fee-token amounts. */
16709
17070
  gasEstimator;
17071
+ /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17072
+ quoteStrategies;
16710
17073
  /**
16711
17074
  * Private constructor — use {@link IntentGateway.create} instead.
16712
17075
  *
@@ -16744,12 +17107,15 @@ var IntentGateway = class _IntentGateway {
16744
17107
  const bidManager = new BidManager(this.ctx, crypto);
16745
17108
  const gasEstimator = new GasEstimator(this.ctx, crypto);
16746
17109
  this.orderPlacer = new OrderPlacer(this.ctx);
16747
- this.orderExecutor = new OrderExecutor(this.ctx, bidManager, crypto);
17110
+ this.orderExecutor = new OrderExecutor(this.ctx, bidManager);
16748
17111
  this.orderCanceller = new OrderCanceller(this.ctx);
16749
17112
  this.orderStatusChecker = new OrderStatusChecker(this.ctx);
16750
17113
  this.bidManager = bidManager;
16751
17114
  this.gasEstimator = gasEstimator;
16752
17115
  this._crypto = crypto;
17116
+ this.quoteStrategies = {
17117
+ uniswap_v4: new UniswapV4IntentQuoteStrategy(dest.configService)
17118
+ };
16753
17119
  }
16754
17120
  /**
16755
17121
  * Creates an initialized IntentGateway instance.
@@ -16794,6 +17160,33 @@ var IntentGateway = class _IntentGateway {
16794
17160
  }
16795
17161
  }
16796
17162
  }
17163
+ /**
17164
+ * Quotes an intent between this gateway's source and destination chains.
17165
+ *
17166
+ * `strategy` defaults to `uniswap_v4`, currently the only supported
17167
+ * strategy. Provide exactly one of `amountIn` or `amountOut`.
17168
+ *
17169
+ * The Uniswap quote strategy always prices against the configured Base
17170
+ * pool, regardless of this gateway's destination chain. Returned
17171
+ * `amountIn`/`amountOut` already account for the gateway's protocol fee
17172
+ * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
17173
+ * inputs; apply only your own slippage tolerance before placing the order.
17174
+ *
17175
+ * @param params - Token pair, amount, and optional strategy/pool overrides.
17176
+ * @returns The quoted amounts plus strategy-specific metadata.
17177
+ * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
17178
+ * @throws {UnsupportedIntentQuotePairError} When no pool is configured for the pair.
17179
+ */
17180
+ async quoteIntent(params) {
17181
+ const strategy = params.strategy ?? "uniswap_v4";
17182
+ const handler = this.quoteStrategies[strategy];
17183
+ if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17184
+ return handler.quote(
17185
+ { ...params, strategy },
17186
+ { stateMachineId: this.source.config.stateMachineId, client: this.source.client },
17187
+ { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client }
17188
+ );
17189
+ }
16797
17190
  /**
16798
17191
  * Bidirectional async generator that orchestrates the full order lifecycle:
16799
17192
  * placement, fee estimation, bid collection, and execution.
@@ -16848,17 +17241,38 @@ var IntentGateway = class _IntentGateway {
16848
17241
  }
16849
17242
  const { order: finalizedOrder, receipt: placementReceipt } = placeOrderSecond.value;
16850
17243
  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
- }
17244
+ yield* this.driveExecution(
17245
+ this.orderExecutor.executeOrder({
17246
+ order: finalizedOrder,
17247
+ sessionPrivateKey,
17248
+ auctionTimeMs: options.auctionTimeMs,
17249
+ pollIntervalMs: options.pollIntervalMs,
17250
+ solver: options.solver
17251
+ })
17252
+ );
16860
17253
  return;
16861
17254
  }
17255
+ /**
17256
+ * Forwards updates from the executor's bidirectional generator to the caller,
17257
+ * threading the {@link SelectBidResult} the caller feeds back after a
17258
+ * `BIDS_RECEIVED` yield into the executor's `.next()`. Other yields expect no
17259
+ * feedback. This is what lets the consumer own `bid.execute()` while the
17260
+ * executor keeps tracking fills and continuing the auction.
17261
+ */
17262
+ async *driveExecution(execGen) {
17263
+ try {
17264
+ let input;
17265
+ while (true) {
17266
+ const { value, done } = await execGen.next(input);
17267
+ input = void 0;
17268
+ if (done) break;
17269
+ const fed = yield value;
17270
+ if (value.status === "BIDS_RECEIVED") input = fed;
17271
+ }
17272
+ } finally {
17273
+ await execGen.return();
17274
+ }
17275
+ }
16862
17276
  /**
16863
17277
  * Validates that an order has the minimum fields required for post-placement
16864
17278
  * resume (i.e. it was previously placed and has an on-chain identity).
@@ -16895,14 +17309,99 @@ var IntentGateway = class _IntentGateway {
16895
17309
  */
16896
17310
  async *resume(order, options) {
16897
17311
  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;
17312
+ yield* this.driveExecution(
17313
+ this.orderExecutor.executeOrder({
17314
+ order,
17315
+ sessionPrivateKey: options.sessionPrivateKey,
17316
+ auctionTimeMs: options.auctionTimeMs,
17317
+ pollIntervalMs: options.pollIntervalMs,
17318
+ solver: options.solver
17319
+ })
17320
+ );
17321
+ }
17322
+ /**
17323
+ * Batteries-included variant of {@link execute}: places the order and then
17324
+ * auto-selects the best bid each round via {@link selectAndExecuteBest}, with
17325
+ * no bid-selection input from the caller.
17326
+ *
17327
+ * The caller still signs the placement transaction: this generator yields
17328
+ * `AWAITING_PLACE_ORDER` and the caller must hand the signed tx back via
17329
+ * `gen.next(signedTx)` exactly as with {@link execute}. Every other stage
17330
+ * (`BIDS_RECEIVED`, `BID_SELECTED`, `FILLED`, …) is handled automatically and
17331
+ * surfaced for observation, so the rest of the loop needs no feedback.
17332
+ *
17333
+ * @param order - The order to place and execute.
17334
+ * @param graffiti - Optional orderflow-attribution tag.
17335
+ * @param options - Same tuning parameters as {@link execute}.
17336
+ * @yields {@link IntentOrderStatusUpdate} at each lifecycle stage.
17337
+ */
17338
+ async *executeBest(order, graffiti = DEFAULT_GRAFFITI, options) {
17339
+ const gen = this.execute(order, graffiti, options);
17340
+ try {
17341
+ let input;
17342
+ while (true) {
17343
+ const { value, done } = await gen.next(input);
17344
+ input = void 0;
17345
+ if (done) break;
17346
+ if (value.status === "BIDS_RECEIVED") {
17347
+ yield value;
17348
+ input = await this.autoSelect(order, value.bids);
17349
+ } else if (value.status === "AWAITING_PLACE_ORDER") {
17350
+ input = yield value;
17351
+ } else {
17352
+ yield value;
17353
+ }
17354
+ }
17355
+ } finally {
17356
+ await gen.return();
17357
+ }
17358
+ }
17359
+ /**
17360
+ * Batteries-included variant of {@link resume}: auto-selects the best bid each
17361
+ * round via {@link selectAndExecuteBest}, with no bid-selection input from the
17362
+ * caller. A plain `for await` loop is sufficient — there is no placement step.
17363
+ *
17364
+ * @param order - A previously placed order with a valid `id` and `session`.
17365
+ * @param options - Optional tuning parameters for bid collection and execution.
17366
+ * @yields {@link IntentOrderStatusUpdate} at each execution stage.
17367
+ */
17368
+ async *resumeBest(order, options) {
17369
+ const gen = this.resume(order, options);
17370
+ try {
17371
+ let input;
17372
+ while (true) {
17373
+ const { value, done } = await gen.next(input);
17374
+ input = void 0;
17375
+ if (done) break;
17376
+ yield value;
17377
+ if (value.status === "BIDS_RECEIVED") {
17378
+ input = await this.autoSelect(order, value.bids);
17379
+ }
17380
+ }
17381
+ } finally {
17382
+ await gen.return();
17383
+ }
17384
+ }
17385
+ /**
17386
+ * Auto-select wrapper used by {@link executeBest} / {@link resumeBest}.
17387
+ *
17388
+ * Runs {@link selectAndExecuteBest} and returns the {@link SelectBidResult} to
17389
+ * feed back to the executor. If selection fails this round — all bids fail
17390
+ * simulation, no valid bids, or the bundler rejects the UserOp — it swallows
17391
+ * the error and returns `undefined`, which tells the executor to keep polling
17392
+ * for fresh bids until the deadline rather than aborting the order. Swallowing
17393
+ * the error here (rather than letting it propagate) also keeps the executor's
17394
+ * `finally` teardown intact, since nothing throws across the suspended
17395
+ * generators.
17396
+ */
17397
+ async autoSelect(order, bids) {
17398
+ try {
17399
+ return await this.selectAndExecuteBest(order, bids);
17400
+ } catch (err) {
17401
+ console.warn(
17402
+ `[IntentGateway] autoSelect: bid selection failed this round, continuing to poll: ${err instanceof Error ? err.message : String(err)}`
17403
+ );
17404
+ return void 0;
16906
17405
  }
16907
17406
  }
16908
17407
  /**
@@ -16958,10 +17457,52 @@ var IntentGateway = class _IntentGateway {
16958
17457
  return this.bidManager.prepareSubmitBid(options);
16959
17458
  }
16960
17459
  /**
16961
- * Selects the best available bid, simulates it, and submits the UserOperation
16962
- * to the bundler.
17460
+ * Decodes raw filler bids into first-class {@link Bid} objects that can be
17461
+ * ranked, simulated, and executed by the consumer.
17462
+ *
17463
+ * Delegates to {@link BidManager.buildBids}.
17464
+ *
17465
+ * @param order - The placed order the bids are competing to fill.
17466
+ * @param bids - Raw filler bids fetched from the coprocessor.
17467
+ * @param sessionPrivateKey - Optional session key override; looked up from
17468
+ * storage by `order.session` if omitted.
17469
+ * @returns Array of executable {@link Bid} objects.
17470
+ */
17471
+ buildBids(order, bids, sessionPrivateKey) {
17472
+ return this.bidManager.buildBids(order, bids, sessionPrivateKey);
17473
+ }
17474
+ /**
17475
+ * Sorts bids by output value using the same strategy the autopilot uses.
17476
+ *
17477
+ * Delegates to {@link BidManager.sortBids}.
17478
+ *
17479
+ * @param order - The placed order whose output spec drives sorting.
17480
+ * @param bids - Bids to sort (from {@link buildBids}).
17481
+ * @returns Sorted array of {@link Bid} objects.
17482
+ */
17483
+ async sortBids(order, bids) {
17484
+ return this.bidManager.sortBids(order, bids);
17485
+ }
17486
+ /**
17487
+ * Autopilot bid selection: sorts the given bids, simulates each until one
17488
+ * passes, then executes it.
17489
+ *
17490
+ * Delegates to {@link BidManager.selectAndExecuteBest}.
17491
+ *
17492
+ * @param order - The placed order to fill.
17493
+ * @param bids - Candidate bids (from {@link buildBids}).
17494
+ * @returns A {@link SelectBidResult} with the submitted UserOperation, hashes,
17495
+ * and fill status.
17496
+ */
17497
+ async selectAndExecuteBest(order, bids) {
17498
+ return this.bidManager.selectAndExecuteBest(order, bids);
17499
+ }
17500
+ /**
17501
+ * Decodes, sorts, simulates, signs, and submits the best of the given raw
17502
+ * filler bids with no per-bid input from the caller.
16963
17503
  *
16964
- * Delegates to {@link BidManager.selectBid}.
17504
+ * Delegates to {@link BidManager.selectBid}. Prefer {@link buildBids} +
17505
+ * {@link selectAndExecuteBest} (or {@link executeBest}) for new code.
16965
17506
  *
16966
17507
  * @param order - The placed order to fill.
16967
17508
  * @param bids - Raw filler bids fetched from the coprocessor.
@@ -17881,7 +18422,7 @@ var HyperFungibleTokenABI = [
17881
18422
  internalType: "uint256"
17882
18423
  }
17883
18424
  ],
17884
- stateMutability: "view"
18425
+ stateMutability: "nonpayable"
17885
18426
  },
17886
18427
  {
17887
18428
  type: "function",
@@ -17932,7 +18473,7 @@ var HyperFungibleTokenABI = [
17932
18473
  internalType: "uint256"
17933
18474
  }
17934
18475
  ],
17935
- stateMutability: "view"
18476
+ stateMutability: "nonpayable"
17936
18477
  },
17937
18478
  {
17938
18479
  type: "function",
@@ -17983,7 +18524,7 @@ var HyperFungibleTokenABI = [
17983
18524
  internalType: "uint256"
17984
18525
  }
17985
18526
  ],
17986
- stateMutability: "view"
18527
+ stateMutability: "nonpayable"
17987
18528
  },
17988
18529
  {
17989
18530
  type: "function",
@@ -19002,7 +19543,7 @@ var WrappedHyperFungibleTokenABI = [
19002
19543
  internalType: "uint256"
19003
19544
  }
19004
19545
  ],
19005
- stateMutability: "view"
19546
+ stateMutability: "nonpayable"
19006
19547
  },
19007
19548
  {
19008
19549
  type: "function",
@@ -19053,7 +19594,7 @@ var WrappedHyperFungibleTokenABI = [
19053
19594
  internalType: "uint256"
19054
19595
  }
19055
19596
  ],
19056
- stateMutability: "view"
19597
+ stateMutability: "nonpayable"
19057
19598
  },
19058
19599
  {
19059
19600
  type: "function",
@@ -19104,7 +19645,7 @@ var WrappedHyperFungibleTokenABI = [
19104
19645
  internalType: "uint256"
19105
19646
  }
19106
19647
  ],
19107
- stateMutability: "view"
19648
+ stateMutability: "nonpayable"
19108
19649
  },
19109
19650
  {
19110
19651
  type: "function",
@@ -19646,13 +20187,13 @@ var HyperFungibleToken = class {
19646
20187
  };
19647
20188
  let totalNativeCost = 0n;
19648
20189
  try {
19649
- totalNativeCost = await this.source.client.readContract({
20190
+ const { result } = await this.source.client.simulateContract({
19650
20191
  address: params.token,
19651
20192
  abi: HyperFungibleTokenABI,
19652
20193
  functionName: "quote",
19653
20194
  args: [sendParams]
19654
20195
  });
19655
- totalNativeCost = totalNativeCost * 101n / 100n;
20196
+ totalNativeCost = result * 101n / 100n;
19656
20197
  } catch {
19657
20198
  }
19658
20199
  return {
@@ -21606,6 +22147,8 @@ exports.TimeoutStatus = TimeoutStatus;
21606
22147
  exports.TokenGateway = TokenGateway;
21607
22148
  exports.TronChain = TronChain;
21608
22149
  exports.USE_ETHERSCAN_CHAINS = USE_ETHERSCAN_CHAINS;
22150
+ exports.UnsupportedIntentQuotePairError = UnsupportedIntentQuotePairError;
22151
+ exports.UnsupportedIntentQuoteStrategyError = UnsupportedIntentQuoteStrategyError;
21609
22152
  exports.WrappedHyperFungibleTokenABI = WrappedHyperFungibleTokenABI;
21610
22153
  exports.__test = __test;
21611
22154
  exports.adjustDecimals = adjustDecimals;