@hyperbridge/sdk 2.4.2 → 2.6.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.
@@ -2,7 +2,7 @@ import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { TextDecoder as TextDecoder$1, TextEncoder as TextEncoder$1 } from 'util';
4
4
  import { createConsola, LogLevels } from 'consola';
5
- import { defineChain, keccak256, toHex, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, decodeAbiParameters, getAddress, encodeAbiParameters, isHex, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, zeroAddress, decodeFunctionResult, formatUnits } from 'viem';
5
+ import { defineChain, keccak256, toHex, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, decodeAbiParameters, getAddress, encodeAbiParameters, isHex, isAddress, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, zeroAddress, decodeFunctionResult, formatUnits } from 'viem';
6
6
  import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimism, polygonAmoy, polygon, base, arbitrum, bsc, mainnet, sepolia, gnosisChiado, bscTestnet, tron, unichain } from 'viem/chains';
7
7
  import { TronWeb } from 'tronweb';
8
8
  import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
@@ -2678,7 +2678,11 @@ var chainConfigs = {
2678
2678
  DAI: "0x6b175474e89094c44da98b954eedeac495271d0f",
2679
2679
  USDC: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
2680
2680
  USDT: "0xdac17f958d2ee523a2206206994597c13d831ec7",
2681
- cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab"
2681
+ cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab",
2682
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2683
+ EURC: "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
2684
+ XSGD: "0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96",
2685
+ TRYB: "0x2C537E5624e4af88A7ae4060C022609376C8D0EB"
2682
2686
  },
2683
2687
  tokenDecimals: {
2684
2688
  USDC: 6,
@@ -2709,6 +2713,7 @@ var chainConfigs = {
2709
2713
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2710
2714
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2711
2715
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2716
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2712
2717
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2713
2718
  },
2714
2719
  rpcEnvKey: "ETH_MAINNET",
@@ -2762,7 +2767,8 @@ var chainConfigs = {
2762
2767
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2763
2768
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2764
2769
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2765
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2770
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2771
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2766
2772
  // "Usdt0Oft": Not available on BSC
2767
2773
  },
2768
2774
  rpcEnvKey: "BSC_MAINNET",
@@ -2820,6 +2826,7 @@ var chainConfigs = {
2820
2826
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2821
2827
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2822
2828
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2829
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2823
2830
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2824
2831
  },
2825
2832
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2845,7 +2852,9 @@ var chainConfigs = {
2845
2852
  USDC: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
2846
2853
  USDT: "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
2847
2854
  EXT: "0x0e668E5127087e236578893a0e01E41837A28469",
2848
- cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F"
2855
+ cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F",
2856
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2857
+ EURC: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
2849
2858
  },
2850
2859
  tokenDecimals: {
2851
2860
  USDC: 6,
@@ -2874,6 +2883,7 @@ var chainConfigs = {
2874
2883
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2875
2884
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2876
2885
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2886
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2877
2887
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2878
2888
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2879
2889
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2904,7 +2914,9 @@ var chainConfigs = {
2904
2914
  USDC: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
2905
2915
  USDT: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
2906
2916
  EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
2907
- cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B"
2917
+ cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B",
2918
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2919
+ XSGD: "0xDC3326e71D45186F113a2F448984CA0e8D201995"
2908
2920
  },
2909
2921
  tokenDecimals: {
2910
2922
  USDC: 6,
@@ -2936,6 +2948,7 @@ var chainConfigs = {
2936
2948
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2937
2949
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2938
2950
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2951
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2939
2952
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2940
2953
  },
2941
2954
  rpcEnvKey: "POLYGON_MAINNET",
@@ -5140,6 +5153,21 @@ var ChainConfigService = class {
5140
5153
  getCNgnAsset(chain) {
5141
5154
  return this.getConfig(chain)?.assets?.cNGN;
5142
5155
  }
5156
+ /**
5157
+ * Address of `symbol` on `chain` from the per-chain asset table, matched
5158
+ * case-insensitively ("cNGN" ≡ "CNGN"). This table is the single source of
5159
+ * truth for token addresses — the simplex asset registry resolves through
5160
+ * it, so a new asset is added once in `chain.ts` and nowhere else.
5161
+ */
5162
+ getAssetBySymbol(chain, symbol) {
5163
+ const assets = this.getConfig(chain)?.assets;
5164
+ if (!assets) return void 0;
5165
+ const target = symbol.trim().toUpperCase();
5166
+ for (const [key, address] of Object.entries(assets)) {
5167
+ if (key.toUpperCase() === target) return address;
5168
+ }
5169
+ return void 0;
5170
+ }
5143
5171
  getCNgnDecimals(chain) {
5144
5172
  return this.getConfig(chain)?.tokenDecimals?.cNGN;
5145
5173
  }
@@ -5231,6 +5259,9 @@ var ChainConfigService = class {
5231
5259
  getCirclePaymasterAddress(chain) {
5232
5260
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5233
5261
  }
5262
+ getSimplexPaymasterAddress(chain) {
5263
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5264
+ }
5234
5265
  getHyperbridgeAddress() {
5235
5266
  return hyperbridgeAddress;
5236
5267
  }
@@ -7051,6 +7082,16 @@ var ExpectedError = class _ExpectedError extends Error {
7051
7082
  return error instanceof _ExpectedError;
7052
7083
  }
7053
7084
  };
7085
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7086
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7087
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7088
+ super(message, options);
7089
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7090
+ }
7091
+ static isError(error) {
7092
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7093
+ }
7094
+ };
7054
7095
 
7055
7096
  // src/configs/constants.ts
7056
7097
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7364,8 +7405,15 @@ var SubstrateChain = class _SubstrateChain {
7364
7405
  id: stateMachineId,
7365
7406
  height: Number(stateMachineHeight.height)
7366
7407
  };
7367
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7368
- return BigInt(updateTime);
7408
+ try {
7409
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7410
+ return BigInt(updateTime);
7411
+ } catch (error) {
7412
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7413
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7414
+ }
7415
+ throw error;
7416
+ }
7369
7417
  }
7370
7418
  /**
7371
7419
  * Get the challenge period for a given state machine id.
@@ -8111,26 +8159,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8111
8159
  };
8112
8160
  }
8113
8161
  /**
8114
- * Subscribes to PhantomOrderRegistered events from the intents coprocessor pallet.
8115
- * Calls the callback for each new phantom order as blocks arrive.
8116
- * Returns an unsubscribe function to stop the subscription.
8117
- */
8118
- async subscribePhantomOrders(callback) {
8119
- const unsub = await this.api.query.system.events((records) => {
8120
- for (const { event } of records) {
8121
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8122
- const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8123
- callback({
8124
- commitment: commitment.toHex(),
8125
- chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8126
- createdAt: createdAt.toNumber(),
8127
- tokenA: tokenA.toHex(),
8128
- tokenB: tokenB.toHex(),
8129
- standardAmount: BigInt(standardAmount.toString())
8130
- });
8162
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8163
+ */
8164
+ async getPhantomOrdersInBlock(blockNumber) {
8165
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8166
+ const apiAt = await this.api.at(blockHash);
8167
+ const records = await apiAt.query.system.events();
8168
+ const orders = [];
8169
+ for (const { event } of records) {
8170
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8171
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8172
+ orders.push({
8173
+ commitment: commitment.toHex(),
8174
+ chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8175
+ createdAt: createdAt.toNumber(),
8176
+ tokenA: tokenA.toHex(),
8177
+ tokenB: tokenB.toHex(),
8178
+ standardAmount: BigInt(standardAmount.toString())
8179
+ });
8180
+ }
8181
+ return orders;
8182
+ }
8183
+ /**
8184
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8185
+ *
8186
+ * Each tick reads the current head and scans every block between the last one processed and that
8187
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8188
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8189
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8190
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8191
+ * meant silently missed bids.
8192
+ *
8193
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8194
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8195
+ * read. Recovery replays the backlog.
8196
+ *
8197
+ * Returns a function that stops polling.
8198
+ */
8199
+ pollPhantomOrders(callback, options = {}) {
8200
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8201
+ let cursor = null;
8202
+ let inFlight = false;
8203
+ let stopped = false;
8204
+ const tick = async () => {
8205
+ if (inFlight || stopped) return;
8206
+ inFlight = true;
8207
+ try {
8208
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8209
+ if (cursor === null) {
8210
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8211
+ }
8212
+ if (head <= cursor) return;
8213
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8214
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8215
+ if (stopped) return;
8216
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8217
+ for (const order of orders) callback(order);
8218
+ cursor = blockNumber;
8219
+ }
8220
+ } catch (err) {
8221
+ onError?.(err);
8222
+ } finally {
8223
+ inFlight = false;
8131
8224
  }
8132
- });
8133
- return unsub;
8225
+ };
8226
+ void tick();
8227
+ const timer = setInterval(() => void tick(), intervalMs);
8228
+ return () => {
8229
+ stopped = true;
8230
+ clearInterval(timer);
8231
+ };
8134
8232
  }
8135
8233
  };
8136
8234
  var TronChain = class _TronChain {
@@ -9263,7 +9361,12 @@ function sleep(ms) {
9263
9361
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9264
9362
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9265
9363
  if (challengePeriod === BigInt(0)) return;
9266
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9364
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9365
+ maxRetries: 3,
9366
+ backoffMs: 500,
9367
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9368
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9369
+ });
9267
9370
  let currentTimestamp = await chain.timestamp();
9268
9371
  let timeElapsed = currentTimestamp - updateTime;
9269
9372
  if (timeElapsed > challengePeriod) return;
@@ -9410,6 +9513,10 @@ function normalizeAddressForEvmBytes32(address) {
9410
9513
  if (address.length === 66) return address;
9411
9514
  return bytes20ToBytes32(address);
9412
9515
  }
9516
+ function normalizeEvmAddress(address, field = "address") {
9517
+ if (!isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9518
+ return address.toLowerCase();
9519
+ }
9413
9520
  function normalizeAddressForStateMachine(address, stateMachineId) {
9414
9521
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9415
9522
  }
@@ -9417,14 +9524,16 @@ var DEFAULT_LOGGER = createConsola({
9417
9524
  level: LogLevels.silent
9418
9525
  });
9419
9526
  async function retryPromise(operation, retryConfig) {
9420
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9527
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9421
9528
  let lastError;
9422
9529
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9423
9530
  try {
9424
9531
  return await operation();
9425
9532
  } catch (error) {
9533
+ if (shouldRetry && !shouldRetry(error)) throw error;
9426
9534
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9427
9535
  lastError = error;
9536
+ if (i === retryConfig.maxRetries - 1) break;
9428
9537
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9429
9538
  }
9430
9539
  }
@@ -11495,6 +11604,55 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11495
11604
  }
11496
11605
  }
11497
11606
  }`;
11607
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11608
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11609
+ phantomOrderPriceSnapshots(
11610
+ filter: {
11611
+ and: [
11612
+ { tokenA: { equalTo: $tokenA } }
11613
+ { tokenB: { equalTo: $tokenB } }
11614
+ ]
11615
+ }
11616
+ orderBy: SNAPSHOT_TIME_DESC
11617
+ first: 1
11618
+ ) {
11619
+ nodes {
11620
+ commitment
11621
+ tokenA
11622
+ tokenB
11623
+ snapshotTime
11624
+ }
11625
+ }
11626
+ }`;
11627
+ var LIQUIDITY_PROVIDER_BALANCES = `
11628
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11629
+ liquidityProviderBalances(
11630
+ filter: {
11631
+ and: [
11632
+ { commitment: { equalTo: $commitment } }
11633
+ { tokenAddress: { equalTo: $tokenAddress } }
11634
+ ]
11635
+ }
11636
+ ) {
11637
+ aggregates {
11638
+ sum {
11639
+ balance
11640
+ }
11641
+ distinctCount {
11642
+ providerId
11643
+ }
11644
+ }
11645
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11646
+ keys
11647
+ sum {
11648
+ balance
11649
+ }
11650
+ distinctCount {
11651
+ providerId
11652
+ }
11653
+ }
11654
+ }
11655
+ }`;
11498
11656
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11499
11657
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11500
11658
  tokenGatewayAssetTeleportedV2s(
@@ -12079,6 +12237,7 @@ function createCancellationStorage(options = {}) {
12079
12237
  const decoded = decodeIProof(value);
12080
12238
  return decoded;
12081
12239
  }
12240
+ if (key2.includes("postCommitment")) return value;
12082
12241
  throw new Error(`Unknown storage key type: ${key2}`);
12083
12242
  };
12084
12243
  const setItem = async (key2, value) => {
@@ -12092,6 +12251,10 @@ function createCancellationStorage(options = {}) {
12092
12251
  await baseStorage.setItem(key2, encoded);
12093
12252
  return;
12094
12253
  }
12254
+ if (key2.includes("postCommitment") && typeof value === "string") {
12255
+ await baseStorage.setItem(key2, value);
12256
+ return;
12257
+ }
12095
12258
  throw new Error(`Unknown storage key type: ${key2}`);
12096
12259
  };
12097
12260
  const removeItem = async (key2) => {
@@ -12105,11 +12268,25 @@ function createCancellationStorage(options = {}) {
12105
12268
  });
12106
12269
  }
12107
12270
  var STORAGE_KEYS = Object.freeze({
12108
- destProof: (orderId) => `cancel-order:${orderId}:destProof`,
12109
- getRequest: (orderId) => `cancel-order:${orderId}:getRequest`,
12110
- sourceProof: (orderId) => `cancel-order:${orderId}:sourceProof`,
12111
- postCommitment: (orderId) => `cancel-order:${orderId}:postCommitment`
12271
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12272
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12273
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12274
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12112
12275
  });
12276
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12277
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12278
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12279
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12280
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12281
+ });
12282
+ function cancellationScope(orderId, source, destination) {
12283
+ if (!source || !destination) return orderId;
12284
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12285
+ }
12286
+ function legacyCancellationScope(orderId, source, destination) {
12287
+ if (!source || !destination) return orderId;
12288
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12289
+ }
12113
12290
  function createUsedUserOpsStorage(options = {}) {
12114
12291
  options.env ?? detectEnvironment();
12115
12292
  const driver = loadDriver({ options }) ?? inMemoryDriver();
@@ -15275,6 +15452,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15275
15452
  }
15276
15453
 
15277
15454
  // src/protocols/intents/OrderPlacer.ts
15455
+ function deriveCanonicalPlacedOrder(order, args) {
15456
+ const canonicalOrder = {
15457
+ ...order,
15458
+ user: args.user,
15459
+ source: args.source,
15460
+ destination: args.destination,
15461
+ deadline: args.deadline,
15462
+ nonce: args.nonce,
15463
+ fees: args.fees,
15464
+ session: args.session,
15465
+ predispatch: {
15466
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15467
+ call: order.predispatch.call
15468
+ },
15469
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15470
+ output: {
15471
+ beneficiary: args.beneficiary,
15472
+ assets: args.outputs.map((asset) => ({ ...asset })),
15473
+ call: order.output.call
15474
+ }
15475
+ };
15476
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15477
+ }
15278
15478
  var OrderPlacer = class {
15279
15479
  /**
15280
15480
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15291,17 +15491,16 @@ var OrderPlacer = class {
15291
15491
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15292
15492
  * transaction sending `data` to `to` and pass the signed transaction back
15293
15493
  * via `gen.next(signedTx)`.
15294
- * 2. Returns `{ order, transactionHash }` — the finalized order with its
15295
- * on-chain `nonce`, `inputs`, and computed `id`, plus the placement
15296
- * transaction hash.
15494
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15495
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15496
+ * placement transaction hash.
15297
15497
  *
15298
15498
  * A fresh ephemeral session key is generated for every call. The key is
15299
15499
  * stored immediately (without a commitment) so it can be retrieved by
15300
15500
  * address, then updated with the commitment once the `OrderPlaced` event
15301
- * confirms the nonce.
15501
+ * confirms the canonical order.
15302
15502
  *
15303
- * @param order - The order to place. `order.session` is mutated in-place
15304
- * with the generated session key address before yielding.
15503
+ * @param order - The order to place. It is not mutated.
15305
15504
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15306
15505
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15307
15506
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15316,7 +15515,7 @@ var OrderPlacer = class {
15316
15515
  const account = privateKeyToAccount(privateKey);
15317
15516
  const sessionKeyAddress = account.address;
15318
15517
  const createdAt = Date.now();
15319
- order.session = sessionKeyAddress;
15518
+ const placementOrder = { ...order, session: sessionKeyAddress };
15320
15519
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15321
15520
  privateKey,
15322
15521
  address: sessionKeyAddress,
@@ -15325,7 +15524,7 @@ var OrderPlacer = class {
15325
15524
  const data = encodeFunctionData({
15326
15525
  abi: ABI3,
15327
15526
  functionName: "placeOrder",
15328
- args: [transformOrderForContract(order), graffiti]
15527
+ args: [transformOrderForContract(placementOrder), graffiti]
15329
15528
  });
15330
15529
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15331
15530
  normalizeStateMachineId(order.source)
@@ -15345,20 +15544,15 @@ var OrderPlacer = class {
15345
15544
  if (!orderPlacedEvent) {
15346
15545
  throw new Error("OrderPlaced event not found in transaction receipt");
15347
15546
  }
15348
- order.nonce = orderPlacedEvent.args.nonce;
15349
- order.inputs = orderPlacedEvent.args.inputs.map((input) => ({
15350
- token: input.token,
15351
- amount: input.amount
15352
- }));
15353
- order.id = orderCommitment(order);
15547
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15354
15548
  const sessionKeyData = {
15355
15549
  privateKey,
15356
15550
  address: sessionKeyAddress,
15357
- commitment: order.id,
15551
+ commitment: finalizedOrder.id,
15358
15552
  createdAt
15359
15553
  };
15360
15554
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15361
- return { order, receipt };
15555
+ return { order: finalizedOrder, receipt };
15362
15556
  }
15363
15557
  };
15364
15558
 
@@ -15688,7 +15882,7 @@ var OrderExecutor = class {
15688
15882
  }
15689
15883
  }
15690
15884
  };
15691
- var OrderCanceller = class {
15885
+ var OrderCanceller = class _OrderCanceller {
15692
15886
  /**
15693
15887
  * @param ctx - Shared IntentsV2 context providing the source and destination
15694
15888
  * chain clients, config service, and cancellation storage.
@@ -15697,6 +15891,9 @@ var OrderCanceller = class {
15697
15891
  this.ctx = ctx;
15698
15892
  }
15699
15893
  ctx;
15894
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15895
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15896
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15700
15897
  logger = createConsola({
15701
15898
  level: LogLevels.info,
15702
15899
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15728,11 +15925,14 @@ var OrderCanceller = class {
15728
15925
  * @returns The native token dispatch fee in wei.
15729
15926
  */
15730
15927
  async quoteCancelFromSource(order) {
15731
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15732
15928
  const sourceStateMachine = normalizeStateMachineId(order.source);
15929
+ const destStateMachine = normalizeStateMachineId(order.destination);
15930
+ if (sourceStateMachine === destStateMachine) {
15931
+ return { nativeValue: 0n, relayerFee: 0n };
15932
+ }
15733
15933
  const height = order.deadline + 1n;
15734
15934
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15735
- normalizeStateMachineId(order.destination)
15935
+ destStateMachine
15736
15936
  );
15737
15937
  const slotHash = await this.ctx.dest.client.readContract({
15738
15938
  abi: ABI3,
@@ -15744,8 +15944,8 @@ var OrderCanceller = class {
15744
15944
  const context = encodeWithdrawalRequest(order, order.user);
15745
15945
  const getRequest = {
15746
15946
  source: sourceStateMachine,
15747
- dest: normalizeStateMachineId(order.destination),
15748
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
15947
+ dest: destStateMachine,
15948
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15749
15949
  nonce: await this.ctx.source.getHostNonce(),
15750
15950
  height,
15751
15951
  keys: [key],
@@ -15762,7 +15962,9 @@ var OrderCanceller = class {
15762
15962
  * cancellation is complete.
15763
15963
  *
15764
15964
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15765
- * the `from` parameter.
15965
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
15966
+ * source-side GET recovery, clears its stale checkpoint and restarts the
15967
+ * source-side stream internally.
15766
15968
  *
15767
15969
  * @param order - The order to cancel.
15768
15970
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15772,12 +15974,32 @@ var OrderCanceller = class {
15772
15974
  * cancellation lifecycle.
15773
15975
  */
15774
15976
  async *cancelOrder(order, indexerClient, options = {}) {
15775
- const isSameChain = order.source === order.destination;
15977
+ const sourceStateMachine = normalizeStateMachineId(order.source);
15978
+ const destStateMachine = normalizeStateMachineId(order.destination);
15979
+ const isSameChain = sourceStateMachine === destStateMachine;
15776
15980
  if (options.from === "destination" && !isSameChain) {
15777
15981
  yield* this.cancelOrderFromDest(order, indexerClient);
15778
15982
  return;
15779
15983
  }
15780
- yield* this.cancelOrderFromSource(order, indexerClient);
15984
+ let recoveryRestarts = 0;
15985
+ while (true) {
15986
+ try {
15987
+ yield* this.cancelOrderFromSource(order, indexerClient);
15988
+ return;
15989
+ } catch (error) {
15990
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
15991
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
15992
+ throw new Error(
15993
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
15994
+ );
15995
+ }
15996
+ recoveryRestarts += 1;
15997
+ this.logger.warn(
15998
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
15999
+ );
16000
+ await this.clearGetRecoveryCache(order);
16001
+ }
16002
+ }
15781
16003
  }
15782
16004
  /**
15783
16005
  * Async generator that cancels an order by initiating the cancel from the
@@ -15803,11 +16025,10 @@ var OrderCanceller = class {
15803
16025
  * @throws If the cancel transaction does not contain the expected on-chain event.
15804
16026
  */
15805
16027
  async *cancelOrderFromSource(order, indexerClient) {
15806
- const orderId = order.id;
15807
- const isSameChain = order.source === order.destination;
15808
- const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15809
- normalizeStateMachineId(order.source)
15810
- );
16028
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16029
+ const destStateMachine = normalizeStateMachineId(order.destination);
16030
+ const isSameChain = sourceStateMachine === destStateMachine;
16031
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15811
16032
  if (isSameChain) {
15812
16033
  const data = encodeFunctionData({
15813
16034
  abi: ABI3,
@@ -15836,18 +16057,37 @@ var OrderCanceller = class {
15836
16057
  };
15837
16058
  return;
15838
16059
  }
16060
+ const storageKeys = this.recoveryStorageKeys(order);
16061
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15839
16062
  const hyperbridge = indexerClient.hyperbridge;
15840
- const sourceStateMachine = normalizeStateMachineId(order.source);
15841
16063
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15842
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16064
+ let destIProof = await this.getRecoveryItem(
16065
+ storageKeys.destProof,
16066
+ legacyStorageKeys.map((keys) => keys.destProof)
16067
+ );
15843
16068
  if (!destIProof) {
15844
16069
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15845
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16070
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15846
16071
  } else {
15847
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16072
+ let refreshed = false;
16073
+ try {
16074
+ await this.assertProofFresh(hyperbridge, destIProof);
16075
+ } catch (error) {
16076
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16077
+ await this.removeRecoveryItems(
16078
+ storageKeys.destProof,
16079
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16080
+ );
16081
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16082
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16083
+ refreshed = true;
16084
+ }
16085
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15848
16086
  }
15849
- let getRequest = await this.ctx.cancellationStorage.getItem(
15850
- STORAGE_KEYS.getRequest(orderId)
16087
+ await this.assertProofFresh(hyperbridge, destIProof);
16088
+ let getRequest = await this.getRecoveryItem(
16089
+ storageKeys.getRequest,
16090
+ legacyStorageKeys.map((keys) => keys.getRequest)
15851
16091
  );
15852
16092
  if (!getRequest) {
15853
16093
  const quote = await this.quoteCancelFromSource(order);
@@ -15867,7 +16107,7 @@ var OrderCanceller = class {
15867
16107
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15868
16108
  if (!request) throw new Error("GetRequest missing");
15869
16109
  getRequest = request.args;
15870
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16110
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15871
16111
  yield {
15872
16112
  status: "CANCEL_STARTED",
15873
16113
  receipt
@@ -15886,8 +16126,9 @@ var OrderCanceller = class {
15886
16126
  metadata: statusUpdate.metadata
15887
16127
  };
15888
16128
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15889
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15890
- STORAGE_KEYS.sourceProof(orderId)
16129
+ let sourceIProof = await this.getRecoveryItem(
16130
+ storageKeys.sourceProof,
16131
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15891
16132
  );
15892
16133
  if (!sourceIProof) {
15893
16134
  sourceIProof = await fetchSourceProof(
@@ -15897,8 +16138,9 @@ var OrderCanceller = class {
15897
16138
  sourceConsensusStateId,
15898
16139
  sourceHeight
15899
16140
  );
15900
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16141
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15901
16142
  }
16143
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15902
16144
  await waitForChallengePeriod(hyperbridge, {
15903
16145
  height: sourceIProof.height,
15904
16146
  id: {
@@ -15906,6 +16148,10 @@ var OrderCanceller = class {
15906
16148
  consensusStateId: sourceConsensusStateId
15907
16149
  }
15908
16150
  });
16151
+ await Promise.all([
16152
+ this.assertProofFresh(hyperbridge, sourceIProof),
16153
+ this.assertProofFresh(hyperbridge, destIProof)
16154
+ ]);
15909
16155
  const getRequestMessage = {
15910
16156
  kind: "GetRequest",
15911
16157
  requests: [getRequest],
@@ -15927,9 +16173,12 @@ var OrderCanceller = class {
15927
16173
  status: "HYPERBRIDGE_FINALIZED",
15928
16174
  metadata: statusUpdate.metadata
15929
16175
  };
15930
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.destProof(orderId));
15931
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.getRequest(orderId));
15932
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.sourceProof(orderId));
16176
+ await this.removeRecoveryItems(
16177
+ storageKeys.destProof,
16178
+ storageKeys.getRequest,
16179
+ storageKeys.sourceProof,
16180
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16181
+ );
15933
16182
  return;
15934
16183
  }
15935
16184
  }
@@ -15946,9 +16195,11 @@ var OrderCanceller = class {
15946
16195
  * @returns The native token dispatch fee in wei.
15947
16196
  */
15948
16197
  async quoteCancelFromDest(order) {
15949
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15950
- const destStateMachine = normalizeStateMachineId(order.destination);
15951
16198
  const sourceStateMachine = normalizeStateMachineId(order.source);
16199
+ const destStateMachine = normalizeStateMachineId(order.destination);
16200
+ if (sourceStateMachine === destStateMachine) {
16201
+ return { nativeValue: 0n, relayerFee: 0n };
16202
+ }
15952
16203
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15953
16204
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15954
16205
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -15986,11 +16237,13 @@ var OrderCanceller = class {
15986
16237
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
15987
16238
  */
15988
16239
  async *cancelOrderFromDest(order, indexerClient) {
15989
- const orderId = order.id;
16240
+ const storageKeys = this.recoveryStorageKeys(order);
15990
16241
  const destStateMachine = normalizeStateMachineId(order.destination);
15991
16242
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15992
- let commitment = await this.ctx.cancellationStorage.getItem(
15993
- STORAGE_KEYS.postCommitment(orderId)
16243
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16244
+ let commitment = await this.getRecoveryItem(
16245
+ storageKeys.postCommitment,
16246
+ legacyStorageKeys.map((keys) => keys.postCommitment)
15994
16247
  );
15995
16248
  if (!commitment) {
15996
16249
  const quote = await this.quoteCancelFromDest(order);
@@ -16015,7 +16268,7 @@ var OrderCanceller = class {
16015
16268
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
16016
16269
  const postArgs = postEvent.args;
16017
16270
  commitment = postRequestCommitment(postArgs).commitment;
16018
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16271
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
16019
16272
  }
16020
16273
  const statusStream = indexerClient.postRequestStatusStream(commitment);
16021
16274
  for await (const statusUpdate of statusStream) {
@@ -16049,7 +16302,10 @@ var OrderCanceller = class {
16049
16302
  if (refundEvents.length === 0) {
16050
16303
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
16051
16304
  }
16052
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16305
+ await this.removeRecoveryItems(
16306
+ storageKeys.postCommitment,
16307
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16308
+ );
16053
16309
  yield {
16054
16310
  status: "CANCELLATION_COMPLETE",
16055
16311
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16092,7 +16348,7 @@ var OrderCanceller = class {
16092
16348
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16093
16349
  this.ctx.dest.config.stateMachineId
16094
16350
  );
16095
- const orderId = order.id;
16351
+ const orderId = this.orderId(order);
16096
16352
  const slotHash = await this.ctx.dest.client.readContract({
16097
16353
  abi: ABI3,
16098
16354
  address: intentGatewayV2Address,
@@ -16109,6 +16365,7 @@ var OrderCanceller = class {
16109
16365
  yield { status: "DESTINATION_FINALIZED", proof };
16110
16366
  return proof;
16111
16367
  } catch (e) {
16368
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16112
16369
  lastFailedHeight = latestHeight;
16113
16370
  await sleep(1e4);
16114
16371
  }
@@ -16139,6 +16396,7 @@ var OrderCanceller = class {
16139
16396
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16140
16397
  await sleep(3e4);
16141
16398
  } catch (error) {
16399
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16142
16400
  this.logger.warn(
16143
16401
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16144
16402
  );
@@ -16147,14 +16405,128 @@ var OrderCanceller = class {
16147
16405
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16148
16406
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16149
16407
  } catch (error) {
16408
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16150
16409
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16151
16410
  this.logger.error(message2);
16152
16411
  throw new Error(message2);
16153
16412
  }
16154
16413
  }
16414
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16415
+ async assertProofFresh(hyperbridge, proof) {
16416
+ await retryPromise(
16417
+ () => hyperbridge.stateMachineUpdateTime({
16418
+ height: proof.height,
16419
+ id: {
16420
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16421
+ consensusStateId: proof.consensusStateId
16422
+ }
16423
+ }),
16424
+ {
16425
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16426
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16427
+ logger: this.logger,
16428
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16429
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16430
+ }
16431
+ );
16432
+ }
16433
+ /**
16434
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16435
+ * a consensus update required by that recovery attempt.
16436
+ */
16437
+ async clearGetRecoveryCache(order) {
16438
+ const keys = this.recoveryStorageKeys(order);
16439
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16440
+ await this.removeRecoveryItems(
16441
+ keys.destProof,
16442
+ keys.sourceProof,
16443
+ keys.getRequest,
16444
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16445
+ legacyKeys.destProof,
16446
+ legacyKeys.sourceProof,
16447
+ legacyKeys.getRequest
16448
+ ])
16449
+ );
16450
+ }
16451
+ /**
16452
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16453
+ * for an order.
16454
+ *
16455
+ * @param order - The order whose source/destination recovery state is stored.
16456
+ */
16457
+ recoveryStorageKeys(order) {
16458
+ const orderId = this.orderId(order);
16459
+ return {
16460
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16461
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16462
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16463
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16464
+ };
16465
+ }
16466
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16467
+ legacyRecoveryStorageKeys(order) {
16468
+ const orderId = this.orderId(order);
16469
+ return [
16470
+ {
16471
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16472
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16473
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16474
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16475
+ },
16476
+ {
16477
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16478
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16479
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16480
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16481
+ }
16482
+ ];
16483
+ }
16484
+ async getRecoveryItem(currentKey, legacyKeys) {
16485
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16486
+ if (current) return current;
16487
+ for (const legacyKey of new Set(legacyKeys)) {
16488
+ if (legacyKey === currentKey) continue;
16489
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16490
+ if (!legacy) continue;
16491
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16492
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16493
+ return legacy;
16494
+ }
16495
+ return null;
16496
+ }
16497
+ async removeRecoveryItems(...keys) {
16498
+ await Promise.all(
16499
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16500
+ );
16501
+ }
16502
+ /**
16503
+ * Returns the order identifier required to persist or clear recovery state.
16504
+ *
16505
+ * @param order - The order being cancelled.
16506
+ * @throws If the order has no identifier.
16507
+ */
16508
+ orderId(order) {
16509
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16510
+ return order.id;
16511
+ }
16512
+ /**
16513
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16514
+ *
16515
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16516
+ * @param commitment - Commitment of the GET request.
16517
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16518
+ */
16155
16519
  async queryDeliveredReceipt(hyperbridge, commitment) {
16156
16520
  return hyperbridge.queryResponseReceipt(commitment);
16157
16521
  }
16522
+ /**
16523
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16524
+ *
16525
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16526
+ * @param commitment - Commitment of the GET request.
16527
+ * @returns The delivered response receipt commitment.
16528
+ * @throws If no receipt is observed within the configured retry limit.
16529
+ */
16158
16530
  async pollDeliveredReceipt(hyperbridge, commitment) {
16159
16531
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16160
16532
  return retryPromise(
@@ -16892,6 +17264,7 @@ var BidManager = class {
16892
17264
  }
16893
17265
  }
16894
17266
  };
17267
+ var RELAYER_MESSAGE_GAS = 1000000n;
16895
17268
  var GasEstimator = class {
16896
17269
  /**
16897
17270
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -16912,7 +17285,12 @@ var GasEstimator = class {
16912
17285
  *
16913
17286
  * **Cross-chain orders:** also estimates the ISMP POST request fee required
16914
17287
  * for the solver to trigger source-chain escrow redemption after filling, and
16915
- * includes it in `fillOptions.relayerFee` and `fillOptions.nativeDispatchFee`.
17288
+ * includes it in `fillOptions.relayerFee`. The dispatch is always paid in the
17289
+ * fee token — `nativeDispatchFee` is fixed at 0. The native rail would draw
17290
+ * from the solver account's native balance, which nothing guarantees, and a
17291
+ * shortfall is invisible to estimation (the account balance is overridden
17292
+ * during simulation) — it would only surface as a reverted execution that
17293
+ * still bills the paymaster.
16916
17294
  *
16917
17295
  * **Bundler path:** constructs a mock `PackedUserOperation` signed by an
16918
17296
  * ephemeral keypair, applies state overrides, and calls
@@ -16961,24 +17339,19 @@ var GasEstimator = class {
16961
17339
  intentGatewayV2Address,
16962
17340
  entryPointAddress
16963
17341
  }),
16964
- isSameChain ? Promise.resolve({ postRequestFee: 0n, protocolFee: 0n }) : this.estimateCrossChainFees(
16965
- sourceFeeToken,
16966
- destFeeToken,
16967
- souceStateMachineId,
16968
- destStateMachineId,
16969
- order
16970
- )
17342
+ isSameChain ? Promise.resolve({ postRequestFee: 0n, relayerFeeInSourceFeeToken: 0n }) : this.estimateCrossChainFees(sourceFeeToken, destFeeToken, souceStateMachineId)
16971
17343
  ]);
16972
17344
  const { viem: stateOverrides, bundler: bundlerStateOverrides } = stateOverridesResult;
16973
17345
  const fillOptions = {
16974
17346
  relayerFee: crossChainFees.postRequestFee,
16975
- nativeDispatchFee: crossChainFees.protocolFee,
17347
+ // Always dispatch with the fee token (see the method docs).
17348
+ nativeDispatchFee: 0n,
16976
17349
  outputs: order.output.assets.map((asset) => ({
16977
17350
  ...asset,
16978
17351
  token: normalizeAddressForEvmBytes32(asset.token)
16979
17352
  }))
16980
17353
  };
16981
- const totalNativeValue = totalEthValue + fillOptions.nativeDispatchFee;
17354
+ const totalNativeValue = totalEthValue;
16982
17355
  const priorityFeeBumpPercent = params.maxPriorityFeePerGasBumpPercent ?? 8;
16983
17356
  const maxFeeBumpPercent = params.maxFeePerGasBumpPercent ?? 10;
16984
17357
  let maxPriorityFeePerGas = gasPrice + gasPrice * BigInt(priorityFeeBumpPercent) / 100n;
@@ -17145,37 +17518,32 @@ var GasEstimator = class {
17145
17518
  maxPriorityFeePerGas,
17146
17519
  totalGasCostWei,
17147
17520
  totalGasInFeeToken: totalGasInSourceFeeToken,
17521
+ relayerFeeInSourceFeeToken: crossChainFees.relayerFeeInSourceFeeToken,
17148
17522
  fillOptions
17149
17523
  };
17150
17524
  }
17151
17525
  /**
17152
- * Estimates cross-chain ISMP POST request fees by running the gas-to-fee-token
17153
- * conversion and host nonce fetch in parallel, then quoting the native dispatch cost.
17154
- */
17155
- async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId, destChainId, order) {
17156
- const postRequestGas = 400000n;
17157
- const [postRequestFeeInSourceFeeToken, nonce] = await Promise.all([
17158
- convertGasToFeeToken(this.ctx, postRequestGas, "source", sourceChainId),
17159
- this.ctx.dest.getHostNonce()
17160
- ]);
17161
- let postRequestFeeInDestFeeToken = adjustDecimals(
17526
+ * Estimates the cross-chain ISMP POST request fee (the relayer fee for the
17527
+ * RedeemEscrow message), in both the source and destination fee tokens.
17528
+ * The dispatch is always paid in the fee token — the native rail was
17529
+ * removed because it silently drew on a native balance nothing guarantees.
17530
+ */
17531
+ async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId) {
17532
+ const postRequestFeeInSourceFeeToken = await convertGasToFeeToken(
17533
+ this.ctx,
17534
+ RELAYER_MESSAGE_GAS,
17535
+ "source",
17536
+ sourceChainId
17537
+ );
17538
+ const postRequestFeeInDestFeeToken = adjustDecimals(
17162
17539
  postRequestFeeInSourceFeeToken,
17163
17540
  sourceFeeToken.decimals,
17164
17541
  destFeeToken.decimals
17165
17542
  );
17166
- const postRequest = {
17167
- source: destChainId,
17168
- dest: sourceChainId,
17169
- body: constructRedeemEscrowRequestBody({ ...order, id: orderCommitment(order) }, MOCK_ADDRESS),
17170
- timeoutTimestamp: 0n,
17171
- nonce,
17172
- from: this.ctx.source.configService.getIntentGatewayAddress(destChainId),
17173
- to: this.ctx.source.configService.getIntentGatewayAddress(sourceChainId)
17543
+ return {
17544
+ postRequestFee: postRequestFeeInDestFeeToken,
17545
+ relayerFeeInSourceFeeToken: postRequestFeeInSourceFeeToken
17174
17546
  };
17175
- postRequestFeeInDestFeeToken = postRequestFeeInDestFeeToken * 1005n / 1000n;
17176
- let protocolFeeInNativeToken = await this.ctx.dest.quoteNative(postRequest, postRequestFeeInDestFeeToken).catch(() => 0n);
17177
- protocolFeeInNativeToken = protocolFeeInNativeToken * 1005n / 1000n;
17178
- return { postRequestFee: postRequestFeeInDestFeeToken, protocolFee: protocolFeeInNativeToken };
17179
17547
  }
17180
17548
  /**
17181
17549
  * Builds EVM state override objects for gas estimation of the `fillOrder`
@@ -17367,6 +17735,178 @@ var OrderStatusChecker = class {
17367
17735
  return true;
17368
17736
  }
17369
17737
  };
17738
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17739
+ var LiquidityEngine = class {
17740
+ /**
17741
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17742
+ * @param chainConfigService - Resolves token decimals for formatted results.
17743
+ */
17744
+ constructor(queryClient, chainConfigService) {
17745
+ this.queryClient = queryClient;
17746
+ this.chainConfigService = chainConfigService;
17747
+ }
17748
+ queryClient;
17749
+ chainConfigService;
17750
+ /**
17751
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17752
+ * indexed output-token liquidity.
17753
+ *
17754
+ * Nexus filters balances to the canonical output token, then aggregates them
17755
+ * overall and by chain. The returned amounts are decimal strings: the total
17756
+ * uses the canonical Base output-token decimals, while each chain group uses
17757
+ * that chain's token decimals. They describe `snapshotTime`, not live
17758
+ * reservations or fill guarantees.
17759
+ *
17760
+ * @param params - Canonical Phantom-market input and output token addresses.
17761
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17762
+ * the directional pair.
17763
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17764
+ * or internally inconsistent snapshot data.
17765
+ */
17766
+ async getAvailableLiquiditySnapshot(params) {
17767
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17768
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17769
+ const response = await this.queryClient.request(
17770
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17771
+ { tokenA: tokenIn, tokenB: tokenOut }
17772
+ );
17773
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17774
+ if (!node) return;
17775
+ const commitment = node.commitment.toLowerCase();
17776
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17777
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17778
+ }
17779
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17780
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17781
+ }
17782
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17783
+ if (Number.isNaN(snapshotTime.getTime())) {
17784
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17785
+ }
17786
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17787
+ commitment,
17788
+ tokenAddress: tokenOut
17789
+ });
17790
+ return {
17791
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17792
+ providerCount,
17793
+ tokenAddress: tokenOut,
17794
+ snapshotTime,
17795
+ liquidityByChain: liquidityByChain.map((group) => ({
17796
+ ...group,
17797
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17798
+ }))
17799
+ };
17800
+ }
17801
+ /**
17802
+ * Requests server-side sums and distinct provider counts for one immutable
17803
+ * snapshot/output-token pair, including chain-level aggregate groups.
17804
+ *
17805
+ * `commitment` uniquely identifies the selected snapshot
17806
+ */
17807
+ async querySnapshotLiquidityAggregates(params) {
17808
+ const response = await this.queryClient.request(
17809
+ LIQUIDITY_PROVIDER_BALANCES,
17810
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17811
+ );
17812
+ const connection = response?.liquidityProviderBalances;
17813
+ const aggregates = connection?.aggregates;
17814
+ if (!connection || !aggregates) {
17815
+ throw new InvalidAvailableLiquiditySnapshotError(
17816
+ params.commitment,
17817
+ "liquidityProviderBalances aggregates are missing"
17818
+ );
17819
+ }
17820
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17821
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17822
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17823
+ const [chain, tokenAddress] = group.keys;
17824
+ if (!chain?.trim() || !tokenAddress) {
17825
+ throw new InvalidAvailableLiquiditySnapshotError(
17826
+ params.commitment,
17827
+ `liquidity group ${index} has invalid keys`
17828
+ );
17829
+ }
17830
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17831
+ tokenAddress,
17832
+ params.commitment,
17833
+ `liquidity group ${index} tokenAddress`
17834
+ );
17835
+ if (normalizedTokenAddress !== params.tokenAddress) {
17836
+ throw new InvalidAvailableLiquiditySnapshotError(
17837
+ params.commitment,
17838
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17839
+ );
17840
+ }
17841
+ return {
17842
+ chain: chain.trim(),
17843
+ tokenAddress: normalizedTokenAddress,
17844
+ totalLiquidity: parseSnapshotBigInt(
17845
+ group.sum.balance ?? "0",
17846
+ params.commitment,
17847
+ `liquidity group ${index} balance`
17848
+ ),
17849
+ providerCount: parseProviderCount(
17850
+ group.distinctCount.providerId,
17851
+ params.commitment,
17852
+ `liquidity group ${index}`
17853
+ )
17854
+ };
17855
+ });
17856
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17857
+ if (groupedTotal !== totalLiquidity) {
17858
+ throw new InvalidAvailableLiquiditySnapshotError(
17859
+ params.commitment,
17860
+ "grouped liquidity does not match the total liquidity"
17861
+ );
17862
+ }
17863
+ return { totalLiquidity, providerCount, liquidityByChain };
17864
+ }
17865
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17866
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17867
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17868
+ if (decimals === void 0) {
17869
+ throw new InvalidAvailableLiquiditySnapshotError(
17870
+ commitment,
17871
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17872
+ );
17873
+ }
17874
+ return formatUnits(amount, decimals);
17875
+ }
17876
+ };
17877
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17878
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17879
+ constructor(commitment, reason) {
17880
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17881
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17882
+ }
17883
+ };
17884
+ function parseSnapshotBigInt(value, commitment, field) {
17885
+ try {
17886
+ const amount = BigInt(value);
17887
+ if (amount < 0n) {
17888
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17889
+ }
17890
+ return amount;
17891
+ } catch (error) {
17892
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17893
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17894
+ }
17895
+ }
17896
+ function parseProviderCount(value, commitment, field) {
17897
+ const count = Number(value);
17898
+ if (!Number.isSafeInteger(count) || count < 0) {
17899
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17900
+ }
17901
+ return count;
17902
+ }
17903
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17904
+ try {
17905
+ return normalizeEvmAddress(address, field);
17906
+ } catch {
17907
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17908
+ }
17909
+ }
17370
17910
 
17371
17911
  // src/protocols/intents/quote/types.ts
17372
17912
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17653,12 +18193,14 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17653
18193
  constructor(chainConfigService, getQueryClient) {
17654
18194
  this.chainConfigService = chainConfigService;
17655
18195
  this.getQueryClient = getQueryClient;
18196
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
17656
18197
  }
17657
18198
  chainConfigService;
17658
18199
  getQueryClient;
18200
+ pairResolver;
17659
18201
  async quote(params, source, destination) {
17660
18202
  validateQuoteParams(params);
17661
- const pair = this.resolveBaseSnapshotPair(params, source.stateMachineId, destination.stateMachineId);
18203
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
17662
18204
  if (!pair) {
17663
18205
  throw new UnsupportedIntentQuotePairError({
17664
18206
  source: source.stateMachineId,
@@ -17731,7 +18273,13 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17731
18273
  throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
17732
18274
  }
17733
18275
  }
17734
- resolveBaseSnapshotPair(params, sourceStateMachineId, destinationStateMachineId) {
18276
+ };
18277
+ var PhantomSnapshotPairResolver = class {
18278
+ constructor(chainConfigService) {
18279
+ this.chainConfigService = chainConfigService;
18280
+ }
18281
+ chainConfigService;
18282
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
17735
18283
  const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
17736
18284
  const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
17737
18285
  if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
@@ -17755,7 +18303,6 @@ function isConfiguredAddress(address) {
17755
18303
  }
17756
18304
 
17757
18305
  // src/protocols/intents/IntentGateway.ts
17758
- var CROSS_CHAIN_ORDER_FEE_GAS_BUMP = 600000n;
17759
18306
  var IntentGateway = class _IntentGateway {
17760
18307
  /** EVM chain on which orders are placed and escrowed. */
17761
18308
  source;
@@ -17783,6 +18330,8 @@ var IntentGateway = class _IntentGateway {
17783
18330
  gasEstimator;
17784
18331
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17785
18332
  quoteStrategies;
18333
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18334
+ phantomSnapshotPairResolver;
17786
18335
  /**
17787
18336
  * Private constructor — use {@link IntentGateway.create} instead.
17788
18337
  *
@@ -17826,6 +18375,7 @@ var IntentGateway = class _IntentGateway {
17826
18375
  this.bidManager = bidManager;
17827
18376
  this.gasEstimator = gasEstimator;
17828
18377
  this._crypto = crypto;
18378
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17829
18379
  this.quoteStrategies = {
17830
18380
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
17831
18381
  dest.configService,
@@ -17904,15 +18454,47 @@ var IntentGateway = class _IntentGateway {
17904
18454
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17905
18455
  return handler.quote({ ...params, strategy }, source, destination);
17906
18456
  }
18457
+ /**
18458
+ * Returns the output-token liquidity measured in the latest directional
18459
+ * Phantom snapshot for this gateway's source and destination.
18460
+ *
18461
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18462
+ * The snapshot itself determines the output token and chain to aggregate. The
18463
+ * amount is in the token's smallest unit and reflects the indexer's
18464
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18465
+ *
18466
+ * Requires a prior call to {@link withQueryClient}.
18467
+ */
18468
+ async queryAvailableLiquidity(params) {
18469
+ const { queryClient } = this.requireIndexer();
18470
+ const sourceStateMachineId = this.source.config.stateMachineId;
18471
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18472
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18473
+ if (!pair) {
18474
+ throw new UnsupportedIntentQuotePairError({
18475
+ source: sourceStateMachineId,
18476
+ destination: destinationStateMachineId,
18477
+ tokenIn: params.tokenIn,
18478
+ tokenOut: params.tokenOut,
18479
+ quoteSource: "Phantom snapshot pair"
18480
+ });
18481
+ }
18482
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18483
+ tokenIn: pair.tokenA,
18484
+ tokenOut: pair.tokenB
18485
+ });
18486
+ }
17907
18487
  /**
17908
18488
  * Bidirectional async generator that orchestrates the full order lifecycle:
17909
18489
  * placement, fee estimation, bid collection, and execution.
17910
18490
  *
17911
18491
  * **Yield/receive protocol:**
17912
- * 1. If `order.fees` is unset or zero, estimates gas and sets same-chain
17913
- * `order.fees` to twice the estimate. Cross-chain orders retain a 1% buffer
17914
- * and include an additional 600k-gas fee uplift, while the wei cost used for
17915
- * the `value` field receives a 2% buffer.
18492
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and
18493
+ * sets same-chain fees to twice the estimate. Cross-chain orders attach
18494
+ * (fill gas + a settlement-message uplift of `RELAYER_MESSAGE_GAS`, the
18495
+ * same gas budget the solver's relayer fee uses) with a 5% buffer over
18496
+ * the whole sum — strictly above the solver's unpadded requirement. The
18497
+ * wei cost used for the `value` field receives a 2% buffer.
17916
18498
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
17917
18499
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
17918
18500
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
@@ -17920,8 +18502,8 @@ var IntentGateway = class _IntentGateway {
17920
18502
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17921
18503
  * subsequent status updates until the order is filled, exhausted, or fails.
17922
18504
  *
17923
- * @param order - The order to place and execute. `order.fees` may be 0; it
17924
- * will be estimated automatically if so.
18505
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18506
+ * may be 0; fees are estimated automatically if so.
17925
18507
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17926
18508
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17927
18509
  * @param options - Optional tuning parameters:
@@ -17934,10 +18516,11 @@ var IntentGateway = class _IntentGateway {
17934
18516
  * estimation returns zero.
17935
18517
  */
17936
18518
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18519
+ const executionOrder = { ...order };
17937
18520
  let value;
17938
- if (!order.fees || order.fees === 0n) {
18521
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17939
18522
  const estimate = await this.gasEstimator.estimateFillOrder({
17940
- order,
18523
+ order: executionOrder,
17941
18524
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
17942
18525
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
17943
18526
  });
@@ -17948,13 +18531,13 @@ var IntentGateway = class _IntentGateway {
17948
18531
  value = estimate.totalGasCostWei + estimate.totalGasCostWei * 2n / 100n;
17949
18532
  const crossChainFeeBump = isSameChain ? 0n : await convertGasToFeeToken(
17950
18533
  this.ctx,
17951
- CROSS_CHAIN_ORDER_FEE_GAS_BUMP,
18534
+ RELAYER_MESSAGE_GAS,
17952
18535
  "source",
17953
18536
  this.source.config.stateMachineId
17954
18537
  );
17955
- order.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
18538
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : (estimate.totalGasInFeeToken + crossChainFeeBump) * 105n / 100n;
17956
18539
  }
17957
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18540
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
17958
18541
  const placeOrderFirst = await placeOrderGen.next();
17959
18542
  if (placeOrderFirst.done) {
17960
18543
  throw new Error("placeOrder generator completed without yielding");
@@ -18065,13 +18648,20 @@ var IntentGateway = class _IntentGateway {
18065
18648
  const gen = this.execute(order, graffiti, options);
18066
18649
  try {
18067
18650
  let input;
18651
+ let finalizedOrder;
18068
18652
  while (true) {
18069
18653
  const { value, done } = await gen.next(input);
18070
18654
  input = void 0;
18071
18655
  if (done) break;
18072
- if (value.status === "BIDS_RECEIVED") {
18656
+ if (value.status === "ORDER_PLACED") {
18657
+ finalizedOrder = value.order;
18073
18658
  yield value;
18074
- input = await this.autoSelect(order, value.bids);
18659
+ } else if (value.status === "BIDS_RECEIVED") {
18660
+ if (!finalizedOrder) {
18661
+ throw new Error("Received bids before the order was finalized");
18662
+ }
18663
+ yield value;
18664
+ input = await this.autoSelect(finalizedOrder, value.bids);
18075
18665
  } else if (value.status === "AWAITING_PLACE_ORDER") {
18076
18666
  input = yield value;
18077
18667
  } else {
@@ -22849,6 +23439,6 @@ async function teleportDot(param_) {
22849
23439
  return stream;
22850
23440
  }
22851
23441
 
22852
- export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeERC7821ExecuteBatch, decodeUserOpScale, encodeERC7821ExecuteBatch, encodeISMPMessage, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
23442
+ export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeERC7821ExecuteBatch, decodeUserOpScale, encodeERC7821ExecuteBatch, encodeISMPMessage, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
22853
23443
  //# sourceMappingURL=index.js.map
22854
23444
  //# sourceMappingURL=index.js.map