@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.
@@ -1,5 +1,5 @@
1
1
  import { createConsola, LogLevels } from 'consola';
2
- 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';
2
+ 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';
3
3
  import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimism, polygonAmoy, polygon, base, arbitrum, bsc, mainnet, sepolia, gnosisChiado, bscTestnet, tron, unichain } from 'viem/chains';
4
4
  import { TronWeb } from 'tronweb';
5
5
  import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
@@ -2728,7 +2728,11 @@ var chainConfigs = {
2728
2728
  DAI: "0x6b175474e89094c44da98b954eedeac495271d0f",
2729
2729
  USDC: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
2730
2730
  USDT: "0xdac17f958d2ee523a2206206994597c13d831ec7",
2731
- cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab"
2731
+ cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab",
2732
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2733
+ EURC: "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
2734
+ XSGD: "0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96",
2735
+ TRYB: "0x2C537E5624e4af88A7ae4060C022609376C8D0EB"
2732
2736
  },
2733
2737
  tokenDecimals: {
2734
2738
  USDC: 6,
@@ -2759,6 +2763,7 @@ var chainConfigs = {
2759
2763
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2760
2764
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2761
2765
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2766
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2762
2767
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2763
2768
  },
2764
2769
  rpcEnvKey: "ETH_MAINNET",
@@ -2812,7 +2817,8 @@ var chainConfigs = {
2812
2817
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2813
2818
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2814
2819
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2815
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2820
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2821
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2816
2822
  // "Usdt0Oft": Not available on BSC
2817
2823
  },
2818
2824
  rpcEnvKey: "BSC_MAINNET",
@@ -2870,6 +2876,7 @@ var chainConfigs = {
2870
2876
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2871
2877
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2872
2878
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2879
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2873
2880
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2874
2881
  },
2875
2882
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2895,7 +2902,9 @@ var chainConfigs = {
2895
2902
  USDC: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
2896
2903
  USDT: "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
2897
2904
  EXT: "0x0e668E5127087e236578893a0e01E41837A28469",
2898
- cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F"
2905
+ cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F",
2906
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2907
+ EURC: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
2899
2908
  },
2900
2909
  tokenDecimals: {
2901
2910
  USDC: 6,
@@ -2924,6 +2933,7 @@ var chainConfigs = {
2924
2933
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2925
2934
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2926
2935
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2936
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2927
2937
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2928
2938
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2929
2939
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2954,7 +2964,9 @@ var chainConfigs = {
2954
2964
  USDC: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
2955
2965
  USDT: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
2956
2966
  EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
2957
- cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B"
2967
+ cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B",
2968
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2969
+ XSGD: "0xDC3326e71D45186F113a2F448984CA0e8D201995"
2958
2970
  },
2959
2971
  tokenDecimals: {
2960
2972
  USDC: 6,
@@ -2986,6 +2998,7 @@ var chainConfigs = {
2986
2998
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2987
2999
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2988
3000
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
3001
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2989
3002
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2990
3003
  },
2991
3004
  rpcEnvKey: "POLYGON_MAINNET",
@@ -5190,6 +5203,21 @@ var ChainConfigService = class {
5190
5203
  getCNgnAsset(chain) {
5191
5204
  return this.getConfig(chain)?.assets?.cNGN;
5192
5205
  }
5206
+ /**
5207
+ * Address of `symbol` on `chain` from the per-chain asset table, matched
5208
+ * case-insensitively ("cNGN" ≡ "CNGN"). This table is the single source of
5209
+ * truth for token addresses — the simplex asset registry resolves through
5210
+ * it, so a new asset is added once in `chain.ts` and nowhere else.
5211
+ */
5212
+ getAssetBySymbol(chain, symbol) {
5213
+ const assets = this.getConfig(chain)?.assets;
5214
+ if (!assets) return void 0;
5215
+ const target = symbol.trim().toUpperCase();
5216
+ for (const [key, address] of Object.entries(assets)) {
5217
+ if (key.toUpperCase() === target) return address;
5218
+ }
5219
+ return void 0;
5220
+ }
5193
5221
  getCNgnDecimals(chain) {
5194
5222
  return this.getConfig(chain)?.tokenDecimals?.cNGN;
5195
5223
  }
@@ -5281,6 +5309,9 @@ var ChainConfigService = class {
5281
5309
  getCirclePaymasterAddress(chain) {
5282
5310
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5283
5311
  }
5312
+ getSimplexPaymasterAddress(chain) {
5313
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5314
+ }
5284
5315
  getHyperbridgeAddress() {
5285
5316
  return hyperbridgeAddress;
5286
5317
  }
@@ -7101,6 +7132,16 @@ var ExpectedError = class _ExpectedError extends Error {
7101
7132
  return error instanceof _ExpectedError;
7102
7133
  }
7103
7134
  };
7135
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7136
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7137
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7138
+ super(message, options);
7139
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7140
+ }
7141
+ static isError(error) {
7142
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7143
+ }
7144
+ };
7104
7145
 
7105
7146
  // src/configs/constants.ts
7106
7147
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7414,8 +7455,15 @@ var SubstrateChain = class _SubstrateChain {
7414
7455
  id: stateMachineId,
7415
7456
  height: Number(stateMachineHeight.height)
7416
7457
  };
7417
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7418
- return BigInt(updateTime);
7458
+ try {
7459
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7460
+ return BigInt(updateTime);
7461
+ } catch (error) {
7462
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7463
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7464
+ }
7465
+ throw error;
7466
+ }
7419
7467
  }
7420
7468
  /**
7421
7469
  * Get the challenge period for a given state machine id.
@@ -8161,26 +8209,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8161
8209
  };
8162
8210
  }
8163
8211
  /**
8164
- * Subscribes to PhantomOrderRegistered events from the intents coprocessor pallet.
8165
- * Calls the callback for each new phantom order as blocks arrive.
8166
- * Returns an unsubscribe function to stop the subscription.
8167
- */
8168
- async subscribePhantomOrders(callback) {
8169
- const unsub = await this.api.query.system.events((records) => {
8170
- for (const { event } of records) {
8171
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8172
- const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8173
- callback({
8174
- commitment: commitment.toHex(),
8175
- chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8176
- createdAt: createdAt.toNumber(),
8177
- tokenA: tokenA.toHex(),
8178
- tokenB: tokenB.toHex(),
8179
- standardAmount: BigInt(standardAmount.toString())
8180
- });
8212
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8213
+ */
8214
+ async getPhantomOrdersInBlock(blockNumber) {
8215
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8216
+ const apiAt = await this.api.at(blockHash);
8217
+ const records = await apiAt.query.system.events();
8218
+ const orders = [];
8219
+ for (const { event } of records) {
8220
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8221
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8222
+ orders.push({
8223
+ commitment: commitment.toHex(),
8224
+ chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8225
+ createdAt: createdAt.toNumber(),
8226
+ tokenA: tokenA.toHex(),
8227
+ tokenB: tokenB.toHex(),
8228
+ standardAmount: BigInt(standardAmount.toString())
8229
+ });
8230
+ }
8231
+ return orders;
8232
+ }
8233
+ /**
8234
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8235
+ *
8236
+ * Each tick reads the current head and scans every block between the last one processed and that
8237
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8238
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8239
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8240
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8241
+ * meant silently missed bids.
8242
+ *
8243
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8244
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8245
+ * read. Recovery replays the backlog.
8246
+ *
8247
+ * Returns a function that stops polling.
8248
+ */
8249
+ pollPhantomOrders(callback, options = {}) {
8250
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8251
+ let cursor = null;
8252
+ let inFlight = false;
8253
+ let stopped = false;
8254
+ const tick = async () => {
8255
+ if (inFlight || stopped) return;
8256
+ inFlight = true;
8257
+ try {
8258
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8259
+ if (cursor === null) {
8260
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8261
+ }
8262
+ if (head <= cursor) return;
8263
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8264
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8265
+ if (stopped) return;
8266
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8267
+ for (const order of orders) callback(order);
8268
+ cursor = blockNumber;
8269
+ }
8270
+ } catch (err) {
8271
+ onError?.(err);
8272
+ } finally {
8273
+ inFlight = false;
8181
8274
  }
8182
- });
8183
- return unsub;
8275
+ };
8276
+ void tick();
8277
+ const timer = setInterval(() => void tick(), intervalMs);
8278
+ return () => {
8279
+ stopped = true;
8280
+ clearInterval(timer);
8281
+ };
8184
8282
  }
8185
8283
  };
8186
8284
  var TronChain = class _TronChain {
@@ -9313,7 +9411,12 @@ function sleep(ms) {
9313
9411
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9314
9412
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9315
9413
  if (challengePeriod === BigInt(0)) return;
9316
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9414
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9415
+ maxRetries: 3,
9416
+ backoffMs: 500,
9417
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9418
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9419
+ });
9317
9420
  let currentTimestamp = await chain.timestamp();
9318
9421
  let timeElapsed = currentTimestamp - updateTime;
9319
9422
  if (timeElapsed > challengePeriod) return;
@@ -9460,6 +9563,10 @@ function normalizeAddressForEvmBytes32(address) {
9460
9563
  if (address.length === 66) return address;
9461
9564
  return bytes20ToBytes32(address);
9462
9565
  }
9566
+ function normalizeEvmAddress(address, field = "address") {
9567
+ if (!isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9568
+ return address.toLowerCase();
9569
+ }
9463
9570
  function normalizeAddressForStateMachine(address, stateMachineId) {
9464
9571
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9465
9572
  }
@@ -9467,14 +9574,16 @@ var DEFAULT_LOGGER = createConsola({
9467
9574
  level: LogLevels.silent
9468
9575
  });
9469
9576
  async function retryPromise(operation, retryConfig) {
9470
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9577
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9471
9578
  let lastError;
9472
9579
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9473
9580
  try {
9474
9581
  return await operation();
9475
9582
  } catch (error) {
9583
+ if (shouldRetry && !shouldRetry(error)) throw error;
9476
9584
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9477
9585
  lastError = error;
9586
+ if (i === retryConfig.maxRetries - 1) break;
9478
9587
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9479
9588
  }
9480
9589
  }
@@ -11545,6 +11654,55 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11545
11654
  }
11546
11655
  }
11547
11656
  }`;
11657
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11658
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11659
+ phantomOrderPriceSnapshots(
11660
+ filter: {
11661
+ and: [
11662
+ { tokenA: { equalTo: $tokenA } }
11663
+ { tokenB: { equalTo: $tokenB } }
11664
+ ]
11665
+ }
11666
+ orderBy: SNAPSHOT_TIME_DESC
11667
+ first: 1
11668
+ ) {
11669
+ nodes {
11670
+ commitment
11671
+ tokenA
11672
+ tokenB
11673
+ snapshotTime
11674
+ }
11675
+ }
11676
+ }`;
11677
+ var LIQUIDITY_PROVIDER_BALANCES = `
11678
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11679
+ liquidityProviderBalances(
11680
+ filter: {
11681
+ and: [
11682
+ { commitment: { equalTo: $commitment } }
11683
+ { tokenAddress: { equalTo: $tokenAddress } }
11684
+ ]
11685
+ }
11686
+ ) {
11687
+ aggregates {
11688
+ sum {
11689
+ balance
11690
+ }
11691
+ distinctCount {
11692
+ providerId
11693
+ }
11694
+ }
11695
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11696
+ keys
11697
+ sum {
11698
+ balance
11699
+ }
11700
+ distinctCount {
11701
+ providerId
11702
+ }
11703
+ }
11704
+ }
11705
+ }`;
11548
11706
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11549
11707
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11550
11708
  tokenGatewayAssetTeleportedV2s(
@@ -12139,6 +12297,7 @@ function createCancellationStorage(options = {}) {
12139
12297
  const decoded = decodeIProof(value);
12140
12298
  return decoded;
12141
12299
  }
12300
+ if (key2.includes("postCommitment")) return value;
12142
12301
  throw new Error(`Unknown storage key type: ${key2}`);
12143
12302
  };
12144
12303
  const setItem = async (key2, value) => {
@@ -12152,6 +12311,10 @@ function createCancellationStorage(options = {}) {
12152
12311
  await baseStorage.setItem(key2, encoded);
12153
12312
  return;
12154
12313
  }
12314
+ if (key2.includes("postCommitment") && typeof value === "string") {
12315
+ await baseStorage.setItem(key2, value);
12316
+ return;
12317
+ }
12155
12318
  throw new Error(`Unknown storage key type: ${key2}`);
12156
12319
  };
12157
12320
  const removeItem = async (key2) => {
@@ -12165,11 +12328,25 @@ function createCancellationStorage(options = {}) {
12165
12328
  });
12166
12329
  }
12167
12330
  var STORAGE_KEYS = Object.freeze({
12168
- destProof: (orderId) => `cancel-order:${orderId}:destProof`,
12169
- getRequest: (orderId) => `cancel-order:${orderId}:getRequest`,
12170
- sourceProof: (orderId) => `cancel-order:${orderId}:sourceProof`,
12171
- postCommitment: (orderId) => `cancel-order:${orderId}:postCommitment`
12331
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12332
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12333
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12334
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12172
12335
  });
12336
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12337
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12338
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12339
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12340
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12341
+ });
12342
+ function cancellationScope(orderId, source, destination) {
12343
+ if (!source || !destination) return orderId;
12344
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12345
+ }
12346
+ function legacyCancellationScope(orderId, source, destination) {
12347
+ if (!source || !destination) return orderId;
12348
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12349
+ }
12173
12350
  function createUsedUserOpsStorage(options = {}) {
12174
12351
  const key = options.env ?? detectEnvironment();
12175
12352
  const driver = loadDriver({ key}) ?? inMemoryDriver();
@@ -15335,6 +15512,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15335
15512
  }
15336
15513
 
15337
15514
  // src/protocols/intents/OrderPlacer.ts
15515
+ function deriveCanonicalPlacedOrder(order, args) {
15516
+ const canonicalOrder = {
15517
+ ...order,
15518
+ user: args.user,
15519
+ source: args.source,
15520
+ destination: args.destination,
15521
+ deadline: args.deadline,
15522
+ nonce: args.nonce,
15523
+ fees: args.fees,
15524
+ session: args.session,
15525
+ predispatch: {
15526
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15527
+ call: order.predispatch.call
15528
+ },
15529
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15530
+ output: {
15531
+ beneficiary: args.beneficiary,
15532
+ assets: args.outputs.map((asset) => ({ ...asset })),
15533
+ call: order.output.call
15534
+ }
15535
+ };
15536
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15537
+ }
15338
15538
  var OrderPlacer = class {
15339
15539
  /**
15340
15540
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15351,17 +15551,16 @@ var OrderPlacer = class {
15351
15551
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15352
15552
  * transaction sending `data` to `to` and pass the signed transaction back
15353
15553
  * via `gen.next(signedTx)`.
15354
- * 2. Returns `{ order, transactionHash }` — the finalized order with its
15355
- * on-chain `nonce`, `inputs`, and computed `id`, plus the placement
15356
- * transaction hash.
15554
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15555
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15556
+ * placement transaction hash.
15357
15557
  *
15358
15558
  * A fresh ephemeral session key is generated for every call. The key is
15359
15559
  * stored immediately (without a commitment) so it can be retrieved by
15360
15560
  * address, then updated with the commitment once the `OrderPlaced` event
15361
- * confirms the nonce.
15561
+ * confirms the canonical order.
15362
15562
  *
15363
- * @param order - The order to place. `order.session` is mutated in-place
15364
- * with the generated session key address before yielding.
15563
+ * @param order - The order to place. It is not mutated.
15365
15564
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15366
15565
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15367
15566
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15376,7 +15575,7 @@ var OrderPlacer = class {
15376
15575
  const account = privateKeyToAccount(privateKey);
15377
15576
  const sessionKeyAddress = account.address;
15378
15577
  const createdAt = Date.now();
15379
- order.session = sessionKeyAddress;
15578
+ const placementOrder = { ...order, session: sessionKeyAddress };
15380
15579
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15381
15580
  privateKey,
15382
15581
  address: sessionKeyAddress,
@@ -15385,7 +15584,7 @@ var OrderPlacer = class {
15385
15584
  const data = encodeFunctionData({
15386
15585
  abi: ABI3,
15387
15586
  functionName: "placeOrder",
15388
- args: [transformOrderForContract(order), graffiti]
15587
+ args: [transformOrderForContract(placementOrder), graffiti]
15389
15588
  });
15390
15589
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15391
15590
  normalizeStateMachineId(order.source)
@@ -15405,20 +15604,15 @@ var OrderPlacer = class {
15405
15604
  if (!orderPlacedEvent) {
15406
15605
  throw new Error("OrderPlaced event not found in transaction receipt");
15407
15606
  }
15408
- order.nonce = orderPlacedEvent.args.nonce;
15409
- order.inputs = orderPlacedEvent.args.inputs.map((input) => ({
15410
- token: input.token,
15411
- amount: input.amount
15412
- }));
15413
- order.id = orderCommitment(order);
15607
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15414
15608
  const sessionKeyData = {
15415
15609
  privateKey,
15416
15610
  address: sessionKeyAddress,
15417
- commitment: order.id,
15611
+ commitment: finalizedOrder.id,
15418
15612
  createdAt
15419
15613
  };
15420
15614
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15421
- return { order, receipt };
15615
+ return { order: finalizedOrder, receipt };
15422
15616
  }
15423
15617
  };
15424
15618
 
@@ -15748,7 +15942,7 @@ var OrderExecutor = class {
15748
15942
  }
15749
15943
  }
15750
15944
  };
15751
- var OrderCanceller = class {
15945
+ var OrderCanceller = class _OrderCanceller {
15752
15946
  /**
15753
15947
  * @param ctx - Shared IntentsV2 context providing the source and destination
15754
15948
  * chain clients, config service, and cancellation storage.
@@ -15757,6 +15951,9 @@ var OrderCanceller = class {
15757
15951
  this.ctx = ctx;
15758
15952
  }
15759
15953
  ctx;
15954
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15955
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15956
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15760
15957
  logger = createConsola({
15761
15958
  level: LogLevels.info,
15762
15959
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15788,11 +15985,14 @@ var OrderCanceller = class {
15788
15985
  * @returns The native token dispatch fee in wei.
15789
15986
  */
15790
15987
  async quoteCancelFromSource(order) {
15791
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15792
15988
  const sourceStateMachine = normalizeStateMachineId(order.source);
15989
+ const destStateMachine = normalizeStateMachineId(order.destination);
15990
+ if (sourceStateMachine === destStateMachine) {
15991
+ return { nativeValue: 0n, relayerFee: 0n };
15992
+ }
15793
15993
  const height = order.deadline + 1n;
15794
15994
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15795
- normalizeStateMachineId(order.destination)
15995
+ destStateMachine
15796
15996
  );
15797
15997
  const slotHash = await this.ctx.dest.client.readContract({
15798
15998
  abi: ABI3,
@@ -15804,8 +16004,8 @@ var OrderCanceller = class {
15804
16004
  const context = encodeWithdrawalRequest(order, order.user);
15805
16005
  const getRequest = {
15806
16006
  source: sourceStateMachine,
15807
- dest: normalizeStateMachineId(order.destination),
15808
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
16007
+ dest: destStateMachine,
16008
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15809
16009
  nonce: await this.ctx.source.getHostNonce(),
15810
16010
  height,
15811
16011
  keys: [key],
@@ -15822,7 +16022,9 @@ var OrderCanceller = class {
15822
16022
  * cancellation is complete.
15823
16023
  *
15824
16024
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15825
- * the `from` parameter.
16025
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
16026
+ * source-side GET recovery, clears its stale checkpoint and restarts the
16027
+ * source-side stream internally.
15826
16028
  *
15827
16029
  * @param order - The order to cancel.
15828
16030
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15832,12 +16034,32 @@ var OrderCanceller = class {
15832
16034
  * cancellation lifecycle.
15833
16035
  */
15834
16036
  async *cancelOrder(order, indexerClient, options = {}) {
15835
- const isSameChain = order.source === order.destination;
16037
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16038
+ const destStateMachine = normalizeStateMachineId(order.destination);
16039
+ const isSameChain = sourceStateMachine === destStateMachine;
15836
16040
  if (options.from === "destination" && !isSameChain) {
15837
16041
  yield* this.cancelOrderFromDest(order, indexerClient);
15838
16042
  return;
15839
16043
  }
15840
- yield* this.cancelOrderFromSource(order, indexerClient);
16044
+ let recoveryRestarts = 0;
16045
+ while (true) {
16046
+ try {
16047
+ yield* this.cancelOrderFromSource(order, indexerClient);
16048
+ return;
16049
+ } catch (error) {
16050
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16051
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
16052
+ throw new Error(
16053
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
16054
+ );
16055
+ }
16056
+ recoveryRestarts += 1;
16057
+ this.logger.warn(
16058
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
16059
+ );
16060
+ await this.clearGetRecoveryCache(order);
16061
+ }
16062
+ }
15841
16063
  }
15842
16064
  /**
15843
16065
  * Async generator that cancels an order by initiating the cancel from the
@@ -15863,11 +16085,10 @@ var OrderCanceller = class {
15863
16085
  * @throws If the cancel transaction does not contain the expected on-chain event.
15864
16086
  */
15865
16087
  async *cancelOrderFromSource(order, indexerClient) {
15866
- const orderId = order.id;
15867
- const isSameChain = order.source === order.destination;
15868
- const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15869
- normalizeStateMachineId(order.source)
15870
- );
16088
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16089
+ const destStateMachine = normalizeStateMachineId(order.destination);
16090
+ const isSameChain = sourceStateMachine === destStateMachine;
16091
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15871
16092
  if (isSameChain) {
15872
16093
  const data = encodeFunctionData({
15873
16094
  abi: ABI3,
@@ -15896,18 +16117,37 @@ var OrderCanceller = class {
15896
16117
  };
15897
16118
  return;
15898
16119
  }
16120
+ const storageKeys = this.recoveryStorageKeys(order);
16121
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15899
16122
  const hyperbridge = indexerClient.hyperbridge;
15900
- const sourceStateMachine = normalizeStateMachineId(order.source);
15901
16123
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15902
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16124
+ let destIProof = await this.getRecoveryItem(
16125
+ storageKeys.destProof,
16126
+ legacyStorageKeys.map((keys) => keys.destProof)
16127
+ );
15903
16128
  if (!destIProof) {
15904
16129
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15905
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16130
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15906
16131
  } else {
15907
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16132
+ let refreshed = false;
16133
+ try {
16134
+ await this.assertProofFresh(hyperbridge, destIProof);
16135
+ } catch (error) {
16136
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16137
+ await this.removeRecoveryItems(
16138
+ storageKeys.destProof,
16139
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16140
+ );
16141
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16142
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16143
+ refreshed = true;
16144
+ }
16145
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15908
16146
  }
15909
- let getRequest = await this.ctx.cancellationStorage.getItem(
15910
- STORAGE_KEYS.getRequest(orderId)
16147
+ await this.assertProofFresh(hyperbridge, destIProof);
16148
+ let getRequest = await this.getRecoveryItem(
16149
+ storageKeys.getRequest,
16150
+ legacyStorageKeys.map((keys) => keys.getRequest)
15911
16151
  );
15912
16152
  if (!getRequest) {
15913
16153
  const quote = await this.quoteCancelFromSource(order);
@@ -15927,7 +16167,7 @@ var OrderCanceller = class {
15927
16167
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15928
16168
  if (!request) throw new Error("GetRequest missing");
15929
16169
  getRequest = request.args;
15930
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16170
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15931
16171
  yield {
15932
16172
  status: "CANCEL_STARTED",
15933
16173
  receipt
@@ -15946,8 +16186,9 @@ var OrderCanceller = class {
15946
16186
  metadata: statusUpdate.metadata
15947
16187
  };
15948
16188
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15949
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15950
- STORAGE_KEYS.sourceProof(orderId)
16189
+ let sourceIProof = await this.getRecoveryItem(
16190
+ storageKeys.sourceProof,
16191
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15951
16192
  );
15952
16193
  if (!sourceIProof) {
15953
16194
  sourceIProof = await fetchSourceProof(
@@ -15957,8 +16198,9 @@ var OrderCanceller = class {
15957
16198
  sourceConsensusStateId,
15958
16199
  sourceHeight
15959
16200
  );
15960
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16201
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15961
16202
  }
16203
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15962
16204
  await waitForChallengePeriod(hyperbridge, {
15963
16205
  height: sourceIProof.height,
15964
16206
  id: {
@@ -15966,6 +16208,10 @@ var OrderCanceller = class {
15966
16208
  consensusStateId: sourceConsensusStateId
15967
16209
  }
15968
16210
  });
16211
+ await Promise.all([
16212
+ this.assertProofFresh(hyperbridge, sourceIProof),
16213
+ this.assertProofFresh(hyperbridge, destIProof)
16214
+ ]);
15969
16215
  const getRequestMessage = {
15970
16216
  kind: "GetRequest",
15971
16217
  requests: [getRequest],
@@ -15987,9 +16233,12 @@ var OrderCanceller = class {
15987
16233
  status: "HYPERBRIDGE_FINALIZED",
15988
16234
  metadata: statusUpdate.metadata
15989
16235
  };
15990
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.destProof(orderId));
15991
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.getRequest(orderId));
15992
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.sourceProof(orderId));
16236
+ await this.removeRecoveryItems(
16237
+ storageKeys.destProof,
16238
+ storageKeys.getRequest,
16239
+ storageKeys.sourceProof,
16240
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16241
+ );
15993
16242
  return;
15994
16243
  }
15995
16244
  }
@@ -16006,9 +16255,11 @@ var OrderCanceller = class {
16006
16255
  * @returns The native token dispatch fee in wei.
16007
16256
  */
16008
16257
  async quoteCancelFromDest(order) {
16009
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
16010
- const destStateMachine = normalizeStateMachineId(order.destination);
16011
16258
  const sourceStateMachine = normalizeStateMachineId(order.source);
16259
+ const destStateMachine = normalizeStateMachineId(order.destination);
16260
+ if (sourceStateMachine === destStateMachine) {
16261
+ return { nativeValue: 0n, relayerFee: 0n };
16262
+ }
16012
16263
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
16013
16264
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
16014
16265
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -16046,11 +16297,13 @@ var OrderCanceller = class {
16046
16297
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
16047
16298
  */
16048
16299
  async *cancelOrderFromDest(order, indexerClient) {
16049
- const orderId = order.id;
16300
+ const storageKeys = this.recoveryStorageKeys(order);
16050
16301
  const destStateMachine = normalizeStateMachineId(order.destination);
16051
16302
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
16052
- let commitment = await this.ctx.cancellationStorage.getItem(
16053
- STORAGE_KEYS.postCommitment(orderId)
16303
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16304
+ let commitment = await this.getRecoveryItem(
16305
+ storageKeys.postCommitment,
16306
+ legacyStorageKeys.map((keys) => keys.postCommitment)
16054
16307
  );
16055
16308
  if (!commitment) {
16056
16309
  const quote = await this.quoteCancelFromDest(order);
@@ -16075,7 +16328,7 @@ var OrderCanceller = class {
16075
16328
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
16076
16329
  const postArgs = postEvent.args;
16077
16330
  commitment = postRequestCommitment(postArgs).commitment;
16078
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16331
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
16079
16332
  }
16080
16333
  const statusStream = indexerClient.postRequestStatusStream(commitment);
16081
16334
  for await (const statusUpdate of statusStream) {
@@ -16109,7 +16362,10 @@ var OrderCanceller = class {
16109
16362
  if (refundEvents.length === 0) {
16110
16363
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
16111
16364
  }
16112
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16365
+ await this.removeRecoveryItems(
16366
+ storageKeys.postCommitment,
16367
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16368
+ );
16113
16369
  yield {
16114
16370
  status: "CANCELLATION_COMPLETE",
16115
16371
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16152,7 +16408,7 @@ var OrderCanceller = class {
16152
16408
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16153
16409
  this.ctx.dest.config.stateMachineId
16154
16410
  );
16155
- const orderId = order.id;
16411
+ const orderId = this.orderId(order);
16156
16412
  const slotHash = await this.ctx.dest.client.readContract({
16157
16413
  abi: ABI3,
16158
16414
  address: intentGatewayV2Address,
@@ -16169,6 +16425,7 @@ var OrderCanceller = class {
16169
16425
  yield { status: "DESTINATION_FINALIZED", proof };
16170
16426
  return proof;
16171
16427
  } catch (e) {
16428
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16172
16429
  lastFailedHeight = latestHeight;
16173
16430
  await sleep(1e4);
16174
16431
  }
@@ -16199,6 +16456,7 @@ var OrderCanceller = class {
16199
16456
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16200
16457
  await sleep(3e4);
16201
16458
  } catch (error) {
16459
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16202
16460
  this.logger.warn(
16203
16461
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16204
16462
  );
@@ -16207,14 +16465,128 @@ var OrderCanceller = class {
16207
16465
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16208
16466
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16209
16467
  } catch (error) {
16468
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16210
16469
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16211
16470
  this.logger.error(message2);
16212
16471
  throw new Error(message2);
16213
16472
  }
16214
16473
  }
16474
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16475
+ async assertProofFresh(hyperbridge, proof) {
16476
+ await retryPromise(
16477
+ () => hyperbridge.stateMachineUpdateTime({
16478
+ height: proof.height,
16479
+ id: {
16480
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16481
+ consensusStateId: proof.consensusStateId
16482
+ }
16483
+ }),
16484
+ {
16485
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16486
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16487
+ logger: this.logger,
16488
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16489
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16490
+ }
16491
+ );
16492
+ }
16493
+ /**
16494
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16495
+ * a consensus update required by that recovery attempt.
16496
+ */
16497
+ async clearGetRecoveryCache(order) {
16498
+ const keys = this.recoveryStorageKeys(order);
16499
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16500
+ await this.removeRecoveryItems(
16501
+ keys.destProof,
16502
+ keys.sourceProof,
16503
+ keys.getRequest,
16504
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16505
+ legacyKeys.destProof,
16506
+ legacyKeys.sourceProof,
16507
+ legacyKeys.getRequest
16508
+ ])
16509
+ );
16510
+ }
16511
+ /**
16512
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16513
+ * for an order.
16514
+ *
16515
+ * @param order - The order whose source/destination recovery state is stored.
16516
+ */
16517
+ recoveryStorageKeys(order) {
16518
+ const orderId = this.orderId(order);
16519
+ return {
16520
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16521
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16522
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16523
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16524
+ };
16525
+ }
16526
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16527
+ legacyRecoveryStorageKeys(order) {
16528
+ const orderId = this.orderId(order);
16529
+ return [
16530
+ {
16531
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16532
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16533
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16534
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16535
+ },
16536
+ {
16537
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16538
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16539
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16540
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16541
+ }
16542
+ ];
16543
+ }
16544
+ async getRecoveryItem(currentKey, legacyKeys) {
16545
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16546
+ if (current) return current;
16547
+ for (const legacyKey of new Set(legacyKeys)) {
16548
+ if (legacyKey === currentKey) continue;
16549
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16550
+ if (!legacy) continue;
16551
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16552
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16553
+ return legacy;
16554
+ }
16555
+ return null;
16556
+ }
16557
+ async removeRecoveryItems(...keys) {
16558
+ await Promise.all(
16559
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16560
+ );
16561
+ }
16562
+ /**
16563
+ * Returns the order identifier required to persist or clear recovery state.
16564
+ *
16565
+ * @param order - The order being cancelled.
16566
+ * @throws If the order has no identifier.
16567
+ */
16568
+ orderId(order) {
16569
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16570
+ return order.id;
16571
+ }
16572
+ /**
16573
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16574
+ *
16575
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16576
+ * @param commitment - Commitment of the GET request.
16577
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16578
+ */
16215
16579
  async queryDeliveredReceipt(hyperbridge, commitment) {
16216
16580
  return hyperbridge.queryResponseReceipt(commitment);
16217
16581
  }
16582
+ /**
16583
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16584
+ *
16585
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16586
+ * @param commitment - Commitment of the GET request.
16587
+ * @returns The delivered response receipt commitment.
16588
+ * @throws If no receipt is observed within the configured retry limit.
16589
+ */
16218
16590
  async pollDeliveredReceipt(hyperbridge, commitment) {
16219
16591
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16220
16592
  return retryPromise(
@@ -16952,6 +17324,7 @@ var BidManager = class {
16952
17324
  }
16953
17325
  }
16954
17326
  };
17327
+ var RELAYER_MESSAGE_GAS = 1000000n;
16955
17328
  var GasEstimator = class {
16956
17329
  /**
16957
17330
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -16972,7 +17345,12 @@ var GasEstimator = class {
16972
17345
  *
16973
17346
  * **Cross-chain orders:** also estimates the ISMP POST request fee required
16974
17347
  * for the solver to trigger source-chain escrow redemption after filling, and
16975
- * includes it in `fillOptions.relayerFee` and `fillOptions.nativeDispatchFee`.
17348
+ * includes it in `fillOptions.relayerFee`. The dispatch is always paid in the
17349
+ * fee token — `nativeDispatchFee` is fixed at 0. The native rail would draw
17350
+ * from the solver account's native balance, which nothing guarantees, and a
17351
+ * shortfall is invisible to estimation (the account balance is overridden
17352
+ * during simulation) — it would only surface as a reverted execution that
17353
+ * still bills the paymaster.
16976
17354
  *
16977
17355
  * **Bundler path:** constructs a mock `PackedUserOperation` signed by an
16978
17356
  * ephemeral keypair, applies state overrides, and calls
@@ -17021,24 +17399,19 @@ var GasEstimator = class {
17021
17399
  intentGatewayV2Address,
17022
17400
  entryPointAddress
17023
17401
  }),
17024
- isSameChain ? Promise.resolve({ postRequestFee: 0n, protocolFee: 0n }) : this.estimateCrossChainFees(
17025
- sourceFeeToken,
17026
- destFeeToken,
17027
- souceStateMachineId,
17028
- destStateMachineId,
17029
- order
17030
- )
17402
+ isSameChain ? Promise.resolve({ postRequestFee: 0n, relayerFeeInSourceFeeToken: 0n }) : this.estimateCrossChainFees(sourceFeeToken, destFeeToken, souceStateMachineId)
17031
17403
  ]);
17032
17404
  const { viem: stateOverrides, bundler: bundlerStateOverrides } = stateOverridesResult;
17033
17405
  const fillOptions = {
17034
17406
  relayerFee: crossChainFees.postRequestFee,
17035
- nativeDispatchFee: crossChainFees.protocolFee,
17407
+ // Always dispatch with the fee token (see the method docs).
17408
+ nativeDispatchFee: 0n,
17036
17409
  outputs: order.output.assets.map((asset) => ({
17037
17410
  ...asset,
17038
17411
  token: normalizeAddressForEvmBytes32(asset.token)
17039
17412
  }))
17040
17413
  };
17041
- const totalNativeValue = totalEthValue + fillOptions.nativeDispatchFee;
17414
+ const totalNativeValue = totalEthValue;
17042
17415
  const priorityFeeBumpPercent = params.maxPriorityFeePerGasBumpPercent ?? 8;
17043
17416
  const maxFeeBumpPercent = params.maxFeePerGasBumpPercent ?? 10;
17044
17417
  let maxPriorityFeePerGas = gasPrice + gasPrice * BigInt(priorityFeeBumpPercent) / 100n;
@@ -17205,37 +17578,32 @@ var GasEstimator = class {
17205
17578
  maxPriorityFeePerGas,
17206
17579
  totalGasCostWei,
17207
17580
  totalGasInFeeToken: totalGasInSourceFeeToken,
17581
+ relayerFeeInSourceFeeToken: crossChainFees.relayerFeeInSourceFeeToken,
17208
17582
  fillOptions
17209
17583
  };
17210
17584
  }
17211
17585
  /**
17212
- * Estimates cross-chain ISMP POST request fees by running the gas-to-fee-token
17213
- * conversion and host nonce fetch in parallel, then quoting the native dispatch cost.
17214
- */
17215
- async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId, destChainId, order) {
17216
- const postRequestGas = 400000n;
17217
- const [postRequestFeeInSourceFeeToken, nonce] = await Promise.all([
17218
- convertGasToFeeToken(this.ctx, postRequestGas, "source", sourceChainId),
17219
- this.ctx.dest.getHostNonce()
17220
- ]);
17221
- let postRequestFeeInDestFeeToken = adjustDecimals(
17586
+ * Estimates the cross-chain ISMP POST request fee (the relayer fee for the
17587
+ * RedeemEscrow message), in both the source and destination fee tokens.
17588
+ * The dispatch is always paid in the fee token — the native rail was
17589
+ * removed because it silently drew on a native balance nothing guarantees.
17590
+ */
17591
+ async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId) {
17592
+ const postRequestFeeInSourceFeeToken = await convertGasToFeeToken(
17593
+ this.ctx,
17594
+ RELAYER_MESSAGE_GAS,
17595
+ "source",
17596
+ sourceChainId
17597
+ );
17598
+ const postRequestFeeInDestFeeToken = adjustDecimals(
17222
17599
  postRequestFeeInSourceFeeToken,
17223
17600
  sourceFeeToken.decimals,
17224
17601
  destFeeToken.decimals
17225
17602
  );
17226
- const postRequest = {
17227
- source: destChainId,
17228
- dest: sourceChainId,
17229
- body: constructRedeemEscrowRequestBody({ ...order, id: orderCommitment(order) }, MOCK_ADDRESS),
17230
- timeoutTimestamp: 0n,
17231
- nonce,
17232
- from: this.ctx.source.configService.getIntentGatewayAddress(destChainId),
17233
- to: this.ctx.source.configService.getIntentGatewayAddress(sourceChainId)
17603
+ return {
17604
+ postRequestFee: postRequestFeeInDestFeeToken,
17605
+ relayerFeeInSourceFeeToken: postRequestFeeInSourceFeeToken
17234
17606
  };
17235
- postRequestFeeInDestFeeToken = postRequestFeeInDestFeeToken * 1005n / 1000n;
17236
- let protocolFeeInNativeToken = await this.ctx.dest.quoteNative(postRequest, postRequestFeeInDestFeeToken).catch(() => 0n);
17237
- protocolFeeInNativeToken = protocolFeeInNativeToken * 1005n / 1000n;
17238
- return { postRequestFee: postRequestFeeInDestFeeToken, protocolFee: protocolFeeInNativeToken };
17239
17607
  }
17240
17608
  /**
17241
17609
  * Builds EVM state override objects for gas estimation of the `fillOrder`
@@ -17427,6 +17795,178 @@ var OrderStatusChecker = class {
17427
17795
  return true;
17428
17796
  }
17429
17797
  };
17798
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17799
+ var LiquidityEngine = class {
17800
+ /**
17801
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17802
+ * @param chainConfigService - Resolves token decimals for formatted results.
17803
+ */
17804
+ constructor(queryClient, chainConfigService) {
17805
+ this.queryClient = queryClient;
17806
+ this.chainConfigService = chainConfigService;
17807
+ }
17808
+ queryClient;
17809
+ chainConfigService;
17810
+ /**
17811
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17812
+ * indexed output-token liquidity.
17813
+ *
17814
+ * Nexus filters balances to the canonical output token, then aggregates them
17815
+ * overall and by chain. The returned amounts are decimal strings: the total
17816
+ * uses the canonical Base output-token decimals, while each chain group uses
17817
+ * that chain's token decimals. They describe `snapshotTime`, not live
17818
+ * reservations or fill guarantees.
17819
+ *
17820
+ * @param params - Canonical Phantom-market input and output token addresses.
17821
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17822
+ * the directional pair.
17823
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17824
+ * or internally inconsistent snapshot data.
17825
+ */
17826
+ async getAvailableLiquiditySnapshot(params) {
17827
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17828
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17829
+ const response = await this.queryClient.request(
17830
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17831
+ { tokenA: tokenIn, tokenB: tokenOut }
17832
+ );
17833
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17834
+ if (!node) return;
17835
+ const commitment = node.commitment.toLowerCase();
17836
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17837
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17838
+ }
17839
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17840
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17841
+ }
17842
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17843
+ if (Number.isNaN(snapshotTime.getTime())) {
17844
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17845
+ }
17846
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17847
+ commitment,
17848
+ tokenAddress: tokenOut
17849
+ });
17850
+ return {
17851
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17852
+ providerCount,
17853
+ tokenAddress: tokenOut,
17854
+ snapshotTime,
17855
+ liquidityByChain: liquidityByChain.map((group) => ({
17856
+ ...group,
17857
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17858
+ }))
17859
+ };
17860
+ }
17861
+ /**
17862
+ * Requests server-side sums and distinct provider counts for one immutable
17863
+ * snapshot/output-token pair, including chain-level aggregate groups.
17864
+ *
17865
+ * `commitment` uniquely identifies the selected snapshot
17866
+ */
17867
+ async querySnapshotLiquidityAggregates(params) {
17868
+ const response = await this.queryClient.request(
17869
+ LIQUIDITY_PROVIDER_BALANCES,
17870
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17871
+ );
17872
+ const connection = response?.liquidityProviderBalances;
17873
+ const aggregates = connection?.aggregates;
17874
+ if (!connection || !aggregates) {
17875
+ throw new InvalidAvailableLiquiditySnapshotError(
17876
+ params.commitment,
17877
+ "liquidityProviderBalances aggregates are missing"
17878
+ );
17879
+ }
17880
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17881
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17882
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17883
+ const [chain, tokenAddress] = group.keys;
17884
+ if (!chain?.trim() || !tokenAddress) {
17885
+ throw new InvalidAvailableLiquiditySnapshotError(
17886
+ params.commitment,
17887
+ `liquidity group ${index} has invalid keys`
17888
+ );
17889
+ }
17890
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17891
+ tokenAddress,
17892
+ params.commitment,
17893
+ `liquidity group ${index} tokenAddress`
17894
+ );
17895
+ if (normalizedTokenAddress !== params.tokenAddress) {
17896
+ throw new InvalidAvailableLiquiditySnapshotError(
17897
+ params.commitment,
17898
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17899
+ );
17900
+ }
17901
+ return {
17902
+ chain: chain.trim(),
17903
+ tokenAddress: normalizedTokenAddress,
17904
+ totalLiquidity: parseSnapshotBigInt(
17905
+ group.sum.balance ?? "0",
17906
+ params.commitment,
17907
+ `liquidity group ${index} balance`
17908
+ ),
17909
+ providerCount: parseProviderCount(
17910
+ group.distinctCount.providerId,
17911
+ params.commitment,
17912
+ `liquidity group ${index}`
17913
+ )
17914
+ };
17915
+ });
17916
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17917
+ if (groupedTotal !== totalLiquidity) {
17918
+ throw new InvalidAvailableLiquiditySnapshotError(
17919
+ params.commitment,
17920
+ "grouped liquidity does not match the total liquidity"
17921
+ );
17922
+ }
17923
+ return { totalLiquidity, providerCount, liquidityByChain };
17924
+ }
17925
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17926
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17927
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17928
+ if (decimals === void 0) {
17929
+ throw new InvalidAvailableLiquiditySnapshotError(
17930
+ commitment,
17931
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17932
+ );
17933
+ }
17934
+ return formatUnits(amount, decimals);
17935
+ }
17936
+ };
17937
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17938
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17939
+ constructor(commitment, reason) {
17940
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17941
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17942
+ }
17943
+ };
17944
+ function parseSnapshotBigInt(value, commitment, field) {
17945
+ try {
17946
+ const amount = BigInt(value);
17947
+ if (amount < 0n) {
17948
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17949
+ }
17950
+ return amount;
17951
+ } catch (error) {
17952
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17953
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17954
+ }
17955
+ }
17956
+ function parseProviderCount(value, commitment, field) {
17957
+ const count = Number(value);
17958
+ if (!Number.isSafeInteger(count) || count < 0) {
17959
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17960
+ }
17961
+ return count;
17962
+ }
17963
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17964
+ try {
17965
+ return normalizeEvmAddress(address, field);
17966
+ } catch {
17967
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17968
+ }
17969
+ }
17430
17970
 
17431
17971
  // src/protocols/intents/quote/types.ts
17432
17972
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17713,12 +18253,14 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17713
18253
  constructor(chainConfigService, getQueryClient) {
17714
18254
  this.chainConfigService = chainConfigService;
17715
18255
  this.getQueryClient = getQueryClient;
18256
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
17716
18257
  }
17717
18258
  chainConfigService;
17718
18259
  getQueryClient;
18260
+ pairResolver;
17719
18261
  async quote(params, source, destination) {
17720
18262
  validateQuoteParams(params);
17721
- const pair = this.resolveBaseSnapshotPair(params, source.stateMachineId, destination.stateMachineId);
18263
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
17722
18264
  if (!pair) {
17723
18265
  throw new UnsupportedIntentQuotePairError({
17724
18266
  source: source.stateMachineId,
@@ -17791,7 +18333,13 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17791
18333
  throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
17792
18334
  }
17793
18335
  }
17794
- resolveBaseSnapshotPair(params, sourceStateMachineId, destinationStateMachineId) {
18336
+ };
18337
+ var PhantomSnapshotPairResolver = class {
18338
+ constructor(chainConfigService) {
18339
+ this.chainConfigService = chainConfigService;
18340
+ }
18341
+ chainConfigService;
18342
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
17795
18343
  const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
17796
18344
  const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
17797
18345
  if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
@@ -17815,7 +18363,6 @@ function isConfiguredAddress(address) {
17815
18363
  }
17816
18364
 
17817
18365
  // src/protocols/intents/IntentGateway.ts
17818
- var CROSS_CHAIN_ORDER_FEE_GAS_BUMP = 600000n;
17819
18366
  var IntentGateway = class _IntentGateway {
17820
18367
  /** EVM chain on which orders are placed and escrowed. */
17821
18368
  source;
@@ -17843,6 +18390,8 @@ var IntentGateway = class _IntentGateway {
17843
18390
  gasEstimator;
17844
18391
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17845
18392
  quoteStrategies;
18393
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18394
+ phantomSnapshotPairResolver;
17846
18395
  /**
17847
18396
  * Private constructor — use {@link IntentGateway.create} instead.
17848
18397
  *
@@ -17886,6 +18435,7 @@ var IntentGateway = class _IntentGateway {
17886
18435
  this.bidManager = bidManager;
17887
18436
  this.gasEstimator = gasEstimator;
17888
18437
  this._crypto = crypto;
18438
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17889
18439
  this.quoteStrategies = {
17890
18440
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
17891
18441
  dest.configService,
@@ -17964,15 +18514,47 @@ var IntentGateway = class _IntentGateway {
17964
18514
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17965
18515
  return handler.quote({ ...params, strategy }, source, destination);
17966
18516
  }
18517
+ /**
18518
+ * Returns the output-token liquidity measured in the latest directional
18519
+ * Phantom snapshot for this gateway's source and destination.
18520
+ *
18521
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18522
+ * The snapshot itself determines the output token and chain to aggregate. The
18523
+ * amount is in the token's smallest unit and reflects the indexer's
18524
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18525
+ *
18526
+ * Requires a prior call to {@link withQueryClient}.
18527
+ */
18528
+ async queryAvailableLiquidity(params) {
18529
+ const { queryClient } = this.requireIndexer();
18530
+ const sourceStateMachineId = this.source.config.stateMachineId;
18531
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18532
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18533
+ if (!pair) {
18534
+ throw new UnsupportedIntentQuotePairError({
18535
+ source: sourceStateMachineId,
18536
+ destination: destinationStateMachineId,
18537
+ tokenIn: params.tokenIn,
18538
+ tokenOut: params.tokenOut,
18539
+ quoteSource: "Phantom snapshot pair"
18540
+ });
18541
+ }
18542
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18543
+ tokenIn: pair.tokenA,
18544
+ tokenOut: pair.tokenB
18545
+ });
18546
+ }
17967
18547
  /**
17968
18548
  * Bidirectional async generator that orchestrates the full order lifecycle:
17969
18549
  * placement, fee estimation, bid collection, and execution.
17970
18550
  *
17971
18551
  * **Yield/receive protocol:**
17972
- * 1. If `order.fees` is unset or zero, estimates gas and sets same-chain
17973
- * `order.fees` to twice the estimate. Cross-chain orders retain a 1% buffer
17974
- * and include an additional 600k-gas fee uplift, while the wei cost used for
17975
- * the `value` field receives a 2% buffer.
18552
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and
18553
+ * sets same-chain fees to twice the estimate. Cross-chain orders attach
18554
+ * (fill gas + a settlement-message uplift of `RELAYER_MESSAGE_GAS`, the
18555
+ * same gas budget the solver's relayer fee uses) with a 5% buffer over
18556
+ * the whole sum — strictly above the solver's unpadded requirement. The
18557
+ * wei cost used for the `value` field receives a 2% buffer.
17976
18558
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
17977
18559
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
17978
18560
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
@@ -17980,8 +18562,8 @@ var IntentGateway = class _IntentGateway {
17980
18562
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17981
18563
  * subsequent status updates until the order is filled, exhausted, or fails.
17982
18564
  *
17983
- * @param order - The order to place and execute. `order.fees` may be 0; it
17984
- * will be estimated automatically if so.
18565
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18566
+ * may be 0; fees are estimated automatically if so.
17985
18567
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17986
18568
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17987
18569
  * @param options - Optional tuning parameters:
@@ -17994,10 +18576,11 @@ var IntentGateway = class _IntentGateway {
17994
18576
  * estimation returns zero.
17995
18577
  */
17996
18578
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18579
+ const executionOrder = { ...order };
17997
18580
  let value;
17998
- if (!order.fees || order.fees === 0n) {
18581
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17999
18582
  const estimate = await this.gasEstimator.estimateFillOrder({
18000
- order,
18583
+ order: executionOrder,
18001
18584
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
18002
18585
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
18003
18586
  });
@@ -18008,13 +18591,13 @@ var IntentGateway = class _IntentGateway {
18008
18591
  value = estimate.totalGasCostWei + estimate.totalGasCostWei * 2n / 100n;
18009
18592
  const crossChainFeeBump = isSameChain ? 0n : await convertGasToFeeToken(
18010
18593
  this.ctx,
18011
- CROSS_CHAIN_ORDER_FEE_GAS_BUMP,
18594
+ RELAYER_MESSAGE_GAS,
18012
18595
  "source",
18013
18596
  this.source.config.stateMachineId
18014
18597
  );
18015
- order.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
18598
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : (estimate.totalGasInFeeToken + crossChainFeeBump) * 105n / 100n;
18016
18599
  }
18017
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18600
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
18018
18601
  const placeOrderFirst = await placeOrderGen.next();
18019
18602
  if (placeOrderFirst.done) {
18020
18603
  throw new Error("placeOrder generator completed without yielding");
@@ -18125,13 +18708,20 @@ var IntentGateway = class _IntentGateway {
18125
18708
  const gen = this.execute(order, graffiti, options);
18126
18709
  try {
18127
18710
  let input;
18711
+ let finalizedOrder;
18128
18712
  while (true) {
18129
18713
  const { value, done } = await gen.next(input);
18130
18714
  input = void 0;
18131
18715
  if (done) break;
18132
- if (value.status === "BIDS_RECEIVED") {
18716
+ if (value.status === "ORDER_PLACED") {
18717
+ finalizedOrder = value.order;
18133
18718
  yield value;
18134
- input = await this.autoSelect(order, value.bids);
18719
+ } else if (value.status === "BIDS_RECEIVED") {
18720
+ if (!finalizedOrder) {
18721
+ throw new Error("Received bids before the order was finalized");
18722
+ }
18723
+ yield value;
18724
+ input = await this.autoSelect(finalizedOrder, value.bids);
18135
18725
  } else if (value.status === "AWAITING_PLACE_ORDER") {
18136
18726
  input = yield value;
18137
18727
  } else {
@@ -22909,6 +23499,6 @@ async function teleportDot(param_) {
22909
23499
  return stream;
22910
23500
  }
22911
23501
 
22912
- 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 };
23502
+ 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 };
22913
23503
  //# sourceMappingURL=index.js.map
22914
23504
  //# sourceMappingURL=index.js.map