@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.
@@ -2689,7 +2689,11 @@ var chainConfigs = {
2689
2689
  DAI: "0x6b175474e89094c44da98b954eedeac495271d0f",
2690
2690
  USDC: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
2691
2691
  USDT: "0xdac17f958d2ee523a2206206994597c13d831ec7",
2692
- cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab"
2692
+ cNGN: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab",
2693
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2694
+ EURC: "0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
2695
+ XSGD: "0x70e8dE73cE538DA2bEEd35d14187F6959a8ecA96",
2696
+ TRYB: "0x2C537E5624e4af88A7ae4060C022609376C8D0EB"
2693
2697
  },
2694
2698
  tokenDecimals: {
2695
2699
  USDC: 6,
@@ -2720,6 +2724,7 @@ var chainConfigs = {
2720
2724
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2721
2725
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2722
2726
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2727
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2723
2728
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2724
2729
  },
2725
2730
  rpcEnvKey: "ETH_MAINNET",
@@ -2773,7 +2778,8 @@ var chainConfigs = {
2773
2778
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2774
2779
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2775
2780
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2776
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2781
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2782
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2777
2783
  // "Usdt0Oft": Not available on BSC
2778
2784
  },
2779
2785
  rpcEnvKey: "BSC_MAINNET",
@@ -2831,6 +2837,7 @@ var chainConfigs = {
2831
2837
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2832
2838
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2833
2839
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2840
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2834
2841
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2835
2842
  },
2836
2843
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2856,7 +2863,9 @@ var chainConfigs = {
2856
2863
  USDC: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
2857
2864
  USDT: "0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",
2858
2865
  EXT: "0x0e668E5127087e236578893a0e01E41837A28469",
2859
- cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F"
2866
+ cNGN: "0x46C85152bFe9f96829aA94755D9f915F9B10EF5F",
2867
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2868
+ EURC: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
2860
2869
  },
2861
2870
  tokenDecimals: {
2862
2871
  USDC: 6,
@@ -2885,6 +2894,7 @@ var chainConfigs = {
2885
2894
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2886
2895
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2887
2896
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2897
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2888
2898
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2889
2899
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2890
2900
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2915,7 +2925,9 @@ var chainConfigs = {
2915
2925
  USDC: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
2916
2926
  USDT: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
2917
2927
  EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
2918
- cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B"
2928
+ cNGN: "0x52828daa48C1a9A06F37500882b42daf0bE04C3B",
2929
+ ZARP: "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
2930
+ XSGD: "0xDC3326e71D45186F113a2F448984CA0e8D201995"
2919
2931
  },
2920
2932
  tokenDecimals: {
2921
2933
  USDC: 6,
@@ -2947,6 +2959,7 @@ var chainConfigs = {
2947
2959
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2948
2960
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2949
2961
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2962
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2950
2963
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2951
2964
  },
2952
2965
  rpcEnvKey: "POLYGON_MAINNET",
@@ -5151,6 +5164,21 @@ var ChainConfigService = class {
5151
5164
  getCNgnAsset(chain) {
5152
5165
  return this.getConfig(chain)?.assets?.cNGN;
5153
5166
  }
5167
+ /**
5168
+ * Address of `symbol` on `chain` from the per-chain asset table, matched
5169
+ * case-insensitively ("cNGN" ≡ "CNGN"). This table is the single source of
5170
+ * truth for token addresses — the simplex asset registry resolves through
5171
+ * it, so a new asset is added once in `chain.ts` and nowhere else.
5172
+ */
5173
+ getAssetBySymbol(chain, symbol) {
5174
+ const assets = this.getConfig(chain)?.assets;
5175
+ if (!assets) return void 0;
5176
+ const target = symbol.trim().toUpperCase();
5177
+ for (const [key, address] of Object.entries(assets)) {
5178
+ if (key.toUpperCase() === target) return address;
5179
+ }
5180
+ return void 0;
5181
+ }
5154
5182
  getCNgnDecimals(chain) {
5155
5183
  return this.getConfig(chain)?.tokenDecimals?.cNGN;
5156
5184
  }
@@ -5242,6 +5270,9 @@ var ChainConfigService = class {
5242
5270
  getCirclePaymasterAddress(chain) {
5243
5271
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5244
5272
  }
5273
+ getSimplexPaymasterAddress(chain) {
5274
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5275
+ }
5245
5276
  getHyperbridgeAddress() {
5246
5277
  return hyperbridgeAddress;
5247
5278
  }
@@ -7062,6 +7093,16 @@ var ExpectedError = class _ExpectedError extends Error {
7062
7093
  return error instanceof _ExpectedError;
7063
7094
  }
7064
7095
  };
7096
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7097
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7098
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7099
+ super(message, options);
7100
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7101
+ }
7102
+ static isError(error) {
7103
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7104
+ }
7105
+ };
7065
7106
 
7066
7107
  // src/configs/constants.ts
7067
7108
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7375,8 +7416,15 @@ var SubstrateChain = class _SubstrateChain {
7375
7416
  id: stateMachineId,
7376
7417
  height: Number(stateMachineHeight.height)
7377
7418
  };
7378
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7379
- return BigInt(updateTime);
7419
+ try {
7420
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7421
+ return BigInt(updateTime);
7422
+ } catch (error) {
7423
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7424
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7425
+ }
7426
+ throw error;
7427
+ }
7380
7428
  }
7381
7429
  /**
7382
7430
  * Get the challenge period for a given state machine id.
@@ -8122,26 +8170,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8122
8170
  };
8123
8171
  }
8124
8172
  /**
8125
- * Subscribes to PhantomOrderRegistered events from the intents coprocessor pallet.
8126
- * Calls the callback for each new phantom order as blocks arrive.
8127
- * Returns an unsubscribe function to stop the subscription.
8128
- */
8129
- async subscribePhantomOrders(callback) {
8130
- const unsub = await this.api.query.system.events((records) => {
8131
- for (const { event } of records) {
8132
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8133
- const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8134
- callback({
8135
- commitment: commitment.toHex(),
8136
- chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8137
- createdAt: createdAt.toNumber(),
8138
- tokenA: tokenA.toHex(),
8139
- tokenB: tokenB.toHex(),
8140
- standardAmount: BigInt(standardAmount.toString())
8141
- });
8173
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8174
+ */
8175
+ async getPhantomOrdersInBlock(blockNumber) {
8176
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8177
+ const apiAt = await this.api.at(blockHash);
8178
+ const records = await apiAt.query.system.events();
8179
+ const orders = [];
8180
+ for (const { event } of records) {
8181
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8182
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8183
+ orders.push({
8184
+ commitment: commitment.toHex(),
8185
+ chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8186
+ createdAt: createdAt.toNumber(),
8187
+ tokenA: tokenA.toHex(),
8188
+ tokenB: tokenB.toHex(),
8189
+ standardAmount: BigInt(standardAmount.toString())
8190
+ });
8191
+ }
8192
+ return orders;
8193
+ }
8194
+ /**
8195
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8196
+ *
8197
+ * Each tick reads the current head and scans every block between the last one processed and that
8198
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8199
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8200
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8201
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8202
+ * meant silently missed bids.
8203
+ *
8204
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8205
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8206
+ * read. Recovery replays the backlog.
8207
+ *
8208
+ * Returns a function that stops polling.
8209
+ */
8210
+ pollPhantomOrders(callback, options = {}) {
8211
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8212
+ let cursor = null;
8213
+ let inFlight = false;
8214
+ let stopped = false;
8215
+ const tick = async () => {
8216
+ if (inFlight || stopped) return;
8217
+ inFlight = true;
8218
+ try {
8219
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8220
+ if (cursor === null) {
8221
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8222
+ }
8223
+ if (head <= cursor) return;
8224
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8225
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8226
+ if (stopped) return;
8227
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8228
+ for (const order of orders) callback(order);
8229
+ cursor = blockNumber;
8230
+ }
8231
+ } catch (err) {
8232
+ onError?.(err);
8233
+ } finally {
8234
+ inFlight = false;
8142
8235
  }
8143
- });
8144
- return unsub;
8236
+ };
8237
+ void tick();
8238
+ const timer = setInterval(() => void tick(), intervalMs);
8239
+ return () => {
8240
+ stopped = true;
8241
+ clearInterval(timer);
8242
+ };
8145
8243
  }
8146
8244
  };
8147
8245
  var TronChain = class _TronChain {
@@ -9274,7 +9372,12 @@ function sleep(ms) {
9274
9372
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9275
9373
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9276
9374
  if (challengePeriod === BigInt(0)) return;
9277
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9375
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9376
+ maxRetries: 3,
9377
+ backoffMs: 500,
9378
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9379
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9380
+ });
9278
9381
  let currentTimestamp = await chain.timestamp();
9279
9382
  let timeElapsed = currentTimestamp - updateTime;
9280
9383
  if (timeElapsed > challengePeriod) return;
@@ -9421,6 +9524,10 @@ function normalizeAddressForEvmBytes32(address) {
9421
9524
  if (address.length === 66) return address;
9422
9525
  return bytes20ToBytes32(address);
9423
9526
  }
9527
+ function normalizeEvmAddress(address, field = "address") {
9528
+ if (!viem.isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9529
+ return address.toLowerCase();
9530
+ }
9424
9531
  function normalizeAddressForStateMachine(address, stateMachineId) {
9425
9532
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9426
9533
  }
@@ -9428,14 +9535,16 @@ var DEFAULT_LOGGER = consola.createConsola({
9428
9535
  level: consola.LogLevels.silent
9429
9536
  });
9430
9537
  async function retryPromise(operation, retryConfig) {
9431
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9538
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9432
9539
  let lastError;
9433
9540
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9434
9541
  try {
9435
9542
  return await operation();
9436
9543
  } catch (error) {
9544
+ if (shouldRetry && !shouldRetry(error)) throw error;
9437
9545
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9438
9546
  lastError = error;
9547
+ if (i === retryConfig.maxRetries - 1) break;
9439
9548
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9440
9549
  }
9441
9550
  }
@@ -11506,6 +11615,55 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11506
11615
  }
11507
11616
  }
11508
11617
  }`;
11618
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11619
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11620
+ phantomOrderPriceSnapshots(
11621
+ filter: {
11622
+ and: [
11623
+ { tokenA: { equalTo: $tokenA } }
11624
+ { tokenB: { equalTo: $tokenB } }
11625
+ ]
11626
+ }
11627
+ orderBy: SNAPSHOT_TIME_DESC
11628
+ first: 1
11629
+ ) {
11630
+ nodes {
11631
+ commitment
11632
+ tokenA
11633
+ tokenB
11634
+ snapshotTime
11635
+ }
11636
+ }
11637
+ }`;
11638
+ var LIQUIDITY_PROVIDER_BALANCES = `
11639
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11640
+ liquidityProviderBalances(
11641
+ filter: {
11642
+ and: [
11643
+ { commitment: { equalTo: $commitment } }
11644
+ { tokenAddress: { equalTo: $tokenAddress } }
11645
+ ]
11646
+ }
11647
+ ) {
11648
+ aggregates {
11649
+ sum {
11650
+ balance
11651
+ }
11652
+ distinctCount {
11653
+ providerId
11654
+ }
11655
+ }
11656
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11657
+ keys
11658
+ sum {
11659
+ balance
11660
+ }
11661
+ distinctCount {
11662
+ providerId
11663
+ }
11664
+ }
11665
+ }
11666
+ }`;
11509
11667
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11510
11668
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11511
11669
  tokenGatewayAssetTeleportedV2s(
@@ -12090,6 +12248,7 @@ function createCancellationStorage(options = {}) {
12090
12248
  const decoded = decodeIProof(value);
12091
12249
  return decoded;
12092
12250
  }
12251
+ if (key2.includes("postCommitment")) return value;
12093
12252
  throw new Error(`Unknown storage key type: ${key2}`);
12094
12253
  };
12095
12254
  const setItem = async (key2, value) => {
@@ -12103,6 +12262,10 @@ function createCancellationStorage(options = {}) {
12103
12262
  await baseStorage.setItem(key2, encoded);
12104
12263
  return;
12105
12264
  }
12265
+ if (key2.includes("postCommitment") && typeof value === "string") {
12266
+ await baseStorage.setItem(key2, value);
12267
+ return;
12268
+ }
12106
12269
  throw new Error(`Unknown storage key type: ${key2}`);
12107
12270
  };
12108
12271
  const removeItem = async (key2) => {
@@ -12116,11 +12279,25 @@ function createCancellationStorage(options = {}) {
12116
12279
  });
12117
12280
  }
12118
12281
  var STORAGE_KEYS = Object.freeze({
12119
- destProof: (orderId) => `cancel-order:${orderId}:destProof`,
12120
- getRequest: (orderId) => `cancel-order:${orderId}:getRequest`,
12121
- sourceProof: (orderId) => `cancel-order:${orderId}:sourceProof`,
12122
- postCommitment: (orderId) => `cancel-order:${orderId}:postCommitment`
12282
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12283
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12284
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12285
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12123
12286
  });
12287
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12288
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12289
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12290
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12291
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12292
+ });
12293
+ function cancellationScope(orderId, source, destination) {
12294
+ if (!source || !destination) return orderId;
12295
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12296
+ }
12297
+ function legacyCancellationScope(orderId, source, destination) {
12298
+ if (!source || !destination) return orderId;
12299
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12300
+ }
12124
12301
  function createUsedUserOpsStorage(options = {}) {
12125
12302
  options.env ?? detectEnvironment();
12126
12303
  const driver = loadDriver({ options }) ?? inMemoryDriver__default.default();
@@ -15286,6 +15463,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15286
15463
  }
15287
15464
 
15288
15465
  // src/protocols/intents/OrderPlacer.ts
15466
+ function deriveCanonicalPlacedOrder(order, args) {
15467
+ const canonicalOrder = {
15468
+ ...order,
15469
+ user: args.user,
15470
+ source: args.source,
15471
+ destination: args.destination,
15472
+ deadline: args.deadline,
15473
+ nonce: args.nonce,
15474
+ fees: args.fees,
15475
+ session: args.session,
15476
+ predispatch: {
15477
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15478
+ call: order.predispatch.call
15479
+ },
15480
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15481
+ output: {
15482
+ beneficiary: args.beneficiary,
15483
+ assets: args.outputs.map((asset) => ({ ...asset })),
15484
+ call: order.output.call
15485
+ }
15486
+ };
15487
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15488
+ }
15289
15489
  var OrderPlacer = class {
15290
15490
  /**
15291
15491
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15302,17 +15502,16 @@ var OrderPlacer = class {
15302
15502
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15303
15503
  * transaction sending `data` to `to` and pass the signed transaction back
15304
15504
  * via `gen.next(signedTx)`.
15305
- * 2. Returns `{ order, transactionHash }` — the finalized order with its
15306
- * on-chain `nonce`, `inputs`, and computed `id`, plus the placement
15307
- * transaction hash.
15505
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15506
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15507
+ * placement transaction hash.
15308
15508
  *
15309
15509
  * A fresh ephemeral session key is generated for every call. The key is
15310
15510
  * stored immediately (without a commitment) so it can be retrieved by
15311
15511
  * address, then updated with the commitment once the `OrderPlaced` event
15312
- * confirms the nonce.
15512
+ * confirms the canonical order.
15313
15513
  *
15314
- * @param order - The order to place. `order.session` is mutated in-place
15315
- * with the generated session key address before yielding.
15514
+ * @param order - The order to place. It is not mutated.
15316
15515
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15317
15516
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15318
15517
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15327,7 +15526,7 @@ var OrderPlacer = class {
15327
15526
  const account = accounts.privateKeyToAccount(privateKey);
15328
15527
  const sessionKeyAddress = account.address;
15329
15528
  const createdAt = Date.now();
15330
- order.session = sessionKeyAddress;
15529
+ const placementOrder = { ...order, session: sessionKeyAddress };
15331
15530
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15332
15531
  privateKey,
15333
15532
  address: sessionKeyAddress,
@@ -15336,7 +15535,7 @@ var OrderPlacer = class {
15336
15535
  const data = viem.encodeFunctionData({
15337
15536
  abi: ABI3,
15338
15537
  functionName: "placeOrder",
15339
- args: [transformOrderForContract(order), graffiti]
15538
+ args: [transformOrderForContract(placementOrder), graffiti]
15340
15539
  });
15341
15540
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15342
15541
  normalizeStateMachineId(order.source)
@@ -15356,20 +15555,15 @@ var OrderPlacer = class {
15356
15555
  if (!orderPlacedEvent) {
15357
15556
  throw new Error("OrderPlaced event not found in transaction receipt");
15358
15557
  }
15359
- order.nonce = orderPlacedEvent.args.nonce;
15360
- order.inputs = orderPlacedEvent.args.inputs.map((input) => ({
15361
- token: input.token,
15362
- amount: input.amount
15363
- }));
15364
- order.id = orderCommitment(order);
15558
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15365
15559
  const sessionKeyData = {
15366
15560
  privateKey,
15367
15561
  address: sessionKeyAddress,
15368
- commitment: order.id,
15562
+ commitment: finalizedOrder.id,
15369
15563
  createdAt
15370
15564
  };
15371
15565
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15372
- return { order, receipt };
15566
+ return { order: finalizedOrder, receipt };
15373
15567
  }
15374
15568
  };
15375
15569
 
@@ -15699,7 +15893,7 @@ var OrderExecutor = class {
15699
15893
  }
15700
15894
  }
15701
15895
  };
15702
- var OrderCanceller = class {
15896
+ var OrderCanceller = class _OrderCanceller {
15703
15897
  /**
15704
15898
  * @param ctx - Shared IntentsV2 context providing the source and destination
15705
15899
  * chain clients, config service, and cancellation storage.
@@ -15708,6 +15902,9 @@ var OrderCanceller = class {
15708
15902
  this.ctx = ctx;
15709
15903
  }
15710
15904
  ctx;
15905
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15906
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15907
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15711
15908
  logger = consola.createConsola({
15712
15909
  level: consola.LogLevels.info,
15713
15910
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15739,11 +15936,14 @@ var OrderCanceller = class {
15739
15936
  * @returns The native token dispatch fee in wei.
15740
15937
  */
15741
15938
  async quoteCancelFromSource(order) {
15742
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15743
15939
  const sourceStateMachine = normalizeStateMachineId(order.source);
15940
+ const destStateMachine = normalizeStateMachineId(order.destination);
15941
+ if (sourceStateMachine === destStateMachine) {
15942
+ return { nativeValue: 0n, relayerFee: 0n };
15943
+ }
15744
15944
  const height = order.deadline + 1n;
15745
15945
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15746
- normalizeStateMachineId(order.destination)
15946
+ destStateMachine
15747
15947
  );
15748
15948
  const slotHash = await this.ctx.dest.client.readContract({
15749
15949
  abi: ABI3,
@@ -15755,8 +15955,8 @@ var OrderCanceller = class {
15755
15955
  const context = encodeWithdrawalRequest(order, order.user);
15756
15956
  const getRequest = {
15757
15957
  source: sourceStateMachine,
15758
- dest: normalizeStateMachineId(order.destination),
15759
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
15958
+ dest: destStateMachine,
15959
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15760
15960
  nonce: await this.ctx.source.getHostNonce(),
15761
15961
  height,
15762
15962
  keys: [key],
@@ -15773,7 +15973,9 @@ var OrderCanceller = class {
15773
15973
  * cancellation is complete.
15774
15974
  *
15775
15975
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15776
- * the `from` parameter.
15976
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
15977
+ * source-side GET recovery, clears its stale checkpoint and restarts the
15978
+ * source-side stream internally.
15777
15979
  *
15778
15980
  * @param order - The order to cancel.
15779
15981
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15783,12 +15985,32 @@ var OrderCanceller = class {
15783
15985
  * cancellation lifecycle.
15784
15986
  */
15785
15987
  async *cancelOrder(order, indexerClient, options = {}) {
15786
- const isSameChain = order.source === order.destination;
15988
+ const sourceStateMachine = normalizeStateMachineId(order.source);
15989
+ const destStateMachine = normalizeStateMachineId(order.destination);
15990
+ const isSameChain = sourceStateMachine === destStateMachine;
15787
15991
  if (options.from === "destination" && !isSameChain) {
15788
15992
  yield* this.cancelOrderFromDest(order, indexerClient);
15789
15993
  return;
15790
15994
  }
15791
- yield* this.cancelOrderFromSource(order, indexerClient);
15995
+ let recoveryRestarts = 0;
15996
+ while (true) {
15997
+ try {
15998
+ yield* this.cancelOrderFromSource(order, indexerClient);
15999
+ return;
16000
+ } catch (error) {
16001
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16002
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
16003
+ throw new Error(
16004
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
16005
+ );
16006
+ }
16007
+ recoveryRestarts += 1;
16008
+ this.logger.warn(
16009
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
16010
+ );
16011
+ await this.clearGetRecoveryCache(order);
16012
+ }
16013
+ }
15792
16014
  }
15793
16015
  /**
15794
16016
  * Async generator that cancels an order by initiating the cancel from the
@@ -15814,11 +16036,10 @@ var OrderCanceller = class {
15814
16036
  * @throws If the cancel transaction does not contain the expected on-chain event.
15815
16037
  */
15816
16038
  async *cancelOrderFromSource(order, indexerClient) {
15817
- const orderId = order.id;
15818
- const isSameChain = order.source === order.destination;
15819
- const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15820
- normalizeStateMachineId(order.source)
15821
- );
16039
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16040
+ const destStateMachine = normalizeStateMachineId(order.destination);
16041
+ const isSameChain = sourceStateMachine === destStateMachine;
16042
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15822
16043
  if (isSameChain) {
15823
16044
  const data = viem.encodeFunctionData({
15824
16045
  abi: ABI3,
@@ -15847,18 +16068,37 @@ var OrderCanceller = class {
15847
16068
  };
15848
16069
  return;
15849
16070
  }
16071
+ const storageKeys = this.recoveryStorageKeys(order);
16072
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15850
16073
  const hyperbridge = indexerClient.hyperbridge;
15851
- const sourceStateMachine = normalizeStateMachineId(order.source);
15852
16074
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15853
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16075
+ let destIProof = await this.getRecoveryItem(
16076
+ storageKeys.destProof,
16077
+ legacyStorageKeys.map((keys) => keys.destProof)
16078
+ );
15854
16079
  if (!destIProof) {
15855
16080
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15856
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16081
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15857
16082
  } else {
15858
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16083
+ let refreshed = false;
16084
+ try {
16085
+ await this.assertProofFresh(hyperbridge, destIProof);
16086
+ } catch (error) {
16087
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16088
+ await this.removeRecoveryItems(
16089
+ storageKeys.destProof,
16090
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16091
+ );
16092
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16093
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16094
+ refreshed = true;
16095
+ }
16096
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15859
16097
  }
15860
- let getRequest = await this.ctx.cancellationStorage.getItem(
15861
- STORAGE_KEYS.getRequest(orderId)
16098
+ await this.assertProofFresh(hyperbridge, destIProof);
16099
+ let getRequest = await this.getRecoveryItem(
16100
+ storageKeys.getRequest,
16101
+ legacyStorageKeys.map((keys) => keys.getRequest)
15862
16102
  );
15863
16103
  if (!getRequest) {
15864
16104
  const quote = await this.quoteCancelFromSource(order);
@@ -15878,7 +16118,7 @@ var OrderCanceller = class {
15878
16118
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15879
16119
  if (!request) throw new Error("GetRequest missing");
15880
16120
  getRequest = request.args;
15881
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16121
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15882
16122
  yield {
15883
16123
  status: "CANCEL_STARTED",
15884
16124
  receipt
@@ -15897,8 +16137,9 @@ var OrderCanceller = class {
15897
16137
  metadata: statusUpdate.metadata
15898
16138
  };
15899
16139
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15900
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15901
- STORAGE_KEYS.sourceProof(orderId)
16140
+ let sourceIProof = await this.getRecoveryItem(
16141
+ storageKeys.sourceProof,
16142
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15902
16143
  );
15903
16144
  if (!sourceIProof) {
15904
16145
  sourceIProof = await fetchSourceProof(
@@ -15908,8 +16149,9 @@ var OrderCanceller = class {
15908
16149
  sourceConsensusStateId,
15909
16150
  sourceHeight
15910
16151
  );
15911
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16152
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15912
16153
  }
16154
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15913
16155
  await waitForChallengePeriod(hyperbridge, {
15914
16156
  height: sourceIProof.height,
15915
16157
  id: {
@@ -15917,6 +16159,10 @@ var OrderCanceller = class {
15917
16159
  consensusStateId: sourceConsensusStateId
15918
16160
  }
15919
16161
  });
16162
+ await Promise.all([
16163
+ this.assertProofFresh(hyperbridge, sourceIProof),
16164
+ this.assertProofFresh(hyperbridge, destIProof)
16165
+ ]);
15920
16166
  const getRequestMessage = {
15921
16167
  kind: "GetRequest",
15922
16168
  requests: [getRequest],
@@ -15938,9 +16184,12 @@ var OrderCanceller = class {
15938
16184
  status: "HYPERBRIDGE_FINALIZED",
15939
16185
  metadata: statusUpdate.metadata
15940
16186
  };
15941
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.destProof(orderId));
15942
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.getRequest(orderId));
15943
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.sourceProof(orderId));
16187
+ await this.removeRecoveryItems(
16188
+ storageKeys.destProof,
16189
+ storageKeys.getRequest,
16190
+ storageKeys.sourceProof,
16191
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16192
+ );
15944
16193
  return;
15945
16194
  }
15946
16195
  }
@@ -15957,9 +16206,11 @@ var OrderCanceller = class {
15957
16206
  * @returns The native token dispatch fee in wei.
15958
16207
  */
15959
16208
  async quoteCancelFromDest(order) {
15960
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15961
- const destStateMachine = normalizeStateMachineId(order.destination);
15962
16209
  const sourceStateMachine = normalizeStateMachineId(order.source);
16210
+ const destStateMachine = normalizeStateMachineId(order.destination);
16211
+ if (sourceStateMachine === destStateMachine) {
16212
+ return { nativeValue: 0n, relayerFee: 0n };
16213
+ }
15963
16214
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15964
16215
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15965
16216
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -15997,11 +16248,13 @@ var OrderCanceller = class {
15997
16248
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
15998
16249
  */
15999
16250
  async *cancelOrderFromDest(order, indexerClient) {
16000
- const orderId = order.id;
16251
+ const storageKeys = this.recoveryStorageKeys(order);
16001
16252
  const destStateMachine = normalizeStateMachineId(order.destination);
16002
16253
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
16003
- let commitment = await this.ctx.cancellationStorage.getItem(
16004
- STORAGE_KEYS.postCommitment(orderId)
16254
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16255
+ let commitment = await this.getRecoveryItem(
16256
+ storageKeys.postCommitment,
16257
+ legacyStorageKeys.map((keys) => keys.postCommitment)
16005
16258
  );
16006
16259
  if (!commitment) {
16007
16260
  const quote = await this.quoteCancelFromDest(order);
@@ -16026,7 +16279,7 @@ var OrderCanceller = class {
16026
16279
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
16027
16280
  const postArgs = postEvent.args;
16028
16281
  commitment = postRequestCommitment(postArgs).commitment;
16029
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16282
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
16030
16283
  }
16031
16284
  const statusStream = indexerClient.postRequestStatusStream(commitment);
16032
16285
  for await (const statusUpdate of statusStream) {
@@ -16060,7 +16313,10 @@ var OrderCanceller = class {
16060
16313
  if (refundEvents.length === 0) {
16061
16314
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
16062
16315
  }
16063
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16316
+ await this.removeRecoveryItems(
16317
+ storageKeys.postCommitment,
16318
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16319
+ );
16064
16320
  yield {
16065
16321
  status: "CANCELLATION_COMPLETE",
16066
16322
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16103,7 +16359,7 @@ var OrderCanceller = class {
16103
16359
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16104
16360
  this.ctx.dest.config.stateMachineId
16105
16361
  );
16106
- const orderId = order.id;
16362
+ const orderId = this.orderId(order);
16107
16363
  const slotHash = await this.ctx.dest.client.readContract({
16108
16364
  abi: ABI3,
16109
16365
  address: intentGatewayV2Address,
@@ -16120,6 +16376,7 @@ var OrderCanceller = class {
16120
16376
  yield { status: "DESTINATION_FINALIZED", proof };
16121
16377
  return proof;
16122
16378
  } catch (e) {
16379
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16123
16380
  lastFailedHeight = latestHeight;
16124
16381
  await sleep(1e4);
16125
16382
  }
@@ -16150,6 +16407,7 @@ var OrderCanceller = class {
16150
16407
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16151
16408
  await sleep(3e4);
16152
16409
  } catch (error) {
16410
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16153
16411
  this.logger.warn(
16154
16412
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16155
16413
  );
@@ -16158,14 +16416,128 @@ var OrderCanceller = class {
16158
16416
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16159
16417
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16160
16418
  } catch (error) {
16419
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16161
16420
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16162
16421
  this.logger.error(message2);
16163
16422
  throw new Error(message2);
16164
16423
  }
16165
16424
  }
16425
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16426
+ async assertProofFresh(hyperbridge, proof) {
16427
+ await retryPromise(
16428
+ () => hyperbridge.stateMachineUpdateTime({
16429
+ height: proof.height,
16430
+ id: {
16431
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16432
+ consensusStateId: proof.consensusStateId
16433
+ }
16434
+ }),
16435
+ {
16436
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16437
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16438
+ logger: this.logger,
16439
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16440
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16441
+ }
16442
+ );
16443
+ }
16444
+ /**
16445
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16446
+ * a consensus update required by that recovery attempt.
16447
+ */
16448
+ async clearGetRecoveryCache(order) {
16449
+ const keys = this.recoveryStorageKeys(order);
16450
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16451
+ await this.removeRecoveryItems(
16452
+ keys.destProof,
16453
+ keys.sourceProof,
16454
+ keys.getRequest,
16455
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16456
+ legacyKeys.destProof,
16457
+ legacyKeys.sourceProof,
16458
+ legacyKeys.getRequest
16459
+ ])
16460
+ );
16461
+ }
16462
+ /**
16463
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16464
+ * for an order.
16465
+ *
16466
+ * @param order - The order whose source/destination recovery state is stored.
16467
+ */
16468
+ recoveryStorageKeys(order) {
16469
+ const orderId = this.orderId(order);
16470
+ return {
16471
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16472
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16473
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16474
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16475
+ };
16476
+ }
16477
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16478
+ legacyRecoveryStorageKeys(order) {
16479
+ const orderId = this.orderId(order);
16480
+ return [
16481
+ {
16482
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16483
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16484
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16485
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16486
+ },
16487
+ {
16488
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16489
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16490
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16491
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16492
+ }
16493
+ ];
16494
+ }
16495
+ async getRecoveryItem(currentKey, legacyKeys) {
16496
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16497
+ if (current) return current;
16498
+ for (const legacyKey of new Set(legacyKeys)) {
16499
+ if (legacyKey === currentKey) continue;
16500
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16501
+ if (!legacy) continue;
16502
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16503
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16504
+ return legacy;
16505
+ }
16506
+ return null;
16507
+ }
16508
+ async removeRecoveryItems(...keys) {
16509
+ await Promise.all(
16510
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16511
+ );
16512
+ }
16513
+ /**
16514
+ * Returns the order identifier required to persist or clear recovery state.
16515
+ *
16516
+ * @param order - The order being cancelled.
16517
+ * @throws If the order has no identifier.
16518
+ */
16519
+ orderId(order) {
16520
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16521
+ return order.id;
16522
+ }
16523
+ /**
16524
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16525
+ *
16526
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16527
+ * @param commitment - Commitment of the GET request.
16528
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16529
+ */
16166
16530
  async queryDeliveredReceipt(hyperbridge, commitment) {
16167
16531
  return hyperbridge.queryResponseReceipt(commitment);
16168
16532
  }
16533
+ /**
16534
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16535
+ *
16536
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16537
+ * @param commitment - Commitment of the GET request.
16538
+ * @returns The delivered response receipt commitment.
16539
+ * @throws If no receipt is observed within the configured retry limit.
16540
+ */
16169
16541
  async pollDeliveredReceipt(hyperbridge, commitment) {
16170
16542
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16171
16543
  return retryPromise(
@@ -16903,6 +17275,7 @@ var BidManager = class {
16903
17275
  }
16904
17276
  }
16905
17277
  };
17278
+ var RELAYER_MESSAGE_GAS = 1000000n;
16906
17279
  var GasEstimator = class {
16907
17280
  /**
16908
17281
  * @param ctx - Shared IntentsV2 context providing the source and destination
@@ -16923,7 +17296,12 @@ var GasEstimator = class {
16923
17296
  *
16924
17297
  * **Cross-chain orders:** also estimates the ISMP POST request fee required
16925
17298
  * for the solver to trigger source-chain escrow redemption after filling, and
16926
- * includes it in `fillOptions.relayerFee` and `fillOptions.nativeDispatchFee`.
17299
+ * includes it in `fillOptions.relayerFee`. The dispatch is always paid in the
17300
+ * fee token — `nativeDispatchFee` is fixed at 0. The native rail would draw
17301
+ * from the solver account's native balance, which nothing guarantees, and a
17302
+ * shortfall is invisible to estimation (the account balance is overridden
17303
+ * during simulation) — it would only surface as a reverted execution that
17304
+ * still bills the paymaster.
16927
17305
  *
16928
17306
  * **Bundler path:** constructs a mock `PackedUserOperation` signed by an
16929
17307
  * ephemeral keypair, applies state overrides, and calls
@@ -16972,24 +17350,19 @@ var GasEstimator = class {
16972
17350
  intentGatewayV2Address,
16973
17351
  entryPointAddress
16974
17352
  }),
16975
- isSameChain ? Promise.resolve({ postRequestFee: 0n, protocolFee: 0n }) : this.estimateCrossChainFees(
16976
- sourceFeeToken,
16977
- destFeeToken,
16978
- souceStateMachineId,
16979
- destStateMachineId,
16980
- order
16981
- )
17353
+ isSameChain ? Promise.resolve({ postRequestFee: 0n, relayerFeeInSourceFeeToken: 0n }) : this.estimateCrossChainFees(sourceFeeToken, destFeeToken, souceStateMachineId)
16982
17354
  ]);
16983
17355
  const { viem: stateOverrides, bundler: bundlerStateOverrides } = stateOverridesResult;
16984
17356
  const fillOptions = {
16985
17357
  relayerFee: crossChainFees.postRequestFee,
16986
- nativeDispatchFee: crossChainFees.protocolFee,
17358
+ // Always dispatch with the fee token (see the method docs).
17359
+ nativeDispatchFee: 0n,
16987
17360
  outputs: order.output.assets.map((asset) => ({
16988
17361
  ...asset,
16989
17362
  token: normalizeAddressForEvmBytes32(asset.token)
16990
17363
  }))
16991
17364
  };
16992
- const totalNativeValue = totalEthValue + fillOptions.nativeDispatchFee;
17365
+ const totalNativeValue = totalEthValue;
16993
17366
  const priorityFeeBumpPercent = params.maxPriorityFeePerGasBumpPercent ?? 8;
16994
17367
  const maxFeeBumpPercent = params.maxFeePerGasBumpPercent ?? 10;
16995
17368
  let maxPriorityFeePerGas = gasPrice + gasPrice * BigInt(priorityFeeBumpPercent) / 100n;
@@ -17156,37 +17529,32 @@ var GasEstimator = class {
17156
17529
  maxPriorityFeePerGas,
17157
17530
  totalGasCostWei,
17158
17531
  totalGasInFeeToken: totalGasInSourceFeeToken,
17532
+ relayerFeeInSourceFeeToken: crossChainFees.relayerFeeInSourceFeeToken,
17159
17533
  fillOptions
17160
17534
  };
17161
17535
  }
17162
17536
  /**
17163
- * Estimates cross-chain ISMP POST request fees by running the gas-to-fee-token
17164
- * conversion and host nonce fetch in parallel, then quoting the native dispatch cost.
17165
- */
17166
- async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId, destChainId, order) {
17167
- const postRequestGas = 400000n;
17168
- const [postRequestFeeInSourceFeeToken, nonce] = await Promise.all([
17169
- convertGasToFeeToken(this.ctx, postRequestGas, "source", sourceChainId),
17170
- this.ctx.dest.getHostNonce()
17171
- ]);
17172
- let postRequestFeeInDestFeeToken = adjustDecimals(
17537
+ * Estimates the cross-chain ISMP POST request fee (the relayer fee for the
17538
+ * RedeemEscrow message), in both the source and destination fee tokens.
17539
+ * The dispatch is always paid in the fee token — the native rail was
17540
+ * removed because it silently drew on a native balance nothing guarantees.
17541
+ */
17542
+ async estimateCrossChainFees(sourceFeeToken, destFeeToken, sourceChainId) {
17543
+ const postRequestFeeInSourceFeeToken = await convertGasToFeeToken(
17544
+ this.ctx,
17545
+ RELAYER_MESSAGE_GAS,
17546
+ "source",
17547
+ sourceChainId
17548
+ );
17549
+ const postRequestFeeInDestFeeToken = adjustDecimals(
17173
17550
  postRequestFeeInSourceFeeToken,
17174
17551
  sourceFeeToken.decimals,
17175
17552
  destFeeToken.decimals
17176
17553
  );
17177
- const postRequest = {
17178
- source: destChainId,
17179
- dest: sourceChainId,
17180
- body: constructRedeemEscrowRequestBody({ ...order, id: orderCommitment(order) }, MOCK_ADDRESS),
17181
- timeoutTimestamp: 0n,
17182
- nonce,
17183
- from: this.ctx.source.configService.getIntentGatewayAddress(destChainId),
17184
- to: this.ctx.source.configService.getIntentGatewayAddress(sourceChainId)
17554
+ return {
17555
+ postRequestFee: postRequestFeeInDestFeeToken,
17556
+ relayerFeeInSourceFeeToken: postRequestFeeInSourceFeeToken
17185
17557
  };
17186
- postRequestFeeInDestFeeToken = postRequestFeeInDestFeeToken * 1005n / 1000n;
17187
- let protocolFeeInNativeToken = await this.ctx.dest.quoteNative(postRequest, postRequestFeeInDestFeeToken).catch(() => 0n);
17188
- protocolFeeInNativeToken = protocolFeeInNativeToken * 1005n / 1000n;
17189
- return { postRequestFee: postRequestFeeInDestFeeToken, protocolFee: protocolFeeInNativeToken };
17190
17558
  }
17191
17559
  /**
17192
17560
  * Builds EVM state override objects for gas estimation of the `fillOrder`
@@ -17378,6 +17746,178 @@ var OrderStatusChecker = class {
17378
17746
  return true;
17379
17747
  }
17380
17748
  };
17749
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17750
+ var LiquidityEngine = class {
17751
+ /**
17752
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17753
+ * @param chainConfigService - Resolves token decimals for formatted results.
17754
+ */
17755
+ constructor(queryClient, chainConfigService) {
17756
+ this.queryClient = queryClient;
17757
+ this.chainConfigService = chainConfigService;
17758
+ }
17759
+ queryClient;
17760
+ chainConfigService;
17761
+ /**
17762
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17763
+ * indexed output-token liquidity.
17764
+ *
17765
+ * Nexus filters balances to the canonical output token, then aggregates them
17766
+ * overall and by chain. The returned amounts are decimal strings: the total
17767
+ * uses the canonical Base output-token decimals, while each chain group uses
17768
+ * that chain's token decimals. They describe `snapshotTime`, not live
17769
+ * reservations or fill guarantees.
17770
+ *
17771
+ * @param params - Canonical Phantom-market input and output token addresses.
17772
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17773
+ * the directional pair.
17774
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17775
+ * or internally inconsistent snapshot data.
17776
+ */
17777
+ async getAvailableLiquiditySnapshot(params) {
17778
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17779
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17780
+ const response = await this.queryClient.request(
17781
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17782
+ { tokenA: tokenIn, tokenB: tokenOut }
17783
+ );
17784
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17785
+ if (!node) return;
17786
+ const commitment = node.commitment.toLowerCase();
17787
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17788
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17789
+ }
17790
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17791
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17792
+ }
17793
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17794
+ if (Number.isNaN(snapshotTime.getTime())) {
17795
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17796
+ }
17797
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17798
+ commitment,
17799
+ tokenAddress: tokenOut
17800
+ });
17801
+ return {
17802
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17803
+ providerCount,
17804
+ tokenAddress: tokenOut,
17805
+ snapshotTime,
17806
+ liquidityByChain: liquidityByChain.map((group) => ({
17807
+ ...group,
17808
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17809
+ }))
17810
+ };
17811
+ }
17812
+ /**
17813
+ * Requests server-side sums and distinct provider counts for one immutable
17814
+ * snapshot/output-token pair, including chain-level aggregate groups.
17815
+ *
17816
+ * `commitment` uniquely identifies the selected snapshot
17817
+ */
17818
+ async querySnapshotLiquidityAggregates(params) {
17819
+ const response = await this.queryClient.request(
17820
+ LIQUIDITY_PROVIDER_BALANCES,
17821
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17822
+ );
17823
+ const connection = response?.liquidityProviderBalances;
17824
+ const aggregates = connection?.aggregates;
17825
+ if (!connection || !aggregates) {
17826
+ throw new InvalidAvailableLiquiditySnapshotError(
17827
+ params.commitment,
17828
+ "liquidityProviderBalances aggregates are missing"
17829
+ );
17830
+ }
17831
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17832
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17833
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17834
+ const [chain, tokenAddress] = group.keys;
17835
+ if (!chain?.trim() || !tokenAddress) {
17836
+ throw new InvalidAvailableLiquiditySnapshotError(
17837
+ params.commitment,
17838
+ `liquidity group ${index} has invalid keys`
17839
+ );
17840
+ }
17841
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17842
+ tokenAddress,
17843
+ params.commitment,
17844
+ `liquidity group ${index} tokenAddress`
17845
+ );
17846
+ if (normalizedTokenAddress !== params.tokenAddress) {
17847
+ throw new InvalidAvailableLiquiditySnapshotError(
17848
+ params.commitment,
17849
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17850
+ );
17851
+ }
17852
+ return {
17853
+ chain: chain.trim(),
17854
+ tokenAddress: normalizedTokenAddress,
17855
+ totalLiquidity: parseSnapshotBigInt(
17856
+ group.sum.balance ?? "0",
17857
+ params.commitment,
17858
+ `liquidity group ${index} balance`
17859
+ ),
17860
+ providerCount: parseProviderCount(
17861
+ group.distinctCount.providerId,
17862
+ params.commitment,
17863
+ `liquidity group ${index}`
17864
+ )
17865
+ };
17866
+ });
17867
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17868
+ if (groupedTotal !== totalLiquidity) {
17869
+ throw new InvalidAvailableLiquiditySnapshotError(
17870
+ params.commitment,
17871
+ "grouped liquidity does not match the total liquidity"
17872
+ );
17873
+ }
17874
+ return { totalLiquidity, providerCount, liquidityByChain };
17875
+ }
17876
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17877
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17878
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17879
+ if (decimals === void 0) {
17880
+ throw new InvalidAvailableLiquiditySnapshotError(
17881
+ commitment,
17882
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17883
+ );
17884
+ }
17885
+ return viem.formatUnits(amount, decimals);
17886
+ }
17887
+ };
17888
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17889
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17890
+ constructor(commitment, reason) {
17891
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17892
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17893
+ }
17894
+ };
17895
+ function parseSnapshotBigInt(value, commitment, field) {
17896
+ try {
17897
+ const amount = BigInt(value);
17898
+ if (amount < 0n) {
17899
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17900
+ }
17901
+ return amount;
17902
+ } catch (error) {
17903
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17904
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17905
+ }
17906
+ }
17907
+ function parseProviderCount(value, commitment, field) {
17908
+ const count = Number(value);
17909
+ if (!Number.isSafeInteger(count) || count < 0) {
17910
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17911
+ }
17912
+ return count;
17913
+ }
17914
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17915
+ try {
17916
+ return normalizeEvmAddress(address, field);
17917
+ } catch {
17918
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17919
+ }
17920
+ }
17381
17921
 
17382
17922
  // src/protocols/intents/quote/types.ts
17383
17923
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17664,12 +18204,14 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17664
18204
  constructor(chainConfigService, getQueryClient) {
17665
18205
  this.chainConfigService = chainConfigService;
17666
18206
  this.getQueryClient = getQueryClient;
18207
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
17667
18208
  }
17668
18209
  chainConfigService;
17669
18210
  getQueryClient;
18211
+ pairResolver;
17670
18212
  async quote(params, source, destination) {
17671
18213
  validateQuoteParams(params);
17672
- const pair = this.resolveBaseSnapshotPair(params, source.stateMachineId, destination.stateMachineId);
18214
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
17673
18215
  if (!pair) {
17674
18216
  throw new UnsupportedIntentQuotePairError({
17675
18217
  source: source.stateMachineId,
@@ -17742,7 +18284,13 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17742
18284
  throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
17743
18285
  }
17744
18286
  }
17745
- resolveBaseSnapshotPair(params, sourceStateMachineId, destinationStateMachineId) {
18287
+ };
18288
+ var PhantomSnapshotPairResolver = class {
18289
+ constructor(chainConfigService) {
18290
+ this.chainConfigService = chainConfigService;
18291
+ }
18292
+ chainConfigService;
18293
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
17746
18294
  const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
17747
18295
  const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
17748
18296
  if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
@@ -17766,7 +18314,6 @@ function isConfiguredAddress(address) {
17766
18314
  }
17767
18315
 
17768
18316
  // src/protocols/intents/IntentGateway.ts
17769
- var CROSS_CHAIN_ORDER_FEE_GAS_BUMP = 600000n;
17770
18317
  var IntentGateway = class _IntentGateway {
17771
18318
  /** EVM chain on which orders are placed and escrowed. */
17772
18319
  source;
@@ -17794,6 +18341,8 @@ var IntentGateway = class _IntentGateway {
17794
18341
  gasEstimator;
17795
18342
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17796
18343
  quoteStrategies;
18344
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18345
+ phantomSnapshotPairResolver;
17797
18346
  /**
17798
18347
  * Private constructor — use {@link IntentGateway.create} instead.
17799
18348
  *
@@ -17837,6 +18386,7 @@ var IntentGateway = class _IntentGateway {
17837
18386
  this.bidManager = bidManager;
17838
18387
  this.gasEstimator = gasEstimator;
17839
18388
  this._crypto = crypto;
18389
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17840
18390
  this.quoteStrategies = {
17841
18391
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
17842
18392
  dest.configService,
@@ -17915,15 +18465,47 @@ var IntentGateway = class _IntentGateway {
17915
18465
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17916
18466
  return handler.quote({ ...params, strategy }, source, destination);
17917
18467
  }
18468
+ /**
18469
+ * Returns the output-token liquidity measured in the latest directional
18470
+ * Phantom snapshot for this gateway's source and destination.
18471
+ *
18472
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18473
+ * The snapshot itself determines the output token and chain to aggregate. The
18474
+ * amount is in the token's smallest unit and reflects the indexer's
18475
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18476
+ *
18477
+ * Requires a prior call to {@link withQueryClient}.
18478
+ */
18479
+ async queryAvailableLiquidity(params) {
18480
+ const { queryClient } = this.requireIndexer();
18481
+ const sourceStateMachineId = this.source.config.stateMachineId;
18482
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18483
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18484
+ if (!pair) {
18485
+ throw new UnsupportedIntentQuotePairError({
18486
+ source: sourceStateMachineId,
18487
+ destination: destinationStateMachineId,
18488
+ tokenIn: params.tokenIn,
18489
+ tokenOut: params.tokenOut,
18490
+ quoteSource: "Phantom snapshot pair"
18491
+ });
18492
+ }
18493
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18494
+ tokenIn: pair.tokenA,
18495
+ tokenOut: pair.tokenB
18496
+ });
18497
+ }
17918
18498
  /**
17919
18499
  * Bidirectional async generator that orchestrates the full order lifecycle:
17920
18500
  * placement, fee estimation, bid collection, and execution.
17921
18501
  *
17922
18502
  * **Yield/receive protocol:**
17923
- * 1. If `order.fees` is unset or zero, estimates gas and sets same-chain
17924
- * `order.fees` to twice the estimate. Cross-chain orders retain a 1% buffer
17925
- * and include an additional 600k-gas fee uplift, while the wei cost used for
17926
- * the `value` field receives a 2% buffer.
18503
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and
18504
+ * sets same-chain fees to twice the estimate. Cross-chain orders attach
18505
+ * (fill gas + a settlement-message uplift of `RELAYER_MESSAGE_GAS`, the
18506
+ * same gas budget the solver's relayer fee uses) with a 5% buffer over
18507
+ * the whole sum — strictly above the solver's unpadded requirement. The
18508
+ * wei cost used for the `value` field receives a 2% buffer.
17927
18509
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
17928
18510
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
17929
18511
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
@@ -17931,8 +18513,8 @@ var IntentGateway = class _IntentGateway {
17931
18513
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17932
18514
  * subsequent status updates until the order is filled, exhausted, or fails.
17933
18515
  *
17934
- * @param order - The order to place and execute. `order.fees` may be 0; it
17935
- * will be estimated automatically if so.
18516
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18517
+ * may be 0; fees are estimated automatically if so.
17936
18518
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17937
18519
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17938
18520
  * @param options - Optional tuning parameters:
@@ -17945,10 +18527,11 @@ var IntentGateway = class _IntentGateway {
17945
18527
  * estimation returns zero.
17946
18528
  */
17947
18529
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18530
+ const executionOrder = { ...order };
17948
18531
  let value;
17949
- if (!order.fees || order.fees === 0n) {
18532
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17950
18533
  const estimate = await this.gasEstimator.estimateFillOrder({
17951
- order,
18534
+ order: executionOrder,
17952
18535
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
17953
18536
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
17954
18537
  });
@@ -17959,13 +18542,13 @@ var IntentGateway = class _IntentGateway {
17959
18542
  value = estimate.totalGasCostWei + estimate.totalGasCostWei * 2n / 100n;
17960
18543
  const crossChainFeeBump = isSameChain ? 0n : await convertGasToFeeToken(
17961
18544
  this.ctx,
17962
- CROSS_CHAIN_ORDER_FEE_GAS_BUMP,
18545
+ RELAYER_MESSAGE_GAS,
17963
18546
  "source",
17964
18547
  this.source.config.stateMachineId
17965
18548
  );
17966
- order.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
18549
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : (estimate.totalGasInFeeToken + crossChainFeeBump) * 105n / 100n;
17967
18550
  }
17968
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18551
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
17969
18552
  const placeOrderFirst = await placeOrderGen.next();
17970
18553
  if (placeOrderFirst.done) {
17971
18554
  throw new Error("placeOrder generator completed without yielding");
@@ -18076,13 +18659,20 @@ var IntentGateway = class _IntentGateway {
18076
18659
  const gen = this.execute(order, graffiti, options);
18077
18660
  try {
18078
18661
  let input;
18662
+ let finalizedOrder;
18079
18663
  while (true) {
18080
18664
  const { value, done } = await gen.next(input);
18081
18665
  input = void 0;
18082
18666
  if (done) break;
18083
- if (value.status === "BIDS_RECEIVED") {
18667
+ if (value.status === "ORDER_PLACED") {
18668
+ finalizedOrder = value.order;
18084
18669
  yield value;
18085
- input = await this.autoSelect(order, value.bids);
18670
+ } else if (value.status === "BIDS_RECEIVED") {
18671
+ if (!finalizedOrder) {
18672
+ throw new Error("Received bids before the order was finalized");
18673
+ }
18674
+ yield value;
18675
+ input = await this.autoSelect(finalizedOrder, value.bids);
18086
18676
  } else if (value.status === "AWAITING_PLACE_ORDER") {
18087
18677
  input = yield value;
18088
18678
  } else {
@@ -22959,6 +23549,7 @@ exports.hyperbridgeAddress = hyperbridgeAddress;
22959
23549
  exports.maxBigInt = maxBigInt;
22960
23550
  exports.normalizeAddressForEvmBytes32 = normalizeAddressForEvmBytes32;
22961
23551
  exports.normalizeAddressForStateMachine = normalizeAddressForStateMachine;
23552
+ exports.normalizeEvmAddress = normalizeEvmAddress;
22962
23553
  exports.normalizeEvmChainId = normalizeEvmChainId;
22963
23554
  exports.normalizeStateMachineId = normalizeStateMachineId;
22964
23555
  exports.orderCommitment = orderCommitment;