@hyperbridge/sdk 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2720,6 +2720,7 @@ var chainConfigs = {
2720
2720
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2721
2721
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2722
2722
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2723
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2723
2724
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2724
2725
  },
2725
2726
  rpcEnvKey: "ETH_MAINNET",
@@ -2773,7 +2774,8 @@ var chainConfigs = {
2773
2774
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2774
2775
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2775
2776
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2776
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2777
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2778
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2777
2779
  // "Usdt0Oft": Not available on BSC
2778
2780
  },
2779
2781
  rpcEnvKey: "BSC_MAINNET",
@@ -2831,6 +2833,7 @@ var chainConfigs = {
2831
2833
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2832
2834
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2833
2835
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2836
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2834
2837
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2835
2838
  },
2836
2839
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2885,6 +2888,7 @@ var chainConfigs = {
2885
2888
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2886
2889
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2887
2890
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2891
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2888
2892
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2889
2893
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2890
2894
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2947,6 +2951,7 @@ var chainConfigs = {
2947
2951
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2948
2952
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2949
2953
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2954
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2950
2955
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2951
2956
  },
2952
2957
  rpcEnvKey: "POLYGON_MAINNET",
@@ -5242,6 +5247,9 @@ var ChainConfigService = class {
5242
5247
  getCirclePaymasterAddress(chain) {
5243
5248
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5244
5249
  }
5250
+ getSimplexPaymasterAddress(chain) {
5251
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5252
+ }
5245
5253
  getHyperbridgeAddress() {
5246
5254
  return hyperbridgeAddress;
5247
5255
  }
@@ -7062,6 +7070,16 @@ var ExpectedError = class _ExpectedError extends Error {
7062
7070
  return error instanceof _ExpectedError;
7063
7071
  }
7064
7072
  };
7073
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7074
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7075
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7076
+ super(message, options);
7077
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7078
+ }
7079
+ static isError(error) {
7080
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7081
+ }
7082
+ };
7065
7083
 
7066
7084
  // src/configs/constants.ts
7067
7085
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7375,8 +7393,15 @@ var SubstrateChain = class _SubstrateChain {
7375
7393
  id: stateMachineId,
7376
7394
  height: Number(stateMachineHeight.height)
7377
7395
  };
7378
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7379
- return BigInt(updateTime);
7396
+ try {
7397
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7398
+ return BigInt(updateTime);
7399
+ } catch (error) {
7400
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7401
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7402
+ }
7403
+ throw error;
7404
+ }
7380
7405
  }
7381
7406
  /**
7382
7407
  * Get the challenge period for a given state machine id.
@@ -8122,26 +8147,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8122
8147
  };
8123
8148
  }
8124
8149
  /**
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
- });
8150
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8151
+ */
8152
+ async getPhantomOrdersInBlock(blockNumber) {
8153
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8154
+ const apiAt = await this.api.at(blockHash);
8155
+ const records = await apiAt.query.system.events();
8156
+ const orders = [];
8157
+ for (const { event } of records) {
8158
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8159
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8160
+ orders.push({
8161
+ commitment: commitment.toHex(),
8162
+ chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8163
+ createdAt: createdAt.toNumber(),
8164
+ tokenA: tokenA.toHex(),
8165
+ tokenB: tokenB.toHex(),
8166
+ standardAmount: BigInt(standardAmount.toString())
8167
+ });
8168
+ }
8169
+ return orders;
8170
+ }
8171
+ /**
8172
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8173
+ *
8174
+ * Each tick reads the current head and scans every block between the last one processed and that
8175
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8176
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8177
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8178
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8179
+ * meant silently missed bids.
8180
+ *
8181
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8182
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8183
+ * read. Recovery replays the backlog.
8184
+ *
8185
+ * Returns a function that stops polling.
8186
+ */
8187
+ pollPhantomOrders(callback, options = {}) {
8188
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8189
+ let cursor = null;
8190
+ let inFlight = false;
8191
+ let stopped = false;
8192
+ const tick = async () => {
8193
+ if (inFlight || stopped) return;
8194
+ inFlight = true;
8195
+ try {
8196
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8197
+ if (cursor === null) {
8198
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8199
+ }
8200
+ if (head <= cursor) return;
8201
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8202
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8203
+ if (stopped) return;
8204
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8205
+ for (const order of orders) callback(order);
8206
+ cursor = blockNumber;
8207
+ }
8208
+ } catch (err) {
8209
+ onError?.(err);
8210
+ } finally {
8211
+ inFlight = false;
8142
8212
  }
8143
- });
8144
- return unsub;
8213
+ };
8214
+ void tick();
8215
+ const timer = setInterval(() => void tick(), intervalMs);
8216
+ return () => {
8217
+ stopped = true;
8218
+ clearInterval(timer);
8219
+ };
8145
8220
  }
8146
8221
  };
8147
8222
  var TronChain = class _TronChain {
@@ -9274,7 +9349,12 @@ function sleep(ms) {
9274
9349
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9275
9350
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9276
9351
  if (challengePeriod === BigInt(0)) return;
9277
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9352
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9353
+ maxRetries: 3,
9354
+ backoffMs: 500,
9355
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9356
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9357
+ });
9278
9358
  let currentTimestamp = await chain.timestamp();
9279
9359
  let timeElapsed = currentTimestamp - updateTime;
9280
9360
  if (timeElapsed > challengePeriod) return;
@@ -9421,6 +9501,10 @@ function normalizeAddressForEvmBytes32(address) {
9421
9501
  if (address.length === 66) return address;
9422
9502
  return bytes20ToBytes32(address);
9423
9503
  }
9504
+ function normalizeEvmAddress(address, field = "address") {
9505
+ if (!viem.isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9506
+ return address.toLowerCase();
9507
+ }
9424
9508
  function normalizeAddressForStateMachine(address, stateMachineId) {
9425
9509
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9426
9510
  }
@@ -9428,14 +9512,16 @@ var DEFAULT_LOGGER = consola.createConsola({
9428
9512
  level: consola.LogLevels.silent
9429
9513
  });
9430
9514
  async function retryPromise(operation, retryConfig) {
9431
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9515
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9432
9516
  let lastError;
9433
9517
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9434
9518
  try {
9435
9519
  return await operation();
9436
9520
  } catch (error) {
9521
+ if (shouldRetry && !shouldRetry(error)) throw error;
9437
9522
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9438
9523
  lastError = error;
9524
+ if (i === retryConfig.maxRetries - 1) break;
9439
9525
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9440
9526
  }
9441
9527
  }
@@ -11506,6 +11592,55 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11506
11592
  }
11507
11593
  }
11508
11594
  }`;
11595
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11596
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11597
+ phantomOrderPriceSnapshots(
11598
+ filter: {
11599
+ and: [
11600
+ { tokenA: { equalTo: $tokenA } }
11601
+ { tokenB: { equalTo: $tokenB } }
11602
+ ]
11603
+ }
11604
+ orderBy: SNAPSHOT_TIME_DESC
11605
+ first: 1
11606
+ ) {
11607
+ nodes {
11608
+ commitment
11609
+ tokenA
11610
+ tokenB
11611
+ snapshotTime
11612
+ }
11613
+ }
11614
+ }`;
11615
+ var LIQUIDITY_PROVIDER_BALANCES = `
11616
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11617
+ liquidityProviderBalances(
11618
+ filter: {
11619
+ and: [
11620
+ { commitment: { equalTo: $commitment } }
11621
+ { tokenAddress: { equalTo: $tokenAddress } }
11622
+ ]
11623
+ }
11624
+ ) {
11625
+ aggregates {
11626
+ sum {
11627
+ balance
11628
+ }
11629
+ distinctCount {
11630
+ providerId
11631
+ }
11632
+ }
11633
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11634
+ keys
11635
+ sum {
11636
+ balance
11637
+ }
11638
+ distinctCount {
11639
+ providerId
11640
+ }
11641
+ }
11642
+ }
11643
+ }`;
11509
11644
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11510
11645
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11511
11646
  tokenGatewayAssetTeleportedV2s(
@@ -12090,6 +12225,7 @@ function createCancellationStorage(options = {}) {
12090
12225
  const decoded = decodeIProof(value);
12091
12226
  return decoded;
12092
12227
  }
12228
+ if (key2.includes("postCommitment")) return value;
12093
12229
  throw new Error(`Unknown storage key type: ${key2}`);
12094
12230
  };
12095
12231
  const setItem = async (key2, value) => {
@@ -12103,6 +12239,10 @@ function createCancellationStorage(options = {}) {
12103
12239
  await baseStorage.setItem(key2, encoded);
12104
12240
  return;
12105
12241
  }
12242
+ if (key2.includes("postCommitment") && typeof value === "string") {
12243
+ await baseStorage.setItem(key2, value);
12244
+ return;
12245
+ }
12106
12246
  throw new Error(`Unknown storage key type: ${key2}`);
12107
12247
  };
12108
12248
  const removeItem = async (key2) => {
@@ -12116,11 +12256,25 @@ function createCancellationStorage(options = {}) {
12116
12256
  });
12117
12257
  }
12118
12258
  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`
12259
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12260
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12261
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12262
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12123
12263
  });
12264
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12265
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12266
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12267
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12268
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12269
+ });
12270
+ function cancellationScope(orderId, source, destination) {
12271
+ if (!source || !destination) return orderId;
12272
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12273
+ }
12274
+ function legacyCancellationScope(orderId, source, destination) {
12275
+ if (!source || !destination) return orderId;
12276
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12277
+ }
12124
12278
  function createUsedUserOpsStorage(options = {}) {
12125
12279
  options.env ?? detectEnvironment();
12126
12280
  const driver = loadDriver({ options }) ?? inMemoryDriver__default.default();
@@ -15286,6 +15440,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15286
15440
  }
15287
15441
 
15288
15442
  // src/protocols/intents/OrderPlacer.ts
15443
+ function deriveCanonicalPlacedOrder(order, args) {
15444
+ const canonicalOrder = {
15445
+ ...order,
15446
+ user: args.user,
15447
+ source: args.source,
15448
+ destination: args.destination,
15449
+ deadline: args.deadline,
15450
+ nonce: args.nonce,
15451
+ fees: args.fees,
15452
+ session: args.session,
15453
+ predispatch: {
15454
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15455
+ call: order.predispatch.call
15456
+ },
15457
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15458
+ output: {
15459
+ beneficiary: args.beneficiary,
15460
+ assets: args.outputs.map((asset) => ({ ...asset })),
15461
+ call: order.output.call
15462
+ }
15463
+ };
15464
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15465
+ }
15289
15466
  var OrderPlacer = class {
15290
15467
  /**
15291
15468
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15302,17 +15479,16 @@ var OrderPlacer = class {
15302
15479
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15303
15480
  * transaction sending `data` to `to` and pass the signed transaction back
15304
15481
  * 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.
15482
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15483
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15484
+ * placement transaction hash.
15308
15485
  *
15309
15486
  * A fresh ephemeral session key is generated for every call. The key is
15310
15487
  * stored immediately (without a commitment) so it can be retrieved by
15311
15488
  * address, then updated with the commitment once the `OrderPlaced` event
15312
- * confirms the nonce.
15489
+ * confirms the canonical order.
15313
15490
  *
15314
- * @param order - The order to place. `order.session` is mutated in-place
15315
- * with the generated session key address before yielding.
15491
+ * @param order - The order to place. It is not mutated.
15316
15492
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15317
15493
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15318
15494
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15327,7 +15503,7 @@ var OrderPlacer = class {
15327
15503
  const account = accounts.privateKeyToAccount(privateKey);
15328
15504
  const sessionKeyAddress = account.address;
15329
15505
  const createdAt = Date.now();
15330
- order.session = sessionKeyAddress;
15506
+ const placementOrder = { ...order, session: sessionKeyAddress };
15331
15507
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15332
15508
  privateKey,
15333
15509
  address: sessionKeyAddress,
@@ -15336,7 +15512,7 @@ var OrderPlacer = class {
15336
15512
  const data = viem.encodeFunctionData({
15337
15513
  abi: ABI3,
15338
15514
  functionName: "placeOrder",
15339
- args: [transformOrderForContract(order), graffiti]
15515
+ args: [transformOrderForContract(placementOrder), graffiti]
15340
15516
  });
15341
15517
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15342
15518
  normalizeStateMachineId(order.source)
@@ -15356,20 +15532,15 @@ var OrderPlacer = class {
15356
15532
  if (!orderPlacedEvent) {
15357
15533
  throw new Error("OrderPlaced event not found in transaction receipt");
15358
15534
  }
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);
15535
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15365
15536
  const sessionKeyData = {
15366
15537
  privateKey,
15367
15538
  address: sessionKeyAddress,
15368
- commitment: order.id,
15539
+ commitment: finalizedOrder.id,
15369
15540
  createdAt
15370
15541
  };
15371
15542
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15372
- return { order, receipt };
15543
+ return { order: finalizedOrder, receipt };
15373
15544
  }
15374
15545
  };
15375
15546
 
@@ -15699,7 +15870,7 @@ var OrderExecutor = class {
15699
15870
  }
15700
15871
  }
15701
15872
  };
15702
- var OrderCanceller = class {
15873
+ var OrderCanceller = class _OrderCanceller {
15703
15874
  /**
15704
15875
  * @param ctx - Shared IntentsV2 context providing the source and destination
15705
15876
  * chain clients, config service, and cancellation storage.
@@ -15708,6 +15879,9 @@ var OrderCanceller = class {
15708
15879
  this.ctx = ctx;
15709
15880
  }
15710
15881
  ctx;
15882
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15883
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15884
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15711
15885
  logger = consola.createConsola({
15712
15886
  level: consola.LogLevels.info,
15713
15887
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15739,11 +15913,14 @@ var OrderCanceller = class {
15739
15913
  * @returns The native token dispatch fee in wei.
15740
15914
  */
15741
15915
  async quoteCancelFromSource(order) {
15742
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15743
15916
  const sourceStateMachine = normalizeStateMachineId(order.source);
15917
+ const destStateMachine = normalizeStateMachineId(order.destination);
15918
+ if (sourceStateMachine === destStateMachine) {
15919
+ return { nativeValue: 0n, relayerFee: 0n };
15920
+ }
15744
15921
  const height = order.deadline + 1n;
15745
15922
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15746
- normalizeStateMachineId(order.destination)
15923
+ destStateMachine
15747
15924
  );
15748
15925
  const slotHash = await this.ctx.dest.client.readContract({
15749
15926
  abi: ABI3,
@@ -15755,8 +15932,8 @@ var OrderCanceller = class {
15755
15932
  const context = encodeWithdrawalRequest(order, order.user);
15756
15933
  const getRequest = {
15757
15934
  source: sourceStateMachine,
15758
- dest: normalizeStateMachineId(order.destination),
15759
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
15935
+ dest: destStateMachine,
15936
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15760
15937
  nonce: await this.ctx.source.getHostNonce(),
15761
15938
  height,
15762
15939
  keys: [key],
@@ -15773,7 +15950,9 @@ var OrderCanceller = class {
15773
15950
  * cancellation is complete.
15774
15951
  *
15775
15952
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15776
- * the `from` parameter.
15953
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
15954
+ * source-side GET recovery, clears its stale checkpoint and restarts the
15955
+ * source-side stream internally.
15777
15956
  *
15778
15957
  * @param order - The order to cancel.
15779
15958
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15783,12 +15962,32 @@ var OrderCanceller = class {
15783
15962
  * cancellation lifecycle.
15784
15963
  */
15785
15964
  async *cancelOrder(order, indexerClient, options = {}) {
15786
- const isSameChain = order.source === order.destination;
15965
+ const sourceStateMachine = normalizeStateMachineId(order.source);
15966
+ const destStateMachine = normalizeStateMachineId(order.destination);
15967
+ const isSameChain = sourceStateMachine === destStateMachine;
15787
15968
  if (options.from === "destination" && !isSameChain) {
15788
15969
  yield* this.cancelOrderFromDest(order, indexerClient);
15789
15970
  return;
15790
15971
  }
15791
- yield* this.cancelOrderFromSource(order, indexerClient);
15972
+ let recoveryRestarts = 0;
15973
+ while (true) {
15974
+ try {
15975
+ yield* this.cancelOrderFromSource(order, indexerClient);
15976
+ return;
15977
+ } catch (error) {
15978
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
15979
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
15980
+ throw new Error(
15981
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
15982
+ );
15983
+ }
15984
+ recoveryRestarts += 1;
15985
+ this.logger.warn(
15986
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
15987
+ );
15988
+ await this.clearGetRecoveryCache(order);
15989
+ }
15990
+ }
15792
15991
  }
15793
15992
  /**
15794
15993
  * Async generator that cancels an order by initiating the cancel from the
@@ -15814,11 +16013,10 @@ var OrderCanceller = class {
15814
16013
  * @throws If the cancel transaction does not contain the expected on-chain event.
15815
16014
  */
15816
16015
  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
- );
16016
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16017
+ const destStateMachine = normalizeStateMachineId(order.destination);
16018
+ const isSameChain = sourceStateMachine === destStateMachine;
16019
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15822
16020
  if (isSameChain) {
15823
16021
  const data = viem.encodeFunctionData({
15824
16022
  abi: ABI3,
@@ -15847,18 +16045,37 @@ var OrderCanceller = class {
15847
16045
  };
15848
16046
  return;
15849
16047
  }
16048
+ const storageKeys = this.recoveryStorageKeys(order);
16049
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15850
16050
  const hyperbridge = indexerClient.hyperbridge;
15851
- const sourceStateMachine = normalizeStateMachineId(order.source);
15852
16051
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15853
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16052
+ let destIProof = await this.getRecoveryItem(
16053
+ storageKeys.destProof,
16054
+ legacyStorageKeys.map((keys) => keys.destProof)
16055
+ );
15854
16056
  if (!destIProof) {
15855
16057
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15856
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16058
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15857
16059
  } else {
15858
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16060
+ let refreshed = false;
16061
+ try {
16062
+ await this.assertProofFresh(hyperbridge, destIProof);
16063
+ } catch (error) {
16064
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16065
+ await this.removeRecoveryItems(
16066
+ storageKeys.destProof,
16067
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16068
+ );
16069
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16070
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16071
+ refreshed = true;
16072
+ }
16073
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15859
16074
  }
15860
- let getRequest = await this.ctx.cancellationStorage.getItem(
15861
- STORAGE_KEYS.getRequest(orderId)
16075
+ await this.assertProofFresh(hyperbridge, destIProof);
16076
+ let getRequest = await this.getRecoveryItem(
16077
+ storageKeys.getRequest,
16078
+ legacyStorageKeys.map((keys) => keys.getRequest)
15862
16079
  );
15863
16080
  if (!getRequest) {
15864
16081
  const quote = await this.quoteCancelFromSource(order);
@@ -15878,7 +16095,7 @@ var OrderCanceller = class {
15878
16095
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15879
16096
  if (!request) throw new Error("GetRequest missing");
15880
16097
  getRequest = request.args;
15881
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16098
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15882
16099
  yield {
15883
16100
  status: "CANCEL_STARTED",
15884
16101
  receipt
@@ -15897,8 +16114,9 @@ var OrderCanceller = class {
15897
16114
  metadata: statusUpdate.metadata
15898
16115
  };
15899
16116
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15900
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15901
- STORAGE_KEYS.sourceProof(orderId)
16117
+ let sourceIProof = await this.getRecoveryItem(
16118
+ storageKeys.sourceProof,
16119
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15902
16120
  );
15903
16121
  if (!sourceIProof) {
15904
16122
  sourceIProof = await fetchSourceProof(
@@ -15908,8 +16126,9 @@ var OrderCanceller = class {
15908
16126
  sourceConsensusStateId,
15909
16127
  sourceHeight
15910
16128
  );
15911
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16129
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15912
16130
  }
16131
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15913
16132
  await waitForChallengePeriod(hyperbridge, {
15914
16133
  height: sourceIProof.height,
15915
16134
  id: {
@@ -15917,6 +16136,10 @@ var OrderCanceller = class {
15917
16136
  consensusStateId: sourceConsensusStateId
15918
16137
  }
15919
16138
  });
16139
+ await Promise.all([
16140
+ this.assertProofFresh(hyperbridge, sourceIProof),
16141
+ this.assertProofFresh(hyperbridge, destIProof)
16142
+ ]);
15920
16143
  const getRequestMessage = {
15921
16144
  kind: "GetRequest",
15922
16145
  requests: [getRequest],
@@ -15938,9 +16161,12 @@ var OrderCanceller = class {
15938
16161
  status: "HYPERBRIDGE_FINALIZED",
15939
16162
  metadata: statusUpdate.metadata
15940
16163
  };
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));
16164
+ await this.removeRecoveryItems(
16165
+ storageKeys.destProof,
16166
+ storageKeys.getRequest,
16167
+ storageKeys.sourceProof,
16168
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16169
+ );
15944
16170
  return;
15945
16171
  }
15946
16172
  }
@@ -15957,9 +16183,11 @@ var OrderCanceller = class {
15957
16183
  * @returns The native token dispatch fee in wei.
15958
16184
  */
15959
16185
  async quoteCancelFromDest(order) {
15960
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15961
- const destStateMachine = normalizeStateMachineId(order.destination);
15962
16186
  const sourceStateMachine = normalizeStateMachineId(order.source);
16187
+ const destStateMachine = normalizeStateMachineId(order.destination);
16188
+ if (sourceStateMachine === destStateMachine) {
16189
+ return { nativeValue: 0n, relayerFee: 0n };
16190
+ }
15963
16191
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15964
16192
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15965
16193
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -15997,11 +16225,13 @@ var OrderCanceller = class {
15997
16225
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
15998
16226
  */
15999
16227
  async *cancelOrderFromDest(order, indexerClient) {
16000
- const orderId = order.id;
16228
+ const storageKeys = this.recoveryStorageKeys(order);
16001
16229
  const destStateMachine = normalizeStateMachineId(order.destination);
16002
16230
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
16003
- let commitment = await this.ctx.cancellationStorage.getItem(
16004
- STORAGE_KEYS.postCommitment(orderId)
16231
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16232
+ let commitment = await this.getRecoveryItem(
16233
+ storageKeys.postCommitment,
16234
+ legacyStorageKeys.map((keys) => keys.postCommitment)
16005
16235
  );
16006
16236
  if (!commitment) {
16007
16237
  const quote = await this.quoteCancelFromDest(order);
@@ -16026,7 +16256,7 @@ var OrderCanceller = class {
16026
16256
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
16027
16257
  const postArgs = postEvent.args;
16028
16258
  commitment = postRequestCommitment(postArgs).commitment;
16029
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16259
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
16030
16260
  }
16031
16261
  const statusStream = indexerClient.postRequestStatusStream(commitment);
16032
16262
  for await (const statusUpdate of statusStream) {
@@ -16060,7 +16290,10 @@ var OrderCanceller = class {
16060
16290
  if (refundEvents.length === 0) {
16061
16291
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
16062
16292
  }
16063
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16293
+ await this.removeRecoveryItems(
16294
+ storageKeys.postCommitment,
16295
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16296
+ );
16064
16297
  yield {
16065
16298
  status: "CANCELLATION_COMPLETE",
16066
16299
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16103,7 +16336,7 @@ var OrderCanceller = class {
16103
16336
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16104
16337
  this.ctx.dest.config.stateMachineId
16105
16338
  );
16106
- const orderId = order.id;
16339
+ const orderId = this.orderId(order);
16107
16340
  const slotHash = await this.ctx.dest.client.readContract({
16108
16341
  abi: ABI3,
16109
16342
  address: intentGatewayV2Address,
@@ -16120,6 +16353,7 @@ var OrderCanceller = class {
16120
16353
  yield { status: "DESTINATION_FINALIZED", proof };
16121
16354
  return proof;
16122
16355
  } catch (e) {
16356
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16123
16357
  lastFailedHeight = latestHeight;
16124
16358
  await sleep(1e4);
16125
16359
  }
@@ -16150,6 +16384,7 @@ var OrderCanceller = class {
16150
16384
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16151
16385
  await sleep(3e4);
16152
16386
  } catch (error) {
16387
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16153
16388
  this.logger.warn(
16154
16389
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16155
16390
  );
@@ -16158,14 +16393,128 @@ var OrderCanceller = class {
16158
16393
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16159
16394
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16160
16395
  } catch (error) {
16396
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16161
16397
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16162
16398
  this.logger.error(message2);
16163
16399
  throw new Error(message2);
16164
16400
  }
16165
16401
  }
16402
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16403
+ async assertProofFresh(hyperbridge, proof) {
16404
+ await retryPromise(
16405
+ () => hyperbridge.stateMachineUpdateTime({
16406
+ height: proof.height,
16407
+ id: {
16408
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16409
+ consensusStateId: proof.consensusStateId
16410
+ }
16411
+ }),
16412
+ {
16413
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16414
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16415
+ logger: this.logger,
16416
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16417
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16418
+ }
16419
+ );
16420
+ }
16421
+ /**
16422
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16423
+ * a consensus update required by that recovery attempt.
16424
+ */
16425
+ async clearGetRecoveryCache(order) {
16426
+ const keys = this.recoveryStorageKeys(order);
16427
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16428
+ await this.removeRecoveryItems(
16429
+ keys.destProof,
16430
+ keys.sourceProof,
16431
+ keys.getRequest,
16432
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16433
+ legacyKeys.destProof,
16434
+ legacyKeys.sourceProof,
16435
+ legacyKeys.getRequest
16436
+ ])
16437
+ );
16438
+ }
16439
+ /**
16440
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16441
+ * for an order.
16442
+ *
16443
+ * @param order - The order whose source/destination recovery state is stored.
16444
+ */
16445
+ recoveryStorageKeys(order) {
16446
+ const orderId = this.orderId(order);
16447
+ return {
16448
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16449
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16450
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16451
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16452
+ };
16453
+ }
16454
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16455
+ legacyRecoveryStorageKeys(order) {
16456
+ const orderId = this.orderId(order);
16457
+ return [
16458
+ {
16459
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16460
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16461
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16462
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16463
+ },
16464
+ {
16465
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16466
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16467
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16468
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16469
+ }
16470
+ ];
16471
+ }
16472
+ async getRecoveryItem(currentKey, legacyKeys) {
16473
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16474
+ if (current) return current;
16475
+ for (const legacyKey of new Set(legacyKeys)) {
16476
+ if (legacyKey === currentKey) continue;
16477
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16478
+ if (!legacy) continue;
16479
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16480
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16481
+ return legacy;
16482
+ }
16483
+ return null;
16484
+ }
16485
+ async removeRecoveryItems(...keys) {
16486
+ await Promise.all(
16487
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16488
+ );
16489
+ }
16490
+ /**
16491
+ * Returns the order identifier required to persist or clear recovery state.
16492
+ *
16493
+ * @param order - The order being cancelled.
16494
+ * @throws If the order has no identifier.
16495
+ */
16496
+ orderId(order) {
16497
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16498
+ return order.id;
16499
+ }
16500
+ /**
16501
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16502
+ *
16503
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16504
+ * @param commitment - Commitment of the GET request.
16505
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16506
+ */
16166
16507
  async queryDeliveredReceipt(hyperbridge, commitment) {
16167
16508
  return hyperbridge.queryResponseReceipt(commitment);
16168
16509
  }
16510
+ /**
16511
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16512
+ *
16513
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16514
+ * @param commitment - Commitment of the GET request.
16515
+ * @returns The delivered response receipt commitment.
16516
+ * @throws If no receipt is observed within the configured retry limit.
16517
+ */
16169
16518
  async pollDeliveredReceipt(hyperbridge, commitment) {
16170
16519
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16171
16520
  return retryPromise(
@@ -17378,6 +17727,178 @@ var OrderStatusChecker = class {
17378
17727
  return true;
17379
17728
  }
17380
17729
  };
17730
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17731
+ var LiquidityEngine = class {
17732
+ /**
17733
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17734
+ * @param chainConfigService - Resolves token decimals for formatted results.
17735
+ */
17736
+ constructor(queryClient, chainConfigService) {
17737
+ this.queryClient = queryClient;
17738
+ this.chainConfigService = chainConfigService;
17739
+ }
17740
+ queryClient;
17741
+ chainConfigService;
17742
+ /**
17743
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17744
+ * indexed output-token liquidity.
17745
+ *
17746
+ * Nexus filters balances to the canonical output token, then aggregates them
17747
+ * overall and by chain. The returned amounts are decimal strings: the total
17748
+ * uses the canonical Base output-token decimals, while each chain group uses
17749
+ * that chain's token decimals. They describe `snapshotTime`, not live
17750
+ * reservations or fill guarantees.
17751
+ *
17752
+ * @param params - Canonical Phantom-market input and output token addresses.
17753
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17754
+ * the directional pair.
17755
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17756
+ * or internally inconsistent snapshot data.
17757
+ */
17758
+ async getAvailableLiquiditySnapshot(params) {
17759
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17760
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17761
+ const response = await this.queryClient.request(
17762
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17763
+ { tokenA: tokenIn, tokenB: tokenOut }
17764
+ );
17765
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17766
+ if (!node) return;
17767
+ const commitment = node.commitment.toLowerCase();
17768
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17769
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17770
+ }
17771
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17772
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17773
+ }
17774
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17775
+ if (Number.isNaN(snapshotTime.getTime())) {
17776
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17777
+ }
17778
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17779
+ commitment,
17780
+ tokenAddress: tokenOut
17781
+ });
17782
+ return {
17783
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17784
+ providerCount,
17785
+ tokenAddress: tokenOut,
17786
+ snapshotTime,
17787
+ liquidityByChain: liquidityByChain.map((group) => ({
17788
+ ...group,
17789
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17790
+ }))
17791
+ };
17792
+ }
17793
+ /**
17794
+ * Requests server-side sums and distinct provider counts for one immutable
17795
+ * snapshot/output-token pair, including chain-level aggregate groups.
17796
+ *
17797
+ * `commitment` uniquely identifies the selected snapshot
17798
+ */
17799
+ async querySnapshotLiquidityAggregates(params) {
17800
+ const response = await this.queryClient.request(
17801
+ LIQUIDITY_PROVIDER_BALANCES,
17802
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17803
+ );
17804
+ const connection = response?.liquidityProviderBalances;
17805
+ const aggregates = connection?.aggregates;
17806
+ if (!connection || !aggregates) {
17807
+ throw new InvalidAvailableLiquiditySnapshotError(
17808
+ params.commitment,
17809
+ "liquidityProviderBalances aggregates are missing"
17810
+ );
17811
+ }
17812
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17813
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17814
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17815
+ const [chain, tokenAddress] = group.keys;
17816
+ if (!chain?.trim() || !tokenAddress) {
17817
+ throw new InvalidAvailableLiquiditySnapshotError(
17818
+ params.commitment,
17819
+ `liquidity group ${index} has invalid keys`
17820
+ );
17821
+ }
17822
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17823
+ tokenAddress,
17824
+ params.commitment,
17825
+ `liquidity group ${index} tokenAddress`
17826
+ );
17827
+ if (normalizedTokenAddress !== params.tokenAddress) {
17828
+ throw new InvalidAvailableLiquiditySnapshotError(
17829
+ params.commitment,
17830
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17831
+ );
17832
+ }
17833
+ return {
17834
+ chain: chain.trim(),
17835
+ tokenAddress: normalizedTokenAddress,
17836
+ totalLiquidity: parseSnapshotBigInt(
17837
+ group.sum.balance ?? "0",
17838
+ params.commitment,
17839
+ `liquidity group ${index} balance`
17840
+ ),
17841
+ providerCount: parseProviderCount(
17842
+ group.distinctCount.providerId,
17843
+ params.commitment,
17844
+ `liquidity group ${index}`
17845
+ )
17846
+ };
17847
+ });
17848
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17849
+ if (groupedTotal !== totalLiquidity) {
17850
+ throw new InvalidAvailableLiquiditySnapshotError(
17851
+ params.commitment,
17852
+ "grouped liquidity does not match the total liquidity"
17853
+ );
17854
+ }
17855
+ return { totalLiquidity, providerCount, liquidityByChain };
17856
+ }
17857
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17858
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17859
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17860
+ if (decimals === void 0) {
17861
+ throw new InvalidAvailableLiquiditySnapshotError(
17862
+ commitment,
17863
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17864
+ );
17865
+ }
17866
+ return viem.formatUnits(amount, decimals);
17867
+ }
17868
+ };
17869
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17870
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17871
+ constructor(commitment, reason) {
17872
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17873
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17874
+ }
17875
+ };
17876
+ function parseSnapshotBigInt(value, commitment, field) {
17877
+ try {
17878
+ const amount = BigInt(value);
17879
+ if (amount < 0n) {
17880
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17881
+ }
17882
+ return amount;
17883
+ } catch (error) {
17884
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17885
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17886
+ }
17887
+ }
17888
+ function parseProviderCount(value, commitment, field) {
17889
+ const count = Number(value);
17890
+ if (!Number.isSafeInteger(count) || count < 0) {
17891
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17892
+ }
17893
+ return count;
17894
+ }
17895
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17896
+ try {
17897
+ return normalizeEvmAddress(address, field);
17898
+ } catch {
17899
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17900
+ }
17901
+ }
17381
17902
 
17382
17903
  // src/protocols/intents/quote/types.ts
17383
17904
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17664,12 +18185,14 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17664
18185
  constructor(chainConfigService, getQueryClient) {
17665
18186
  this.chainConfigService = chainConfigService;
17666
18187
  this.getQueryClient = getQueryClient;
18188
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
17667
18189
  }
17668
18190
  chainConfigService;
17669
18191
  getQueryClient;
18192
+ pairResolver;
17670
18193
  async quote(params, source, destination) {
17671
18194
  validateQuoteParams(params);
17672
- const pair = this.resolveBaseSnapshotPair(params, source.stateMachineId, destination.stateMachineId);
18195
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
17673
18196
  if (!pair) {
17674
18197
  throw new UnsupportedIntentQuotePairError({
17675
18198
  source: source.stateMachineId,
@@ -17742,7 +18265,13 @@ var PhantomSnapshotIntentQuoteStrategy = class {
17742
18265
  throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
17743
18266
  }
17744
18267
  }
17745
- resolveBaseSnapshotPair(params, sourceStateMachineId, destinationStateMachineId) {
18268
+ };
18269
+ var PhantomSnapshotPairResolver = class {
18270
+ constructor(chainConfigService) {
18271
+ this.chainConfigService = chainConfigService;
18272
+ }
18273
+ chainConfigService;
18274
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
17746
18275
  const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
17747
18276
  const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
17748
18277
  if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
@@ -17794,6 +18323,8 @@ var IntentGateway = class _IntentGateway {
17794
18323
  gasEstimator;
17795
18324
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17796
18325
  quoteStrategies;
18326
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18327
+ phantomSnapshotPairResolver;
17797
18328
  /**
17798
18329
  * Private constructor — use {@link IntentGateway.create} instead.
17799
18330
  *
@@ -17837,6 +18368,7 @@ var IntentGateway = class _IntentGateway {
17837
18368
  this.bidManager = bidManager;
17838
18369
  this.gasEstimator = gasEstimator;
17839
18370
  this._crypto = crypto;
18371
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17840
18372
  this.quoteStrategies = {
17841
18373
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
17842
18374
  dest.configService,
@@ -17915,13 +18447,43 @@ var IntentGateway = class _IntentGateway {
17915
18447
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17916
18448
  return handler.quote({ ...params, strategy }, source, destination);
17917
18449
  }
18450
+ /**
18451
+ * Returns the output-token liquidity measured in the latest directional
18452
+ * Phantom snapshot for this gateway's source and destination.
18453
+ *
18454
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18455
+ * The snapshot itself determines the output token and chain to aggregate. The
18456
+ * amount is in the token's smallest unit and reflects the indexer's
18457
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18458
+ *
18459
+ * Requires a prior call to {@link withQueryClient}.
18460
+ */
18461
+ async queryAvailableLiquidity(params) {
18462
+ const { queryClient } = this.requireIndexer();
18463
+ const sourceStateMachineId = this.source.config.stateMachineId;
18464
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18465
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18466
+ if (!pair) {
18467
+ throw new UnsupportedIntentQuotePairError({
18468
+ source: sourceStateMachineId,
18469
+ destination: destinationStateMachineId,
18470
+ tokenIn: params.tokenIn,
18471
+ tokenOut: params.tokenOut,
18472
+ quoteSource: "Phantom snapshot pair"
18473
+ });
18474
+ }
18475
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18476
+ tokenIn: pair.tokenA,
18477
+ tokenOut: pair.tokenB
18478
+ });
18479
+ }
17918
18480
  /**
17919
18481
  * Bidirectional async generator that orchestrates the full order lifecycle:
17920
18482
  * placement, fee estimation, bid collection, and execution.
17921
18483
  *
17922
18484
  * **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
18485
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and sets
18486
+ * same-chain fees to twice the estimate. Cross-chain orders retain a 1% buffer
17925
18487
  * and include an additional 600k-gas fee uplift, while the wei cost used for
17926
18488
  * the `value` field receives a 2% buffer.
17927
18489
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
@@ -17931,8 +18493,8 @@ var IntentGateway = class _IntentGateway {
17931
18493
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17932
18494
  * subsequent status updates until the order is filled, exhausted, or fails.
17933
18495
  *
17934
- * @param order - The order to place and execute. `order.fees` may be 0; it
17935
- * will be estimated automatically if so.
18496
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18497
+ * may be 0; fees are estimated automatically if so.
17936
18498
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17937
18499
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17938
18500
  * @param options - Optional tuning parameters:
@@ -17945,10 +18507,11 @@ var IntentGateway = class _IntentGateway {
17945
18507
  * estimation returns zero.
17946
18508
  */
17947
18509
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18510
+ const executionOrder = { ...order };
17948
18511
  let value;
17949
- if (!order.fees || order.fees === 0n) {
18512
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17950
18513
  const estimate = await this.gasEstimator.estimateFillOrder({
17951
- order,
18514
+ order: executionOrder,
17952
18515
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
17953
18516
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
17954
18517
  });
@@ -17963,9 +18526,9 @@ var IntentGateway = class _IntentGateway {
17963
18526
  "source",
17964
18527
  this.source.config.stateMachineId
17965
18528
  );
17966
- order.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
18529
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
17967
18530
  }
17968
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18531
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
17969
18532
  const placeOrderFirst = await placeOrderGen.next();
17970
18533
  if (placeOrderFirst.done) {
17971
18534
  throw new Error("placeOrder generator completed without yielding");
@@ -18076,13 +18639,20 @@ var IntentGateway = class _IntentGateway {
18076
18639
  const gen = this.execute(order, graffiti, options);
18077
18640
  try {
18078
18641
  let input;
18642
+ let finalizedOrder;
18079
18643
  while (true) {
18080
18644
  const { value, done } = await gen.next(input);
18081
18645
  input = void 0;
18082
18646
  if (done) break;
18083
- if (value.status === "BIDS_RECEIVED") {
18647
+ if (value.status === "ORDER_PLACED") {
18648
+ finalizedOrder = value.order;
18084
18649
  yield value;
18085
- input = await this.autoSelect(order, value.bids);
18650
+ } else if (value.status === "BIDS_RECEIVED") {
18651
+ if (!finalizedOrder) {
18652
+ throw new Error("Received bids before the order was finalized");
18653
+ }
18654
+ yield value;
18655
+ input = await this.autoSelect(finalizedOrder, value.bids);
18086
18656
  } else if (value.status === "AWAITING_PLACE_ORDER") {
18087
18657
  input = yield value;
18088
18658
  } else {
@@ -22959,6 +23529,7 @@ exports.hyperbridgeAddress = hyperbridgeAddress;
22959
23529
  exports.maxBigInt = maxBigInt;
22960
23530
  exports.normalizeAddressForEvmBytes32 = normalizeAddressForEvmBytes32;
22961
23531
  exports.normalizeAddressForStateMachine = normalizeAddressForStateMachine;
23532
+ exports.normalizeEvmAddress = normalizeEvmAddress;
22962
23533
  exports.normalizeEvmChainId = normalizeEvmChainId;
22963
23534
  exports.normalizeStateMachineId = normalizeStateMachineId;
22964
23535
  exports.orderCommitment = orderCommitment;