@hyperbridge/sdk 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2490,30 +2490,30 @@ var handlerV2_default = { ABI: ABI2 };
2490
2490
  function tronAddress(base58) {
2491
2491
  return `0x${tronweb.TronWeb.address.toHex(base58).slice(2)}`;
2492
2492
  }
2493
- var Chains = /* @__PURE__ */ ((Chains3) => {
2494
- Chains3["BSC_CHAPEL"] = "EVM-97";
2495
- Chains3["GNOSIS_CHIADO"] = "EVM-10200";
2496
- Chains3["HYPERBRIDGE_GARGANTUA"] = "KUSAMA-4009";
2497
- Chains3["SEPOLIA"] = "EVM-11155111";
2498
- Chains3["MAINNET"] = "EVM-1";
2499
- Chains3["BSC_MAINNET"] = "EVM-56";
2500
- Chains3["ARBITRUM_MAINNET"] = "EVM-42161";
2501
- Chains3["ARBITRUM_SEPOLIA"] = "EVM-421614";
2502
- Chains3["BASE_MAINNET"] = "EVM-8453";
2503
- Chains3["BASE_SEPOLIA"] = "EVM-84532";
2504
- Chains3["OPTIMISM_MAINNET"] = "EVM-10";
2505
- Chains3["OPTIMISM_SEPOLIA"] = "EVM-11155420";
2506
- Chains3["GNOSIS_MAINNET"] = "EVM-100";
2507
- Chains3["SONEIUM_MAINNET"] = "EVM-1868";
2508
- Chains3["POLYGON_MAINNET"] = "EVM-137";
2509
- Chains3["POLKADOT_HUB_MAINNET"] = "EVM-420420419";
2510
- Chains3["POLYGON_AMOY"] = "EVM-80002";
2511
- Chains3["POLKADOT_ASSET_HUB_PASEO"] = "EVM-420420417";
2512
- Chains3["TRON_MAINNET"] = "EVM-728126428";
2513
- Chains3["TRON_NILE"] = "EVM-3448148188";
2514
- Chains3["PHAROS_MAINNET"] = "EVM-688600";
2515
- Chains3["PHAROS_ATLANTIC"] = "EVM-688689";
2516
- return Chains3;
2493
+ var Chains = /* @__PURE__ */ ((Chains2) => {
2494
+ Chains2["BSC_CHAPEL"] = "EVM-97";
2495
+ Chains2["GNOSIS_CHIADO"] = "EVM-10200";
2496
+ Chains2["HYPERBRIDGE_GARGANTUA"] = "KUSAMA-4009";
2497
+ Chains2["SEPOLIA"] = "EVM-11155111";
2498
+ Chains2["MAINNET"] = "EVM-1";
2499
+ Chains2["BSC_MAINNET"] = "EVM-56";
2500
+ Chains2["ARBITRUM_MAINNET"] = "EVM-42161";
2501
+ Chains2["ARBITRUM_SEPOLIA"] = "EVM-421614";
2502
+ Chains2["BASE_MAINNET"] = "EVM-8453";
2503
+ Chains2["BASE_SEPOLIA"] = "EVM-84532";
2504
+ Chains2["OPTIMISM_MAINNET"] = "EVM-10";
2505
+ Chains2["OPTIMISM_SEPOLIA"] = "EVM-11155420";
2506
+ Chains2["GNOSIS_MAINNET"] = "EVM-100";
2507
+ Chains2["SONEIUM_MAINNET"] = "EVM-1868";
2508
+ Chains2["POLYGON_MAINNET"] = "EVM-137";
2509
+ Chains2["POLKADOT_HUB_MAINNET"] = "EVM-420420419";
2510
+ Chains2["POLYGON_AMOY"] = "EVM-80002";
2511
+ Chains2["POLKADOT_ASSET_HUB_PASEO"] = "EVM-420420417";
2512
+ Chains2["TRON_MAINNET"] = "EVM-728126428";
2513
+ Chains2["TRON_NILE"] = "EVM-3448148188";
2514
+ Chains2["PHAROS_MAINNET"] = "EVM-688600";
2515
+ Chains2["PHAROS_ATLANTIC"] = "EVM-688689";
2516
+ return Chains2;
2517
2517
  })(Chains || {});
2518
2518
  var polkadotAssetHubPaseo = viem.defineChain({
2519
2519
  id: 420420417,
@@ -2704,7 +2704,7 @@ var chainConfigs = {
2704
2704
  },
2705
2705
  addresses: {
2706
2706
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2707
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2707
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2708
2708
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2709
2709
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2710
2710
  UniswapRouter02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
@@ -2720,6 +2720,7 @@ var chainConfigs = {
2720
2720
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2721
2721
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2722
2722
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2723
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2723
2724
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2724
2725
  },
2725
2726
  rpcEnvKey: "ETH_MAINNET",
@@ -2759,7 +2760,7 @@ var chainConfigs = {
2759
2760
  },
2760
2761
  addresses: {
2761
2762
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2762
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2763
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2763
2764
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2764
2765
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2765
2766
  UniswapRouter02: "0x10ED43C718714eb63d5aA57B78B54704E256024E",
@@ -2773,7 +2774,8 @@ var chainConfigs = {
2773
2774
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2774
2775
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2775
2776
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2776
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2777
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2778
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2777
2779
  // "Usdt0Oft": Not available on BSC
2778
2780
  },
2779
2781
  rpcEnvKey: "BSC_MAINNET",
@@ -2815,7 +2817,7 @@ var chainConfigs = {
2815
2817
  },
2816
2818
  addresses: {
2817
2819
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2818
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2820
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2819
2821
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2820
2822
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2821
2823
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2831,6 +2833,7 @@ var chainConfigs = {
2831
2833
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2832
2834
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2833
2835
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2836
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2834
2837
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2835
2838
  },
2836
2839
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2872,7 +2875,7 @@ var chainConfigs = {
2872
2875
  },
2873
2876
  addresses: {
2874
2877
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2875
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2878
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2876
2879
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2877
2880
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2878
2881
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2885,6 +2888,7 @@ var chainConfigs = {
2885
2888
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2886
2889
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2887
2890
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2891
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2888
2892
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2889
2893
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2890
2894
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2931,7 +2935,7 @@ var chainConfigs = {
2931
2935
  },
2932
2936
  addresses: {
2933
2937
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2934
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2938
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2935
2939
  TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E",
2936
2940
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2937
2941
  UniswapRouter02: "0xd2f9496824951D5237cC71245D659E48d0d5f9E8",
@@ -2947,6 +2951,7 @@ var chainConfigs = {
2947
2951
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2948
2952
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2949
2953
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2954
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2950
2955
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2951
2956
  },
2952
2957
  rpcEnvKey: "POLYGON_MAINNET",
@@ -3042,7 +3047,7 @@ var chainConfigs = {
3042
3047
  },
3043
3048
  addresses: {
3044
3049
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3045
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3050
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3046
3051
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3047
3052
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3048
3053
  UniswapRouter02: "0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2",
@@ -3077,7 +3082,7 @@ var chainConfigs = {
3077
3082
  },
3078
3083
  addresses: {
3079
3084
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3080
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3085
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3081
3086
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3082
3087
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3083
3088
  UniswapRouter02: "0xB2e26652e4BAd1e56055A051f922E06760cA0BFE",
@@ -3232,7 +3237,7 @@ var chainConfigs = {
3232
3237
  },
3233
3238
  addresses: {
3234
3239
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3235
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3240
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3236
3241
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3237
3242
  Calldispatcher: "0xE2C7e576E26E0bE7aC97c6fE925bcDAbD87c4bEd"
3238
3243
  },
@@ -5118,6 +5123,24 @@ var ChainConfigService = class {
5118
5123
  getAssetAddress(chain, symbol) {
5119
5124
  return this.getConfig(chain)?.assets?.[symbol];
5120
5125
  }
5126
+ /**
5127
+ * Resolves configured token metadata from an address on a specific chain.
5128
+ * This is used by SDK helpers that accept token addresses rather than caller-
5129
+ * supplied symbols or decimals.
5130
+ */
5131
+ getAssetMetadataByAddress(chain, address) {
5132
+ const config = this.getConfig(chain);
5133
+ if (!config?.assets) return;
5134
+ const normalizedAddress = address.toLowerCase();
5135
+ for (const [symbol, configuredAddress] of Object.entries(config.assets)) {
5136
+ if (configuredAddress.toLowerCase() !== normalizedAddress) continue;
5137
+ return {
5138
+ symbol,
5139
+ address: configuredAddress,
5140
+ decimals: config.tokenDecimals?.[symbol]
5141
+ };
5142
+ }
5143
+ }
5121
5144
  getUsdtAsset(chain) {
5122
5145
  return this.getConfig(chain)?.assets?.USDT ?? "0x";
5123
5146
  }
@@ -5224,6 +5247,9 @@ var ChainConfigService = class {
5224
5247
  getCirclePaymasterAddress(chain) {
5225
5248
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5226
5249
  }
5250
+ getSimplexPaymasterAddress(chain) {
5251
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5252
+ }
5227
5253
  getHyperbridgeAddress() {
5228
5254
  return hyperbridgeAddress;
5229
5255
  }
@@ -7044,6 +7070,16 @@ var ExpectedError = class _ExpectedError extends Error {
7044
7070
  return error instanceof _ExpectedError;
7045
7071
  }
7046
7072
  };
7073
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7074
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7075
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7076
+ super(message, options);
7077
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7078
+ }
7079
+ static isError(error) {
7080
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7081
+ }
7082
+ };
7047
7083
 
7048
7084
  // src/configs/constants.ts
7049
7085
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7357,8 +7393,15 @@ var SubstrateChain = class _SubstrateChain {
7357
7393
  id: stateMachineId,
7358
7394
  height: Number(stateMachineHeight.height)
7359
7395
  };
7360
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7361
- return BigInt(updateTime);
7396
+ try {
7397
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7398
+ return BigInt(updateTime);
7399
+ } catch (error) {
7400
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7401
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7402
+ }
7403
+ throw error;
7404
+ }
7362
7405
  }
7363
7406
  /**
7364
7407
  * Get the challenge period for a given state machine id.
@@ -7791,7 +7834,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7791
7834
  */
7792
7835
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
7793
7836
  const keyPair = this.getKeyPair();
7794
- let baseTip = 500000000000n;
7837
+ const baseTip = 1000000000n;
7795
7838
  let attempt = 0;
7796
7839
  while (attempt < maxRetries) {
7797
7840
  const currentTip = baseTip * BigInt(2 ** attempt);
@@ -8104,26 +8147,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8104
8147
  };
8105
8148
  }
8106
8149
  /**
8107
- * Subscribes to PhantomOrderRegistered events from the intents coprocessor pallet.
8108
- * Calls the callback for each new phantom order as blocks arrive.
8109
- * Returns an unsubscribe function to stop the subscription.
8110
- */
8111
- async subscribePhantomOrders(callback) {
8112
- const unsub = await this.api.query.system.events((records) => {
8113
- for (const { event } of records) {
8114
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8115
- const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8116
- callback({
8117
- commitment: commitment.toHex(),
8118
- chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8119
- createdAt: createdAt.toNumber(),
8120
- tokenA: tokenA.toHex(),
8121
- tokenB: tokenB.toHex(),
8122
- standardAmount: BigInt(standardAmount.toString())
8123
- });
8150
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8151
+ */
8152
+ async getPhantomOrdersInBlock(blockNumber) {
8153
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8154
+ const apiAt = await this.api.at(blockHash);
8155
+ const records = await apiAt.query.system.events();
8156
+ const orders = [];
8157
+ for (const { event } of records) {
8158
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8159
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8160
+ orders.push({
8161
+ commitment: commitment.toHex(),
8162
+ chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8163
+ createdAt: createdAt.toNumber(),
8164
+ tokenA: tokenA.toHex(),
8165
+ tokenB: tokenB.toHex(),
8166
+ standardAmount: BigInt(standardAmount.toString())
8167
+ });
8168
+ }
8169
+ return orders;
8170
+ }
8171
+ /**
8172
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8173
+ *
8174
+ * Each tick reads the current head and scans every block between the last one processed and that
8175
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8176
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8177
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8178
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8179
+ * meant silently missed bids.
8180
+ *
8181
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8182
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8183
+ * read. Recovery replays the backlog.
8184
+ *
8185
+ * Returns a function that stops polling.
8186
+ */
8187
+ pollPhantomOrders(callback, options = {}) {
8188
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8189
+ let cursor = null;
8190
+ let inFlight = false;
8191
+ let stopped = false;
8192
+ const tick = async () => {
8193
+ if (inFlight || stopped) return;
8194
+ inFlight = true;
8195
+ try {
8196
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8197
+ if (cursor === null) {
8198
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8199
+ }
8200
+ if (head <= cursor) return;
8201
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8202
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8203
+ if (stopped) return;
8204
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8205
+ for (const order of orders) callback(order);
8206
+ cursor = blockNumber;
8207
+ }
8208
+ } catch (err) {
8209
+ onError?.(err);
8210
+ } finally {
8211
+ inFlight = false;
8124
8212
  }
8125
- });
8126
- return unsub;
8213
+ };
8214
+ void tick();
8215
+ const timer = setInterval(() => void tick(), intervalMs);
8216
+ return () => {
8217
+ stopped = true;
8218
+ clearInterval(timer);
8219
+ };
8127
8220
  }
8128
8221
  };
8129
8222
  var TronChain = class _TronChain {
@@ -9256,7 +9349,12 @@ function sleep(ms) {
9256
9349
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9257
9350
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9258
9351
  if (challengePeriod === BigInt(0)) return;
9259
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9352
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9353
+ maxRetries: 3,
9354
+ backoffMs: 500,
9355
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9356
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9357
+ });
9260
9358
  let currentTimestamp = await chain.timestamp();
9261
9359
  let timeElapsed = currentTimestamp - updateTime;
9262
9360
  if (timeElapsed > challengePeriod) return;
@@ -9403,6 +9501,10 @@ function normalizeAddressForEvmBytes32(address) {
9403
9501
  if (address.length === 66) return address;
9404
9502
  return bytes20ToBytes32(address);
9405
9503
  }
9504
+ function normalizeEvmAddress(address, field = "address") {
9505
+ if (!viem.isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9506
+ return address.toLowerCase();
9507
+ }
9406
9508
  function normalizeAddressForStateMachine(address, stateMachineId) {
9407
9509
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9408
9510
  }
@@ -9410,14 +9512,16 @@ var DEFAULT_LOGGER = consola.createConsola({
9410
9512
  level: consola.LogLevels.silent
9411
9513
  });
9412
9514
  async function retryPromise(operation, retryConfig) {
9413
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9515
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9414
9516
  let lastError;
9415
9517
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9416
9518
  try {
9417
9519
  return await operation();
9418
9520
  } catch (error) {
9521
+ if (shouldRetry && !shouldRetry(error)) throw error;
9419
9522
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9420
9523
  lastError = error;
9524
+ if (i === retryConfig.maxRetries - 1) break;
9421
9525
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9422
9526
  }
9423
9527
  }
@@ -11461,6 +11565,82 @@ query OrderStatus($commitment: String!) {
11461
11565
  }
11462
11566
  }
11463
11567
  }`;
11568
+ var LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT = `
11569
+ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11570
+ phantomOrderPriceSnapshots(
11571
+ filter: {
11572
+ and: [
11573
+ { tokenA: { equalTo: $tokenA } }
11574
+ { tokenB: { equalTo: $tokenB } }
11575
+ { medianPrice: { isNull: false } }
11576
+ ]
11577
+ }
11578
+ orderBy: BLOCK_NUMBER_DESC
11579
+ first: 1
11580
+ ) {
11581
+ nodes {
11582
+ commitment
11583
+ tokenA
11584
+ tokenB
11585
+ standardAmount
11586
+ blockNumber
11587
+ medianPrice
11588
+ lowestPrice
11589
+ highestPrice
11590
+ bidCount
11591
+ snapshotTime
11592
+ }
11593
+ }
11594
+ }`;
11595
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11596
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11597
+ phantomOrderPriceSnapshots(
11598
+ filter: {
11599
+ and: [
11600
+ { tokenA: { equalTo: $tokenA } }
11601
+ { tokenB: { equalTo: $tokenB } }
11602
+ ]
11603
+ }
11604
+ orderBy: SNAPSHOT_TIME_DESC
11605
+ first: 1
11606
+ ) {
11607
+ nodes {
11608
+ commitment
11609
+ tokenA
11610
+ tokenB
11611
+ snapshotTime
11612
+ }
11613
+ }
11614
+ }`;
11615
+ var LIQUIDITY_PROVIDER_BALANCES = `
11616
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11617
+ liquidityProviderBalances(
11618
+ filter: {
11619
+ and: [
11620
+ { commitment: { equalTo: $commitment } }
11621
+ { tokenAddress: { equalTo: $tokenAddress } }
11622
+ ]
11623
+ }
11624
+ ) {
11625
+ aggregates {
11626
+ sum {
11627
+ balance
11628
+ }
11629
+ distinctCount {
11630
+ providerId
11631
+ }
11632
+ }
11633
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11634
+ keys
11635
+ sum {
11636
+ balance
11637
+ }
11638
+ distinctCount {
11639
+ providerId
11640
+ }
11641
+ }
11642
+ }
11643
+ }`;
11464
11644
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11465
11645
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11466
11646
  tokenGatewayAssetTeleportedV2s(
@@ -11522,6 +11702,29 @@ async function queryAssetTeleported(params) {
11522
11702
  function queryGetRequest(params) {
11523
11703
  return _queryGetRequestInternal(params);
11524
11704
  }
11705
+ async function _queryLatestPhantomOrderPriceSnapshot(params) {
11706
+ const response = await params.queryClient.request(
11707
+ LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT,
11708
+ {
11709
+ tokenA: params.tokenA.toLowerCase(),
11710
+ tokenB: params.tokenB.toLowerCase()
11711
+ }
11712
+ );
11713
+ const snapshot = response.phantomOrderPriceSnapshots.nodes[0];
11714
+ if (!snapshot?.medianPrice) return;
11715
+ return {
11716
+ commitment: snapshot.commitment,
11717
+ tokenA: snapshot.tokenA,
11718
+ tokenB: snapshot.tokenB,
11719
+ standardAmount: BigInt(snapshot.standardAmount),
11720
+ blockNumber: BigInt(snapshot.blockNumber),
11721
+ medianPrice: BigInt(snapshot.medianPrice),
11722
+ lowestPrice: snapshot.lowestPrice === null ? void 0 : BigInt(snapshot.lowestPrice),
11723
+ highestPrice: snapshot.highestPrice === null ? void 0 : BigInt(snapshot.highestPrice),
11724
+ bidCount: snapshot.bidCount,
11725
+ snapshotTime: new Date(snapshot.snapshotTime)
11726
+ };
11727
+ }
11525
11728
  async function _queryTokenGatewayAssetTeleportedInternal(params) {
11526
11729
  const { commitmentHash, queryClient: client, logger = DEFAULT_LOGGER } = params;
11527
11730
  const response = await retryPromise(
@@ -12022,6 +12225,7 @@ function createCancellationStorage(options = {}) {
12022
12225
  const decoded = decodeIProof(value);
12023
12226
  return decoded;
12024
12227
  }
12228
+ if (key2.includes("postCommitment")) return value;
12025
12229
  throw new Error(`Unknown storage key type: ${key2}`);
12026
12230
  };
12027
12231
  const setItem = async (key2, value) => {
@@ -12035,6 +12239,10 @@ function createCancellationStorage(options = {}) {
12035
12239
  await baseStorage.setItem(key2, encoded);
12036
12240
  return;
12037
12241
  }
12242
+ if (key2.includes("postCommitment") && typeof value === "string") {
12243
+ await baseStorage.setItem(key2, value);
12244
+ return;
12245
+ }
12038
12246
  throw new Error(`Unknown storage key type: ${key2}`);
12039
12247
  };
12040
12248
  const removeItem = async (key2) => {
@@ -12048,11 +12256,25 @@ function createCancellationStorage(options = {}) {
12048
12256
  });
12049
12257
  }
12050
12258
  var STORAGE_KEYS = Object.freeze({
12051
- destProof: (orderId) => `cancel-order:${orderId}:destProof`,
12052
- getRequest: (orderId) => `cancel-order:${orderId}:getRequest`,
12053
- sourceProof: (orderId) => `cancel-order:${orderId}:sourceProof`,
12054
- postCommitment: (orderId) => `cancel-order:${orderId}:postCommitment`
12259
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12260
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12261
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12262
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12055
12263
  });
12264
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12265
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12266
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12267
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12268
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12269
+ });
12270
+ function cancellationScope(orderId, source, destination) {
12271
+ if (!source || !destination) return orderId;
12272
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12273
+ }
12274
+ function legacyCancellationScope(orderId, source, destination) {
12275
+ if (!source || !destination) return orderId;
12276
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12277
+ }
12056
12278
  function createUsedUserOpsStorage(options = {}) {
12057
12279
  options.env ?? detectEnvironment();
12058
12280
  const driver = loadDriver({ options }) ?? inMemoryDriver__default.default();
@@ -15218,6 +15440,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15218
15440
  }
15219
15441
 
15220
15442
  // src/protocols/intents/OrderPlacer.ts
15443
+ function deriveCanonicalPlacedOrder(order, args) {
15444
+ const canonicalOrder = {
15445
+ ...order,
15446
+ user: args.user,
15447
+ source: args.source,
15448
+ destination: args.destination,
15449
+ deadline: args.deadline,
15450
+ nonce: args.nonce,
15451
+ fees: args.fees,
15452
+ session: args.session,
15453
+ predispatch: {
15454
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15455
+ call: order.predispatch.call
15456
+ },
15457
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15458
+ output: {
15459
+ beneficiary: args.beneficiary,
15460
+ assets: args.outputs.map((asset) => ({ ...asset })),
15461
+ call: order.output.call
15462
+ }
15463
+ };
15464
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15465
+ }
15221
15466
  var OrderPlacer = class {
15222
15467
  /**
15223
15468
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15234,17 +15479,16 @@ var OrderPlacer = class {
15234
15479
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15235
15480
  * transaction sending `data` to `to` and pass the signed transaction back
15236
15481
  * via `gen.next(signedTx)`.
15237
- * 2. Returns `{ order, transactionHash }` — the finalized order with its
15238
- * on-chain `nonce`, `inputs`, and computed `id`, plus the placement
15239
- * transaction hash.
15482
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15483
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15484
+ * placement transaction hash.
15240
15485
  *
15241
15486
  * A fresh ephemeral session key is generated for every call. The key is
15242
15487
  * stored immediately (without a commitment) so it can be retrieved by
15243
15488
  * address, then updated with the commitment once the `OrderPlaced` event
15244
- * confirms the nonce.
15489
+ * confirms the canonical order.
15245
15490
  *
15246
- * @param order - The order to place. `order.session` is mutated in-place
15247
- * with the generated session key address before yielding.
15491
+ * @param order - The order to place. It is not mutated.
15248
15492
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15249
15493
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15250
15494
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15259,7 +15503,7 @@ var OrderPlacer = class {
15259
15503
  const account = accounts.privateKeyToAccount(privateKey);
15260
15504
  const sessionKeyAddress = account.address;
15261
15505
  const createdAt = Date.now();
15262
- order.session = sessionKeyAddress;
15506
+ const placementOrder = { ...order, session: sessionKeyAddress };
15263
15507
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15264
15508
  privateKey,
15265
15509
  address: sessionKeyAddress,
@@ -15268,7 +15512,7 @@ var OrderPlacer = class {
15268
15512
  const data = viem.encodeFunctionData({
15269
15513
  abi: ABI3,
15270
15514
  functionName: "placeOrder",
15271
- args: [transformOrderForContract(order), graffiti]
15515
+ args: [transformOrderForContract(placementOrder), graffiti]
15272
15516
  });
15273
15517
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15274
15518
  normalizeStateMachineId(order.source)
@@ -15288,20 +15532,15 @@ var OrderPlacer = class {
15288
15532
  if (!orderPlacedEvent) {
15289
15533
  throw new Error("OrderPlaced event not found in transaction receipt");
15290
15534
  }
15291
- order.nonce = orderPlacedEvent.args.nonce;
15292
- order.inputs = orderPlacedEvent.args.inputs.map((input) => ({
15293
- token: input.token,
15294
- amount: input.amount
15295
- }));
15296
- order.id = orderCommitment(order);
15535
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15297
15536
  const sessionKeyData = {
15298
15537
  privateKey,
15299
15538
  address: sessionKeyAddress,
15300
- commitment: order.id,
15539
+ commitment: finalizedOrder.id,
15301
15540
  createdAt
15302
15541
  };
15303
15542
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15304
- return { order, receipt };
15543
+ return { order: finalizedOrder, receipt };
15305
15544
  }
15306
15545
  };
15307
15546
 
@@ -15631,7 +15870,7 @@ var OrderExecutor = class {
15631
15870
  }
15632
15871
  }
15633
15872
  };
15634
- var OrderCanceller = class {
15873
+ var OrderCanceller = class _OrderCanceller {
15635
15874
  /**
15636
15875
  * @param ctx - Shared IntentsV2 context providing the source and destination
15637
15876
  * chain clients, config service, and cancellation storage.
@@ -15640,6 +15879,9 @@ var OrderCanceller = class {
15640
15879
  this.ctx = ctx;
15641
15880
  }
15642
15881
  ctx;
15882
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15883
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15884
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15643
15885
  logger = consola.createConsola({
15644
15886
  level: consola.LogLevels.info,
15645
15887
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15671,11 +15913,14 @@ var OrderCanceller = class {
15671
15913
  * @returns The native token dispatch fee in wei.
15672
15914
  */
15673
15915
  async quoteCancelFromSource(order) {
15674
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15675
15916
  const sourceStateMachine = normalizeStateMachineId(order.source);
15917
+ const destStateMachine = normalizeStateMachineId(order.destination);
15918
+ if (sourceStateMachine === destStateMachine) {
15919
+ return { nativeValue: 0n, relayerFee: 0n };
15920
+ }
15676
15921
  const height = order.deadline + 1n;
15677
15922
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15678
- normalizeStateMachineId(order.destination)
15923
+ destStateMachine
15679
15924
  );
15680
15925
  const slotHash = await this.ctx.dest.client.readContract({
15681
15926
  abi: ABI3,
@@ -15687,8 +15932,8 @@ var OrderCanceller = class {
15687
15932
  const context = encodeWithdrawalRequest(order, order.user);
15688
15933
  const getRequest = {
15689
15934
  source: sourceStateMachine,
15690
- dest: normalizeStateMachineId(order.destination),
15691
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
15935
+ dest: destStateMachine,
15936
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15692
15937
  nonce: await this.ctx.source.getHostNonce(),
15693
15938
  height,
15694
15939
  keys: [key],
@@ -15705,7 +15950,9 @@ var OrderCanceller = class {
15705
15950
  * cancellation is complete.
15706
15951
  *
15707
15952
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15708
- * the `from` parameter.
15953
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
15954
+ * source-side GET recovery, clears its stale checkpoint and restarts the
15955
+ * source-side stream internally.
15709
15956
  *
15710
15957
  * @param order - The order to cancel.
15711
15958
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15715,12 +15962,32 @@ var OrderCanceller = class {
15715
15962
  * cancellation lifecycle.
15716
15963
  */
15717
15964
  async *cancelOrder(order, indexerClient, options = {}) {
15718
- const isSameChain = order.source === order.destination;
15965
+ const sourceStateMachine = normalizeStateMachineId(order.source);
15966
+ const destStateMachine = normalizeStateMachineId(order.destination);
15967
+ const isSameChain = sourceStateMachine === destStateMachine;
15719
15968
  if (options.from === "destination" && !isSameChain) {
15720
15969
  yield* this.cancelOrderFromDest(order, indexerClient);
15721
15970
  return;
15722
15971
  }
15723
- yield* this.cancelOrderFromSource(order, indexerClient);
15972
+ let recoveryRestarts = 0;
15973
+ while (true) {
15974
+ try {
15975
+ yield* this.cancelOrderFromSource(order, indexerClient);
15976
+ return;
15977
+ } catch (error) {
15978
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
15979
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
15980
+ throw new Error(
15981
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
15982
+ );
15983
+ }
15984
+ recoveryRestarts += 1;
15985
+ this.logger.warn(
15986
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
15987
+ );
15988
+ await this.clearGetRecoveryCache(order);
15989
+ }
15990
+ }
15724
15991
  }
15725
15992
  /**
15726
15993
  * Async generator that cancels an order by initiating the cancel from the
@@ -15746,11 +16013,10 @@ var OrderCanceller = class {
15746
16013
  * @throws If the cancel transaction does not contain the expected on-chain event.
15747
16014
  */
15748
16015
  async *cancelOrderFromSource(order, indexerClient) {
15749
- const orderId = order.id;
15750
- const isSameChain = order.source === order.destination;
15751
- const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15752
- normalizeStateMachineId(order.source)
15753
- );
16016
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16017
+ const destStateMachine = normalizeStateMachineId(order.destination);
16018
+ const isSameChain = sourceStateMachine === destStateMachine;
16019
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15754
16020
  if (isSameChain) {
15755
16021
  const data = viem.encodeFunctionData({
15756
16022
  abi: ABI3,
@@ -15779,18 +16045,37 @@ var OrderCanceller = class {
15779
16045
  };
15780
16046
  return;
15781
16047
  }
16048
+ const storageKeys = this.recoveryStorageKeys(order);
16049
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15782
16050
  const hyperbridge = indexerClient.hyperbridge;
15783
- const sourceStateMachine = normalizeStateMachineId(order.source);
15784
16051
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15785
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16052
+ let destIProof = await this.getRecoveryItem(
16053
+ storageKeys.destProof,
16054
+ legacyStorageKeys.map((keys) => keys.destProof)
16055
+ );
15786
16056
  if (!destIProof) {
15787
16057
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15788
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16058
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15789
16059
  } else {
15790
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16060
+ let refreshed = false;
16061
+ try {
16062
+ await this.assertProofFresh(hyperbridge, destIProof);
16063
+ } catch (error) {
16064
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16065
+ await this.removeRecoveryItems(
16066
+ storageKeys.destProof,
16067
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16068
+ );
16069
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16070
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16071
+ refreshed = true;
16072
+ }
16073
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15791
16074
  }
15792
- let getRequest = await this.ctx.cancellationStorage.getItem(
15793
- STORAGE_KEYS.getRequest(orderId)
16075
+ await this.assertProofFresh(hyperbridge, destIProof);
16076
+ let getRequest = await this.getRecoveryItem(
16077
+ storageKeys.getRequest,
16078
+ legacyStorageKeys.map((keys) => keys.getRequest)
15794
16079
  );
15795
16080
  if (!getRequest) {
15796
16081
  const quote = await this.quoteCancelFromSource(order);
@@ -15810,7 +16095,7 @@ var OrderCanceller = class {
15810
16095
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15811
16096
  if (!request) throw new Error("GetRequest missing");
15812
16097
  getRequest = request.args;
15813
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16098
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15814
16099
  yield {
15815
16100
  status: "CANCEL_STARTED",
15816
16101
  receipt
@@ -15829,8 +16114,9 @@ var OrderCanceller = class {
15829
16114
  metadata: statusUpdate.metadata
15830
16115
  };
15831
16116
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15832
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15833
- STORAGE_KEYS.sourceProof(orderId)
16117
+ let sourceIProof = await this.getRecoveryItem(
16118
+ storageKeys.sourceProof,
16119
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15834
16120
  );
15835
16121
  if (!sourceIProof) {
15836
16122
  sourceIProof = await fetchSourceProof(
@@ -15840,8 +16126,9 @@ var OrderCanceller = class {
15840
16126
  sourceConsensusStateId,
15841
16127
  sourceHeight
15842
16128
  );
15843
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16129
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15844
16130
  }
16131
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15845
16132
  await waitForChallengePeriod(hyperbridge, {
15846
16133
  height: sourceIProof.height,
15847
16134
  id: {
@@ -15849,6 +16136,10 @@ var OrderCanceller = class {
15849
16136
  consensusStateId: sourceConsensusStateId
15850
16137
  }
15851
16138
  });
16139
+ await Promise.all([
16140
+ this.assertProofFresh(hyperbridge, sourceIProof),
16141
+ this.assertProofFresh(hyperbridge, destIProof)
16142
+ ]);
15852
16143
  const getRequestMessage = {
15853
16144
  kind: "GetRequest",
15854
16145
  requests: [getRequest],
@@ -15870,9 +16161,12 @@ var OrderCanceller = class {
15870
16161
  status: "HYPERBRIDGE_FINALIZED",
15871
16162
  metadata: statusUpdate.metadata
15872
16163
  };
15873
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.destProof(orderId));
15874
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.getRequest(orderId));
15875
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.sourceProof(orderId));
16164
+ await this.removeRecoveryItems(
16165
+ storageKeys.destProof,
16166
+ storageKeys.getRequest,
16167
+ storageKeys.sourceProof,
16168
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16169
+ );
15876
16170
  return;
15877
16171
  }
15878
16172
  }
@@ -15889,9 +16183,11 @@ var OrderCanceller = class {
15889
16183
  * @returns The native token dispatch fee in wei.
15890
16184
  */
15891
16185
  async quoteCancelFromDest(order) {
15892
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15893
- const destStateMachine = normalizeStateMachineId(order.destination);
15894
16186
  const sourceStateMachine = normalizeStateMachineId(order.source);
16187
+ const destStateMachine = normalizeStateMachineId(order.destination);
16188
+ if (sourceStateMachine === destStateMachine) {
16189
+ return { nativeValue: 0n, relayerFee: 0n };
16190
+ }
15895
16191
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15896
16192
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15897
16193
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -15929,11 +16225,13 @@ var OrderCanceller = class {
15929
16225
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
15930
16226
  */
15931
16227
  async *cancelOrderFromDest(order, indexerClient) {
15932
- const orderId = order.id;
16228
+ const storageKeys = this.recoveryStorageKeys(order);
15933
16229
  const destStateMachine = normalizeStateMachineId(order.destination);
15934
16230
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15935
- let commitment = await this.ctx.cancellationStorage.getItem(
15936
- STORAGE_KEYS.postCommitment(orderId)
16231
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16232
+ let commitment = await this.getRecoveryItem(
16233
+ storageKeys.postCommitment,
16234
+ legacyStorageKeys.map((keys) => keys.postCommitment)
15937
16235
  );
15938
16236
  if (!commitment) {
15939
16237
  const quote = await this.quoteCancelFromDest(order);
@@ -15958,7 +16256,7 @@ var OrderCanceller = class {
15958
16256
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
15959
16257
  const postArgs = postEvent.args;
15960
16258
  commitment = postRequestCommitment(postArgs).commitment;
15961
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16259
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
15962
16260
  }
15963
16261
  const statusStream = indexerClient.postRequestStatusStream(commitment);
15964
16262
  for await (const statusUpdate of statusStream) {
@@ -15992,7 +16290,10 @@ var OrderCanceller = class {
15992
16290
  if (refundEvents.length === 0) {
15993
16291
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
15994
16292
  }
15995
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16293
+ await this.removeRecoveryItems(
16294
+ storageKeys.postCommitment,
16295
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16296
+ );
15996
16297
  yield {
15997
16298
  status: "CANCELLATION_COMPLETE",
15998
16299
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16035,7 +16336,7 @@ var OrderCanceller = class {
16035
16336
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16036
16337
  this.ctx.dest.config.stateMachineId
16037
16338
  );
16038
- const orderId = order.id;
16339
+ const orderId = this.orderId(order);
16039
16340
  const slotHash = await this.ctx.dest.client.readContract({
16040
16341
  abi: ABI3,
16041
16342
  address: intentGatewayV2Address,
@@ -16052,6 +16353,7 @@ var OrderCanceller = class {
16052
16353
  yield { status: "DESTINATION_FINALIZED", proof };
16053
16354
  return proof;
16054
16355
  } catch (e) {
16356
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16055
16357
  lastFailedHeight = latestHeight;
16056
16358
  await sleep(1e4);
16057
16359
  }
@@ -16082,6 +16384,7 @@ var OrderCanceller = class {
16082
16384
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16083
16385
  await sleep(3e4);
16084
16386
  } catch (error) {
16387
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16085
16388
  this.logger.warn(
16086
16389
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16087
16390
  );
@@ -16090,14 +16393,128 @@ var OrderCanceller = class {
16090
16393
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16091
16394
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16092
16395
  } catch (error) {
16396
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16093
16397
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16094
16398
  this.logger.error(message2);
16095
16399
  throw new Error(message2);
16096
16400
  }
16097
16401
  }
16402
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16403
+ async assertProofFresh(hyperbridge, proof) {
16404
+ await retryPromise(
16405
+ () => hyperbridge.stateMachineUpdateTime({
16406
+ height: proof.height,
16407
+ id: {
16408
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16409
+ consensusStateId: proof.consensusStateId
16410
+ }
16411
+ }),
16412
+ {
16413
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16414
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16415
+ logger: this.logger,
16416
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16417
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16418
+ }
16419
+ );
16420
+ }
16421
+ /**
16422
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16423
+ * a consensus update required by that recovery attempt.
16424
+ */
16425
+ async clearGetRecoveryCache(order) {
16426
+ const keys = this.recoveryStorageKeys(order);
16427
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16428
+ await this.removeRecoveryItems(
16429
+ keys.destProof,
16430
+ keys.sourceProof,
16431
+ keys.getRequest,
16432
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16433
+ legacyKeys.destProof,
16434
+ legacyKeys.sourceProof,
16435
+ legacyKeys.getRequest
16436
+ ])
16437
+ );
16438
+ }
16439
+ /**
16440
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16441
+ * for an order.
16442
+ *
16443
+ * @param order - The order whose source/destination recovery state is stored.
16444
+ */
16445
+ recoveryStorageKeys(order) {
16446
+ const orderId = this.orderId(order);
16447
+ return {
16448
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16449
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16450
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16451
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16452
+ };
16453
+ }
16454
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16455
+ legacyRecoveryStorageKeys(order) {
16456
+ const orderId = this.orderId(order);
16457
+ return [
16458
+ {
16459
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16460
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16461
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16462
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16463
+ },
16464
+ {
16465
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16466
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16467
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16468
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16469
+ }
16470
+ ];
16471
+ }
16472
+ async getRecoveryItem(currentKey, legacyKeys) {
16473
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16474
+ if (current) return current;
16475
+ for (const legacyKey of new Set(legacyKeys)) {
16476
+ if (legacyKey === currentKey) continue;
16477
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16478
+ if (!legacy) continue;
16479
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16480
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16481
+ return legacy;
16482
+ }
16483
+ return null;
16484
+ }
16485
+ async removeRecoveryItems(...keys) {
16486
+ await Promise.all(
16487
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16488
+ );
16489
+ }
16490
+ /**
16491
+ * Returns the order identifier required to persist or clear recovery state.
16492
+ *
16493
+ * @param order - The order being cancelled.
16494
+ * @throws If the order has no identifier.
16495
+ */
16496
+ orderId(order) {
16497
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16498
+ return order.id;
16499
+ }
16500
+ /**
16501
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16502
+ *
16503
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16504
+ * @param commitment - Commitment of the GET request.
16505
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16506
+ */
16098
16507
  async queryDeliveredReceipt(hyperbridge, commitment) {
16099
16508
  return hyperbridge.queryResponseReceipt(commitment);
16100
16509
  }
16510
+ /**
16511
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16512
+ *
16513
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16514
+ * @param commitment - Commitment of the GET request.
16515
+ * @returns The delivered response receipt commitment.
16516
+ * @throws If no receipt is observed within the configured retry limit.
16517
+ */
16101
16518
  async pollDeliveredReceipt(hyperbridge, commitment) {
16102
16519
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16103
16520
  return retryPromise(
@@ -17310,6 +17727,178 @@ var OrderStatusChecker = class {
17310
17727
  return true;
17311
17728
  }
17312
17729
  };
17730
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17731
+ var LiquidityEngine = class {
17732
+ /**
17733
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17734
+ * @param chainConfigService - Resolves token decimals for formatted results.
17735
+ */
17736
+ constructor(queryClient, chainConfigService) {
17737
+ this.queryClient = queryClient;
17738
+ this.chainConfigService = chainConfigService;
17739
+ }
17740
+ queryClient;
17741
+ chainConfigService;
17742
+ /**
17743
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17744
+ * indexed output-token liquidity.
17745
+ *
17746
+ * Nexus filters balances to the canonical output token, then aggregates them
17747
+ * overall and by chain. The returned amounts are decimal strings: the total
17748
+ * uses the canonical Base output-token decimals, while each chain group uses
17749
+ * that chain's token decimals. They describe `snapshotTime`, not live
17750
+ * reservations or fill guarantees.
17751
+ *
17752
+ * @param params - Canonical Phantom-market input and output token addresses.
17753
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17754
+ * the directional pair.
17755
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17756
+ * or internally inconsistent snapshot data.
17757
+ */
17758
+ async getAvailableLiquiditySnapshot(params) {
17759
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17760
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17761
+ const response = await this.queryClient.request(
17762
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17763
+ { tokenA: tokenIn, tokenB: tokenOut }
17764
+ );
17765
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17766
+ if (!node) return;
17767
+ const commitment = node.commitment.toLowerCase();
17768
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17769
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17770
+ }
17771
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17772
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17773
+ }
17774
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17775
+ if (Number.isNaN(snapshotTime.getTime())) {
17776
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17777
+ }
17778
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17779
+ commitment,
17780
+ tokenAddress: tokenOut
17781
+ });
17782
+ return {
17783
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17784
+ providerCount,
17785
+ tokenAddress: tokenOut,
17786
+ snapshotTime,
17787
+ liquidityByChain: liquidityByChain.map((group) => ({
17788
+ ...group,
17789
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17790
+ }))
17791
+ };
17792
+ }
17793
+ /**
17794
+ * Requests server-side sums and distinct provider counts for one immutable
17795
+ * snapshot/output-token pair, including chain-level aggregate groups.
17796
+ *
17797
+ * `commitment` uniquely identifies the selected snapshot
17798
+ */
17799
+ async querySnapshotLiquidityAggregates(params) {
17800
+ const response = await this.queryClient.request(
17801
+ LIQUIDITY_PROVIDER_BALANCES,
17802
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17803
+ );
17804
+ const connection = response?.liquidityProviderBalances;
17805
+ const aggregates = connection?.aggregates;
17806
+ if (!connection || !aggregates) {
17807
+ throw new InvalidAvailableLiquiditySnapshotError(
17808
+ params.commitment,
17809
+ "liquidityProviderBalances aggregates are missing"
17810
+ );
17811
+ }
17812
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17813
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17814
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17815
+ const [chain, tokenAddress] = group.keys;
17816
+ if (!chain?.trim() || !tokenAddress) {
17817
+ throw new InvalidAvailableLiquiditySnapshotError(
17818
+ params.commitment,
17819
+ `liquidity group ${index} has invalid keys`
17820
+ );
17821
+ }
17822
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17823
+ tokenAddress,
17824
+ params.commitment,
17825
+ `liquidity group ${index} tokenAddress`
17826
+ );
17827
+ if (normalizedTokenAddress !== params.tokenAddress) {
17828
+ throw new InvalidAvailableLiquiditySnapshotError(
17829
+ params.commitment,
17830
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17831
+ );
17832
+ }
17833
+ return {
17834
+ chain: chain.trim(),
17835
+ tokenAddress: normalizedTokenAddress,
17836
+ totalLiquidity: parseSnapshotBigInt(
17837
+ group.sum.balance ?? "0",
17838
+ params.commitment,
17839
+ `liquidity group ${index} balance`
17840
+ ),
17841
+ providerCount: parseProviderCount(
17842
+ group.distinctCount.providerId,
17843
+ params.commitment,
17844
+ `liquidity group ${index}`
17845
+ )
17846
+ };
17847
+ });
17848
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17849
+ if (groupedTotal !== totalLiquidity) {
17850
+ throw new InvalidAvailableLiquiditySnapshotError(
17851
+ params.commitment,
17852
+ "grouped liquidity does not match the total liquidity"
17853
+ );
17854
+ }
17855
+ return { totalLiquidity, providerCount, liquidityByChain };
17856
+ }
17857
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17858
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17859
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17860
+ if (decimals === void 0) {
17861
+ throw new InvalidAvailableLiquiditySnapshotError(
17862
+ commitment,
17863
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17864
+ );
17865
+ }
17866
+ return viem.formatUnits(amount, decimals);
17867
+ }
17868
+ };
17869
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17870
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17871
+ constructor(commitment, reason) {
17872
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17873
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17874
+ }
17875
+ };
17876
+ function parseSnapshotBigInt(value, commitment, field) {
17877
+ try {
17878
+ const amount = BigInt(value);
17879
+ if (amount < 0n) {
17880
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17881
+ }
17882
+ return amount;
17883
+ } catch (error) {
17884
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17885
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17886
+ }
17887
+ }
17888
+ function parseProviderCount(value, commitment, field) {
17889
+ const count = Number(value);
17890
+ if (!Number.isSafeInteger(count) || count < 0) {
17891
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17892
+ }
17893
+ return count;
17894
+ }
17895
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17896
+ try {
17897
+ return normalizeEvmAddress(address, field);
17898
+ } catch {
17899
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17900
+ }
17901
+ }
17313
17902
 
17314
17903
  // src/protocols/intents/quote/types.ts
17315
17904
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17321,15 +17910,67 @@ var UnsupportedIntentQuoteStrategyError = class extends Error {
17321
17910
  var UnsupportedIntentQuotePairError = class extends Error {
17322
17911
  constructor(params) {
17323
17912
  super(
17324
- `No Uniswap v4 pool config found for ${params.tokenIn.symbol ?? params.tokenIn.address} -> ${params.tokenOut.symbol ?? params.tokenOut.address} on ${params.source} -> ${params.destination}`
17913
+ `No ${params.quoteSource ?? "Uniswap v4 pool config"} found for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}`
17325
17914
  );
17326
17915
  this.name = "UnsupportedIntentQuotePairError";
17327
17916
  }
17328
17917
  };
17918
+ var PhantomSnapshotUnavailableError = class extends Error {
17919
+ constructor(tokenA, tokenB) {
17920
+ super(`No Phantom order price snapshot found for ${tokenA} -> ${tokenB}`);
17921
+ this.name = "PhantomSnapshotUnavailableError";
17922
+ }
17923
+ };
17924
+ var InvalidPhantomSnapshotError = class extends Error {
17925
+ constructor(commitment, reason) {
17926
+ super(`Invalid Phantom order price snapshot ${commitment}: ${reason}`);
17927
+ this.name = "InvalidPhantomSnapshotError";
17928
+ }
17929
+ };
17930
+ var BPS_DENOMINATOR = 10000n;
17931
+ function validateQuoteParams(params) {
17932
+ const hasAmountIn = params.amountIn !== void 0;
17933
+ const hasAmountOut = params.amountOut !== void 0;
17934
+ if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
17935
+ if (params.amountIn !== void 0 && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
17936
+ if (params.amountOut !== void 0 && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
17937
+ if (params.tokenIn.toLowerCase() === params.tokenOut.toLowerCase()) {
17938
+ throw new Error("tokenIn and tokenOut cannot be the same");
17939
+ }
17940
+ }
17941
+ async function readProtocolFeeBps(chainConfigService, source) {
17942
+ const gatewayAddress = chainConfigService.getIntentGatewayAddress(source.stateMachineId);
17943
+ if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === viem.zeroAddress) {
17944
+ throw new Error(`IntentGatewayV2 is not configured for chain ${source.stateMachineId}`);
17945
+ }
17946
+ const gatewayParams = await source.client.readContract({
17947
+ address: gatewayAddress,
17948
+ abi: IntentGatewayV2_default.ABI,
17949
+ functionName: "params"
17950
+ });
17951
+ const protocolFeeBps = Array.isArray(gatewayParams) ? BigInt(gatewayParams[4]) : BigInt(gatewayParams.protocolFeeBps ?? 0);
17952
+ if (protocolFeeBps < 0n || protocolFeeBps >= BPS_DENOMINATOR) {
17953
+ throw new Error(`Invalid IntentGateway protocol fee: ${protocolFeeBps} bps`);
17954
+ }
17955
+ return protocolFeeBps;
17956
+ }
17957
+ function deductProtocolFee(amount, protocolFeeBps) {
17958
+ if (protocolFeeBps <= 0n) return amount;
17959
+ const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
17960
+ return amount - fee;
17961
+ }
17962
+ function grossUpForProtocolFee(netAmount, protocolFeeBps) {
17963
+ if (protocolFeeBps <= 0n) return netAmount;
17964
+ if (protocolFeeBps >= BPS_DENOMINATOR) throw new Error("protocolFeeBps must be less than 10,000");
17965
+ return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
17966
+ }
17967
+ function divCeil(numerator, denominator) {
17968
+ if (denominator <= 0n) throw new Error("denominator must be greater than zero");
17969
+ return (numerator + denominator - 1n) / denominator;
17970
+ }
17329
17971
 
17330
17972
  // src/protocols/intents/quote/uniswapV4.ts
17331
17973
  var UNISWAP_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
17332
- var BPS_DENOMINATOR = 10000n;
17333
17974
  var UniswapV4IntentQuoteStrategy = class {
17334
17975
  constructor(chainConfigService) {
17335
17976
  this.chainConfigService = chainConfigService;
@@ -17337,10 +17978,10 @@ var UniswapV4IntentQuoteStrategy = class {
17337
17978
  chainConfigService;
17338
17979
  baseQuoteClient;
17339
17980
  async quote(params, source, destination) {
17340
- this.validateQuoteParams(params);
17341
- const protocolFeeBps = await this.readProtocolFeeBps(source.client, source.stateMachineId);
17981
+ validateQuoteParams(params);
17982
+ const protocolFeeBps = await readProtocolFeeBps(this.chainConfigService, source);
17342
17983
  const quoteClient = this.resolveQuoteClient(source, destination);
17343
- const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, UNISWAP_INTENT_QUOTE_CHAIN);
17984
+ const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, destination.stateMachineId);
17344
17985
  return params.amountIn !== void 0 ? this.quoteExactInput({ params, client: quoteClient, protocolFeeBps, poolConfig }) : this.quoteExactOutput({ params, client: quoteClient, protocolFeeBps, poolConfig });
17345
17986
  }
17346
17987
  resolveQuoteClient(source, destination) {
@@ -17356,34 +17997,11 @@ var UniswapV4IntentQuoteStrategy = class {
17356
17997
  this.baseQuoteClient = baseQuoteClient;
17357
17998
  return baseQuoteClient;
17358
17999
  }
17359
- validateQuoteParams(params) {
17360
- const hasAmountIn = params.amountIn !== void 0;
17361
- const hasAmountOut = params.amountOut !== void 0;
17362
- if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
17363
- if (hasAmountIn && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
17364
- if (hasAmountOut && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
17365
- if (params.tokenIn.address.toLowerCase() === params.tokenOut.address.toLowerCase()) {
17366
- throw new Error("tokenIn and tokenOut cannot be the same");
17367
- }
17368
- }
17369
- async readProtocolFeeBps(client, chain) {
17370
- const gatewayAddress = this.chainConfigService.getIntentGatewayAddress(chain);
17371
- if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === viem.zeroAddress) {
17372
- throw new Error(`IntentGatewayV2 is not configured for chain ${chain}`);
17373
- }
17374
- const gatewayParams = await client.readContract({
17375
- address: gatewayAddress,
17376
- abi: IntentGatewayV2_default.ABI,
17377
- functionName: "params"
17378
- });
17379
- if (isGatewayParamsTuple(gatewayParams)) return BigInt(gatewayParams[4]);
17380
- return BigInt(gatewayParams.protocolFeeBps ?? 0);
17381
- }
17382
18000
  resolvePoolConfig(params, source, destination) {
17383
18001
  const override = params.uniswapV4?.poolKey;
17384
18002
  if (override) {
17385
18003
  const poolKey = normalizePoolKey(override);
17386
- const tokenInForQuote = viem.getAddress(override.currencyIn ?? params.tokenIn.address);
18004
+ const tokenInForQuote = viem.getAddress(override.currencyIn ?? params.tokenIn);
17387
18005
  if (tokenInForQuote !== poolKey.currency0 && tokenInForQuote !== poolKey.currency1) {
17388
18006
  throw new Error(
17389
18007
  `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.`
@@ -17391,11 +18009,11 @@ var UniswapV4IntentQuoteStrategy = class {
17391
18009
  }
17392
18010
  return {
17393
18011
  poolKey,
17394
- quoterAddress: this.resolveQuoterAddress(destination, override.quoterAddress),
18012
+ quoterAddress: this.resolveQuoterAddress(UNISWAP_INTENT_QUOTE_CHAIN, override.quoterAddress),
17395
18013
  tokenInForQuote
17396
18014
  };
17397
18015
  }
17398
- const poolConfig = this.resolveConfiguredPool(params, destination);
18016
+ const poolConfig = this.resolveConfiguredPool(params, source, destination, UNISWAP_INTENT_QUOTE_CHAIN);
17399
18017
  if (poolConfig) return poolConfig;
17400
18018
  throw new UnsupportedIntentQuotePairError({
17401
18019
  source,
@@ -17404,12 +18022,12 @@ var UniswapV4IntentQuoteStrategy = class {
17404
18022
  tokenOut: params.tokenOut
17405
18023
  });
17406
18024
  }
17407
- resolveConfiguredPool(params, chain) {
17408
- for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(chain)) {
17409
- const resolvedPool = this.resolveConfiguredPoolTokens(chain, pool);
18025
+ resolveConfiguredPool(params, sourceChain, destinationChain, quoteChain) {
18026
+ for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(quoteChain)) {
18027
+ const resolvedPool = this.resolveConfiguredPoolTokens(quoteChain, pool);
17410
18028
  if (!resolvedPool) continue;
17411
- const tokenInForQuote = matchPoolToken(params.tokenIn, resolvedPool);
17412
- const tokenOutForQuote = matchPoolToken(params.tokenOut, resolvedPool);
18029
+ const tokenInForQuote = this.matchPoolToken(params.tokenIn, sourceChain, resolvedPool);
18030
+ const tokenOutForQuote = this.matchPoolToken(params.tokenOut, destinationChain, resolvedPool);
17413
18031
  if (!tokenInForQuote || !tokenOutForQuote || tokenInForQuote.symbol === tokenOutForQuote.symbol) continue;
17414
18032
  const { currency0, currency1 } = sortCurrencies(resolvedPool[0].address, resolvedPool[1].address);
17415
18033
  return {
@@ -17420,7 +18038,7 @@ var UniswapV4IntentQuoteStrategy = class {
17420
18038
  tickSpacing: pool.tickSpacing,
17421
18039
  hooks: viem.getAddress(pool.hooks ?? viem.zeroAddress)
17422
18040
  },
17423
- quoterAddress: this.resolveQuoterAddress(chain),
18041
+ quoterAddress: this.resolveQuoterAddress(quoteChain),
17424
18042
  tokenInForQuote: tokenInForQuote.address
17425
18043
  };
17426
18044
  }
@@ -17443,8 +18061,18 @@ var UniswapV4IntentQuoteStrategy = class {
17443
18061
  if (!address || address === "0x") return null;
17444
18062
  return { symbol, address: viem.getAddress(address) };
17445
18063
  }
18064
+ matchPoolToken(tokenAddress, orderChain, poolTokens) {
18065
+ const addressMatch = poolTokens.find(
18066
+ (poolToken) => poolToken.address.toLowerCase() === tokenAddress.toLowerCase()
18067
+ );
18068
+ if (addressMatch) return addressMatch;
18069
+ const metadata = this.chainConfigService.getAssetMetadataByAddress(orderChain, tokenAddress);
18070
+ if (!metadata) return null;
18071
+ return poolTokens.find((poolToken) => poolToken.symbol === metadata.symbol) ?? null;
18072
+ }
17446
18073
  async quoteExactInput(args) {
17447
18074
  const amountIn = args.params.amountIn;
18075
+ if (amountIn === void 0) throw new Error("amountIn is required for an exact-input quote");
17448
18076
  const swapAmountIn = deductProtocolFee(amountIn, args.protocolFeeBps);
17449
18077
  const amountOut = await this.readV4QuoteExactInput(args.client, args.poolConfig, swapAmountIn);
17450
18078
  return {
@@ -17462,6 +18090,7 @@ var UniswapV4IntentQuoteStrategy = class {
17462
18090
  }
17463
18091
  async quoteExactOutput(args) {
17464
18092
  const amountOut = args.params.amountOut;
18093
+ if (amountOut === void 0) throw new Error("amountOut is required for an exact-output quote");
17465
18094
  const swapAmountIn = await this.readV4QuoteExactOutput(args.client, args.poolConfig, amountOut);
17466
18095
  const amountIn = grossUpForProtocolFee(swapAmountIn, args.protocolFeeBps);
17467
18096
  return {
@@ -17540,34 +18169,133 @@ function sortCurrencies(tokenIn, tokenOut) {
17540
18169
  const output = viem.getAddress(tokenOut);
17541
18170
  return BigInt(input) < BigInt(output) ? { currency0: input, currency1: output } : { currency0: output, currency1: input };
17542
18171
  }
17543
- function matchPoolToken(token, poolTokens) {
17544
- const tokenAddress = token.address.toLowerCase();
17545
- const addressMatch = poolTokens.find((poolToken) => poolToken.address.toLowerCase() === tokenAddress);
17546
- if (addressMatch) return addressMatch;
17547
- const tokenSymbol = token.symbol?.toUpperCase();
17548
- if (!tokenSymbol) return null;
17549
- return poolTokens.find((poolToken) => poolToken.symbol.toUpperCase() === tokenSymbol) ?? null;
17550
- }
17551
18172
  function getZeroForOne(tokenIn, poolKey) {
17552
18173
  return viem.getAddress(tokenIn).toLowerCase() === viem.getAddress(poolKey.currency0).toLowerCase();
17553
18174
  }
17554
- function isGatewayParamsTuple(value) {
17555
- return Array.isArray(value);
17556
- }
17557
- function deductProtocolFee(amount, protocolFeeBps) {
17558
- if (protocolFeeBps <= 0n) return amount;
17559
- const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
17560
- return amount - fee;
17561
- }
17562
- function grossUpForProtocolFee(netAmount, protocolFeeBps) {
17563
- if (protocolFeeBps <= 0n) return netAmount;
17564
- return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
18175
+
18176
+ // src/protocols/intents/quote/phantomSnapshot.ts
18177
+ var PHANTOM_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
18178
+ var BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET = {
18179
+ USDC: "USDC",
18180
+ USDT: "USDC",
18181
+ cNGN: "cNGN"
18182
+ };
18183
+ var SUPPORTED_BASE_SNAPSHOT_PAIRS = /* @__PURE__ */ new Set(["USDC:cNGN", "cNGN:USDC"]);
18184
+ var PhantomSnapshotIntentQuoteStrategy = class {
18185
+ constructor(chainConfigService, getQueryClient) {
18186
+ this.chainConfigService = chainConfigService;
18187
+ this.getQueryClient = getQueryClient;
18188
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
18189
+ }
18190
+ chainConfigService;
18191
+ getQueryClient;
18192
+ pairResolver;
18193
+ async quote(params, source, destination) {
18194
+ validateQuoteParams(params);
18195
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
18196
+ if (!pair) {
18197
+ throw new UnsupportedIntentQuotePairError({
18198
+ source: source.stateMachineId,
18199
+ destination: destination.stateMachineId,
18200
+ tokenIn: params.tokenIn,
18201
+ tokenOut: params.tokenOut,
18202
+ quoteSource: "Phantom snapshot pair"
18203
+ });
18204
+ }
18205
+ const queryClient = this.getQueryClient();
18206
+ const [protocolFeeBps, snapshot] = await Promise.all([
18207
+ readProtocolFeeBps(this.chainConfigService, source),
18208
+ _queryLatestPhantomOrderPriceSnapshot({
18209
+ tokenA: pair.tokenA,
18210
+ tokenB: pair.tokenB,
18211
+ queryClient
18212
+ })
18213
+ ]);
18214
+ if (!snapshot) throw new PhantomSnapshotUnavailableError(pair.tokenA, pair.tokenB);
18215
+ this.validateSnapshot(snapshot);
18216
+ return this.quoteSnapshot(params, protocolFeeBps, snapshot);
18217
+ }
18218
+ quoteSnapshot(params, protocolFeeBps, snapshot) {
18219
+ if (params.amountIn !== void 0) {
18220
+ const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
18221
+ const amountOut = netAmountIn2 * snapshot.medianPrice / snapshot.standardAmount;
18222
+ if (amountOut <= 0n) {
18223
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "quote rounds down to zero output");
18224
+ }
18225
+ return this.buildResult("EXACT_INPUT", params.amountIn, amountOut, protocolFeeBps, snapshot);
18226
+ }
18227
+ if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
18228
+ const netAmountIn = divCeil(params.amountOut * snapshot.standardAmount, snapshot.medianPrice);
18229
+ const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
18230
+ return this.buildResult("EXACT_OUTPUT", amountIn, params.amountOut, protocolFeeBps, snapshot);
18231
+ }
18232
+ buildResult(tradeType, amountIn, amountOut, protocolFeeBps, snapshot) {
18233
+ return {
18234
+ strategy: "phantom_snapshot",
18235
+ tradeType,
18236
+ amountIn,
18237
+ amountOut,
18238
+ quoteMetadata: {
18239
+ quoteChain: PHANTOM_INTENT_QUOTE_CHAIN,
18240
+ commitment: snapshot.commitment,
18241
+ tokenA: snapshot.tokenA,
18242
+ tokenB: snapshot.tokenB,
18243
+ standardAmount: snapshot.standardAmount,
18244
+ medianPrice: snapshot.medianPrice,
18245
+ lowestPrice: snapshot.lowestPrice,
18246
+ highestPrice: snapshot.highestPrice,
18247
+ blockNumber: snapshot.blockNumber,
18248
+ snapshotTime: snapshot.snapshotTime,
18249
+ bidCount: snapshot.bidCount,
18250
+ protocolFeeBps
18251
+ }
18252
+ };
18253
+ }
18254
+ validateSnapshot(snapshot) {
18255
+ if (snapshot.standardAmount <= 0n) {
18256
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "standardAmount must be greater than zero");
18257
+ }
18258
+ if (snapshot.medianPrice <= 0n) {
18259
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "medianPrice must be greater than zero");
18260
+ }
18261
+ if (snapshot.bidCount <= 0) {
18262
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "bidCount must be greater than zero");
18263
+ }
18264
+ if (Number.isNaN(snapshot.snapshotTime.getTime())) {
18265
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
18266
+ }
18267
+ }
18268
+ };
18269
+ var PhantomSnapshotPairResolver = class {
18270
+ constructor(chainConfigService) {
18271
+ this.chainConfigService = chainConfigService;
18272
+ }
18273
+ chainConfigService;
18274
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
18275
+ const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
18276
+ const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
18277
+ if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
18278
+ return { tokenA: tokenIn.address, tokenB: tokenOut.address };
18279
+ }
18280
+ resolveBaseSnapshotToken(chain, tokenAddress) {
18281
+ const orderAsset = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.symbol;
18282
+ if (!orderAsset) return;
18283
+ const snapshotAsset = BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET[orderAsset];
18284
+ if (!snapshotAsset) return;
18285
+ const address = this.chainConfigService.getAssetAddress(PHANTOM_INTENT_QUOTE_CHAIN, snapshotAsset);
18286
+ if (!isConfiguredAddress(address)) return;
18287
+ return { symbol: snapshotAsset, address };
18288
+ }
18289
+ };
18290
+ function isSupportedSnapshotPair(tokenA, tokenB) {
18291
+ return SUPPORTED_BASE_SNAPSHOT_PAIRS.has(`${tokenA}:${tokenB}`);
17565
18292
  }
17566
- function divCeil(numerator, denominator) {
17567
- return (numerator + denominator - 1n) / denominator;
18293
+ function isConfiguredAddress(address) {
18294
+ return Boolean(address && address !== "0x" && !/^0x0{40}$/i.test(address));
17568
18295
  }
17569
18296
 
17570
18297
  // src/protocols/intents/IntentGateway.ts
18298
+ var CROSS_CHAIN_ORDER_FEE_GAS_BUMP = 600000n;
17571
18299
  var IntentGateway = class _IntentGateway {
17572
18300
  /** EVM chain on which orders are placed and escrowed. */
17573
18301
  source;
@@ -17595,6 +18323,8 @@ var IntentGateway = class _IntentGateway {
17595
18323
  gasEstimator;
17596
18324
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17597
18325
  quoteStrategies;
18326
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18327
+ phantomSnapshotPairResolver;
17598
18328
  /**
17599
18329
  * Private constructor — use {@link IntentGateway.create} instead.
17600
18330
  *
@@ -17638,7 +18368,12 @@ var IntentGateway = class _IntentGateway {
17638
18368
  this.bidManager = bidManager;
17639
18369
  this.gasEstimator = gasEstimator;
17640
18370
  this._crypto = crypto;
18371
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17641
18372
  this.quoteStrategies = {
18373
+ phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18374
+ dest.configService,
18375
+ () => this.requireIndexer().queryClient
18376
+ ),
17642
18377
  uniswap_v4: new UniswapV4IntentQuoteStrategy(dest.configService)
17643
18378
  };
17644
18379
  }
@@ -17688,37 +18423,69 @@ var IntentGateway = class _IntentGateway {
17688
18423
  /**
17689
18424
  * Quotes an intent between this gateway's source and destination chains.
17690
18425
  *
17691
- * `strategy` defaults to `uniswap_v4`, currently the only supported
17692
- * strategy. Provide exactly one of `amountIn` or `amountOut`.
18426
+ * Uses the latest directional Phantom order price snapshot from the attached
18427
+ * indexer by default. Pass `strategy: "uniswap_v4"` only when explicitly
18428
+ * requesting a Uniswap quote. Provide exactly one of `amountIn` or `amountOut`.
17693
18429
  *
17694
- * The Uniswap quote strategy always prices against the configured Base
17695
- * pool, regardless of this gateway's destination chain. Returned
18430
+ * Both built-in strategies resolve their canonical market on Base,
18431
+ * regardless of this gateway's destination chain. Returned
17696
18432
  * `amountIn`/`amountOut` already account for the gateway's protocol fee
17697
18433
  * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
17698
- * inputs; apply only your own slippage tolerance before placing the order.
18434
+ * inputs; use the returned amounts directly when placing the order.
17699
18435
  *
17700
18436
  * @param params - Token pair, amount, and optional strategy/pool overrides.
17701
18437
  * @returns The quoted amounts plus strategy-specific metadata.
17702
18438
  * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
17703
- * @throws {UnsupportedIntentQuotePairError} When no pool is configured for the pair.
18439
+ * @throws {UnsupportedIntentQuotePairError} When the selected strategy does not support the pair.
18440
+ * @throws {PhantomSnapshotUnavailableError} When an eligible cNGN pair has no snapshot.
17704
18441
  */
17705
18442
  async quoteIntent(params) {
17706
- const strategy = params.strategy ?? "uniswap_v4";
18443
+ const source = { stateMachineId: this.source.config.stateMachineId, client: this.source.client };
18444
+ const destination = { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client };
18445
+ const strategy = params.strategy ?? "phantom_snapshot";
17707
18446
  const handler = this.quoteStrategies[strategy];
17708
18447
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17709
- return handler.quote(
17710
- { ...params, strategy },
17711
- { stateMachineId: this.source.config.stateMachineId, client: this.source.client },
17712
- { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client }
17713
- );
18448
+ return handler.quote({ ...params, strategy }, source, destination);
18449
+ }
18450
+ /**
18451
+ * Returns the output-token liquidity measured in the latest directional
18452
+ * Phantom snapshot for this gateway's source and destination.
18453
+ *
18454
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18455
+ * The snapshot itself determines the output token and chain to aggregate. The
18456
+ * amount is in the token's smallest unit and reflects the indexer's
18457
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18458
+ *
18459
+ * Requires a prior call to {@link withQueryClient}.
18460
+ */
18461
+ async queryAvailableLiquidity(params) {
18462
+ const { queryClient } = this.requireIndexer();
18463
+ const sourceStateMachineId = this.source.config.stateMachineId;
18464
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18465
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18466
+ if (!pair) {
18467
+ throw new UnsupportedIntentQuotePairError({
18468
+ source: sourceStateMachineId,
18469
+ destination: destinationStateMachineId,
18470
+ tokenIn: params.tokenIn,
18471
+ tokenOut: params.tokenOut,
18472
+ quoteSource: "Phantom snapshot pair"
18473
+ });
18474
+ }
18475
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18476
+ tokenIn: pair.tokenA,
18477
+ tokenOut: pair.tokenB
18478
+ });
17714
18479
  }
17715
18480
  /**
17716
18481
  * Bidirectional async generator that orchestrates the full order lifecycle:
17717
18482
  * placement, fee estimation, bid collection, and execution.
17718
18483
  *
17719
18484
  * **Yield/receive protocol:**
17720
- * 1. If `order.fees` is unset or zero, estimates gas and sets `order.fees`
17721
- * with a 1% buffer and the wei cost with a 2% buffer for the `value` field.
18485
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and sets
18486
+ * same-chain fees to twice the estimate. Cross-chain orders retain a 1% buffer
18487
+ * and include an additional 600k-gas fee uplift, while the wei cost used for
18488
+ * the `value` field receives a 2% buffer.
17722
18489
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
17723
18490
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
17724
18491
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
@@ -17726,8 +18493,8 @@ var IntentGateway = class _IntentGateway {
17726
18493
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17727
18494
  * subsequent status updates until the order is filled, exhausted, or fails.
17728
18495
  *
17729
- * @param order - The order to place and execute. `order.fees` may be 0; it
17730
- * will be estimated automatically if so.
18496
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18497
+ * may be 0; fees are estimated automatically if so.
17731
18498
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17732
18499
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17733
18500
  * @param options - Optional tuning parameters:
@@ -17740,20 +18507,28 @@ var IntentGateway = class _IntentGateway {
17740
18507
  * estimation returns zero.
17741
18508
  */
17742
18509
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18510
+ const executionOrder = { ...order };
17743
18511
  let value;
17744
- if (!order.fees || order.fees === 0n) {
18512
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17745
18513
  const estimate = await this.gasEstimator.estimateFillOrder({
17746
- order,
18514
+ order: executionOrder,
17747
18515
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
17748
18516
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
17749
18517
  });
17750
18518
  if (estimate.totalGasCostWei === 0n || estimate.totalGasInFeeToken === 0n) {
17751
18519
  throw new Error("Gas estimation failed");
17752
18520
  }
18521
+ const isSameChain = this.source.config.stateMachineId === this.dest.config.stateMachineId;
17753
18522
  value = estimate.totalGasCostWei + estimate.totalGasCostWei * 2n / 100n;
17754
- order.fees = estimate.totalGasInFeeToken + estimate.totalGasInFeeToken * 1n / 100n;
18523
+ const crossChainFeeBump = isSameChain ? 0n : await convertGasToFeeToken(
18524
+ this.ctx,
18525
+ CROSS_CHAIN_ORDER_FEE_GAS_BUMP,
18526
+ "source",
18527
+ this.source.config.stateMachineId
18528
+ );
18529
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
17755
18530
  }
17756
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18531
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
17757
18532
  const placeOrderFirst = await placeOrderGen.next();
17758
18533
  if (placeOrderFirst.done) {
17759
18534
  throw new Error("placeOrder generator completed without yielding");
@@ -17864,13 +18639,20 @@ var IntentGateway = class _IntentGateway {
17864
18639
  const gen = this.execute(order, graffiti, options);
17865
18640
  try {
17866
18641
  let input;
18642
+ let finalizedOrder;
17867
18643
  while (true) {
17868
18644
  const { value, done } = await gen.next(input);
17869
18645
  input = void 0;
17870
18646
  if (done) break;
17871
- if (value.status === "BIDS_RECEIVED") {
18647
+ if (value.status === "ORDER_PLACED") {
18648
+ finalizedOrder = value.order;
17872
18649
  yield value;
17873
- input = await this.autoSelect(order, value.bids);
18650
+ } else if (value.status === "BIDS_RECEIVED") {
18651
+ if (!finalizedOrder) {
18652
+ throw new Error("Received bids before the order was finalized");
18653
+ }
18654
+ yield value;
18655
+ input = await this.autoSelect(finalizedOrder, value.bids);
17874
18656
  } else if (value.status === "AWAITING_PLACE_ORDER") {
17875
18657
  input = yield value;
17876
18658
  } else {
@@ -18132,7 +18914,9 @@ var IntentGateway = class _IntentGateway {
18132
18914
  }
18133
18915
  requireIndexer() {
18134
18916
  if (!this.indexer) {
18135
- throw new Error("IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods");
18917
+ throw new Error(
18918
+ "IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods or Phantom quotes"
18919
+ );
18136
18920
  }
18137
18921
  return this.indexer;
18138
18922
  }
@@ -22667,6 +23451,7 @@ exports.IntentGateway = IntentGateway;
22667
23451
  exports.IntentGatewayABI = ABI3;
22668
23452
  exports.IntentOrderStatus = IntentOrderStatus;
22669
23453
  exports.IntentsCoprocessor = IntentsCoprocessor;
23454
+ exports.InvalidPhantomSnapshotError = InvalidPhantomSnapshotError;
22670
23455
  exports.IsmpClient = IsmpClient;
22671
23456
  exports.MOCK_ADDRESS = MOCK_ADDRESS;
22672
23457
  exports.ORDER_V2_PARAM_TYPE = ORDER_V2_PARAM_TYPE;
@@ -22674,6 +23459,7 @@ exports.OrderStatus = OrderStatus;
22674
23459
  exports.OrderStatusChecker = OrderStatusChecker;
22675
23460
  exports.PACKED_USEROP_TYPEHASH = PACKED_USEROP_TYPEHASH;
22676
23461
  exports.PLACE_ORDER_SELECTOR = PLACE_ORDER_SELECTOR;
23462
+ exports.PhantomSnapshotUnavailableError = PhantomSnapshotUnavailableError;
22677
23463
  exports.PharosChain = PharosChain;
22678
23464
  exports.PolkadotHubChain = PolkadotHubChain;
22679
23465
  exports.REQUEST_COMMITMENTS_SLOT = REQUEST_COMMITMENTS_SLOT;
@@ -22743,6 +23529,7 @@ exports.hyperbridgeAddress = hyperbridgeAddress;
22743
23529
  exports.maxBigInt = maxBigInt;
22744
23530
  exports.normalizeAddressForEvmBytes32 = normalizeAddressForEvmBytes32;
22745
23531
  exports.normalizeAddressForStateMachine = normalizeAddressForStateMachine;
23532
+ exports.normalizeEvmAddress = normalizeEvmAddress;
22746
23533
  exports.normalizeEvmChainId = normalizeEvmChainId;
22747
23534
  exports.normalizeStateMachineId = normalizeStateMachineId;
22748
23535
  exports.orderCommitment = orderCommitment;