@hyperbridge/sdk 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { createConsola, LogLevels } from 'consola';
2
- import { defineChain, keccak256, toHex, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, decodeAbiParameters, getAddress, encodeAbiParameters, isHex, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, zeroAddress, decodeFunctionResult, formatUnits } from 'viem';
2
+ import { defineChain, keccak256, toHex, createPublicClient, http, hexToBytes, toFunctionSelector, bytesToHex, encodeFunctionData, erc20Abi, bytesToBigInt, pad, toBytes, numberToBytes, decodeAbiParameters, getAddress, encodeAbiParameters, isHex, isAddress, maxUint256, parseAbiParameters, encodePacked, parseAbiItem, concat, decodeFunctionData, hexToString as hexToString$1, decodeEventLog, parseEventLogs, parseUnits, concatHex, zeroAddress, decodeFunctionResult, formatUnits } from 'viem';
3
3
  import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimism, polygonAmoy, polygon, base, arbitrum, bsc, mainnet, sepolia, gnosisChiado, bscTestnet, tron, unichain } from 'viem/chains';
4
4
  import { TronWeb } from 'tronweb';
5
5
  import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
@@ -2529,30 +2529,30 @@ var handlerV2_default = { ABI: ABI2 };
2529
2529
  function tronAddress(base58) {
2530
2530
  return `0x${TronWeb.address.toHex(base58).slice(2)}`;
2531
2531
  }
2532
- var Chains = /* @__PURE__ */ ((Chains3) => {
2533
- Chains3["BSC_CHAPEL"] = "EVM-97";
2534
- Chains3["GNOSIS_CHIADO"] = "EVM-10200";
2535
- Chains3["HYPERBRIDGE_GARGANTUA"] = "KUSAMA-4009";
2536
- Chains3["SEPOLIA"] = "EVM-11155111";
2537
- Chains3["MAINNET"] = "EVM-1";
2538
- Chains3["BSC_MAINNET"] = "EVM-56";
2539
- Chains3["ARBITRUM_MAINNET"] = "EVM-42161";
2540
- Chains3["ARBITRUM_SEPOLIA"] = "EVM-421614";
2541
- Chains3["BASE_MAINNET"] = "EVM-8453";
2542
- Chains3["BASE_SEPOLIA"] = "EVM-84532";
2543
- Chains3["OPTIMISM_MAINNET"] = "EVM-10";
2544
- Chains3["OPTIMISM_SEPOLIA"] = "EVM-11155420";
2545
- Chains3["GNOSIS_MAINNET"] = "EVM-100";
2546
- Chains3["SONEIUM_MAINNET"] = "EVM-1868";
2547
- Chains3["POLYGON_MAINNET"] = "EVM-137";
2548
- Chains3["POLKADOT_HUB_MAINNET"] = "EVM-420420419";
2549
- Chains3["POLYGON_AMOY"] = "EVM-80002";
2550
- Chains3["POLKADOT_ASSET_HUB_PASEO"] = "EVM-420420417";
2551
- Chains3["TRON_MAINNET"] = "EVM-728126428";
2552
- Chains3["TRON_NILE"] = "EVM-3448148188";
2553
- Chains3["PHAROS_MAINNET"] = "EVM-688600";
2554
- Chains3["PHAROS_ATLANTIC"] = "EVM-688689";
2555
- return Chains3;
2532
+ var Chains = /* @__PURE__ */ ((Chains2) => {
2533
+ Chains2["BSC_CHAPEL"] = "EVM-97";
2534
+ Chains2["GNOSIS_CHIADO"] = "EVM-10200";
2535
+ Chains2["HYPERBRIDGE_GARGANTUA"] = "KUSAMA-4009";
2536
+ Chains2["SEPOLIA"] = "EVM-11155111";
2537
+ Chains2["MAINNET"] = "EVM-1";
2538
+ Chains2["BSC_MAINNET"] = "EVM-56";
2539
+ Chains2["ARBITRUM_MAINNET"] = "EVM-42161";
2540
+ Chains2["ARBITRUM_SEPOLIA"] = "EVM-421614";
2541
+ Chains2["BASE_MAINNET"] = "EVM-8453";
2542
+ Chains2["BASE_SEPOLIA"] = "EVM-84532";
2543
+ Chains2["OPTIMISM_MAINNET"] = "EVM-10";
2544
+ Chains2["OPTIMISM_SEPOLIA"] = "EVM-11155420";
2545
+ Chains2["GNOSIS_MAINNET"] = "EVM-100";
2546
+ Chains2["SONEIUM_MAINNET"] = "EVM-1868";
2547
+ Chains2["POLYGON_MAINNET"] = "EVM-137";
2548
+ Chains2["POLKADOT_HUB_MAINNET"] = "EVM-420420419";
2549
+ Chains2["POLYGON_AMOY"] = "EVM-80002";
2550
+ Chains2["POLKADOT_ASSET_HUB_PASEO"] = "EVM-420420417";
2551
+ Chains2["TRON_MAINNET"] = "EVM-728126428";
2552
+ Chains2["TRON_NILE"] = "EVM-3448148188";
2553
+ Chains2["PHAROS_MAINNET"] = "EVM-688600";
2554
+ Chains2["PHAROS_ATLANTIC"] = "EVM-688689";
2555
+ return Chains2;
2556
2556
  })(Chains || {});
2557
2557
  var polkadotAssetHubPaseo = defineChain({
2558
2558
  id: 420420417,
@@ -2743,7 +2743,7 @@ var chainConfigs = {
2743
2743
  },
2744
2744
  addresses: {
2745
2745
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2746
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2746
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2747
2747
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2748
2748
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2749
2749
  UniswapRouter02: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
@@ -2759,6 +2759,7 @@ var chainConfigs = {
2759
2759
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2760
2760
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2761
2761
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2762
+ SimplexPaymaster: "0xD4340d7466e040626383cb9cda9307ba8E081149",
2762
2763
  Usdt0Oft: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
2763
2764
  },
2764
2765
  rpcEnvKey: "ETH_MAINNET",
@@ -2798,7 +2799,7 @@ var chainConfigs = {
2798
2799
  },
2799
2800
  addresses: {
2800
2801
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2801
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2802
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2802
2803
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2803
2804
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2804
2805
  UniswapRouter02: "0x10ED43C718714eb63d5aA57B78B54704E256024E",
@@ -2812,7 +2813,8 @@ var chainConfigs = {
2812
2813
  UniswapV4StateView: "0xd13dd3d6e93f276fafc9db9e6bb47c1180aee0c4",
2813
2814
  Calldispatcher: "0xc71251c8b3e7b02697a84363eef6dce8dfbdf333",
2814
2815
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2815
- EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108"
2816
+ EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2817
+ SimplexPaymaster: "0xeD02f9f0df8F562B89cC5b25867Ad3C2d61252A9"
2816
2818
  // "Usdt0Oft": Not available on BSC
2817
2819
  },
2818
2820
  rpcEnvKey: "BSC_MAINNET",
@@ -2854,7 +2856,7 @@ var chainConfigs = {
2854
2856
  },
2855
2857
  addresses: {
2856
2858
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2857
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2859
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2858
2860
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2859
2861
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2860
2862
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2870,6 +2872,7 @@ var chainConfigs = {
2870
2872
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2871
2873
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2872
2874
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2875
+ SimplexPaymaster: "0x7281Bccb4f0BCE44F3B8542d1fC5e51c2F5fC08C",
2873
2876
  Usdt0Oft: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92"
2874
2877
  },
2875
2878
  rpcEnvKey: "ARBITRUM_MAINNET",
@@ -2911,7 +2914,7 @@ var chainConfigs = {
2911
2914
  },
2912
2915
  addresses: {
2913
2916
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2914
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2917
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2915
2918
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
2916
2919
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2917
2920
  UniswapRouter02: "0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24",
@@ -2924,6 +2927,7 @@ var chainConfigs = {
2924
2927
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2925
2928
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2926
2929
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2930
+ SimplexPaymaster: "0x15b3B03C870c7ef252029c35A12d3b339F5c8d7f",
2927
2931
  AerodromeRouter: "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
2928
2932
  UniswapV4PositionManager: "0x7c5f5a4bbd8fd63184577525326123b519429bdc",
2929
2933
  UniswapV4PoolManager: "0x498581ff718922c3f8e6a244956af099b2652b2b",
@@ -2970,7 +2974,7 @@ var chainConfigs = {
2970
2974
  },
2971
2975
  addresses: {
2972
2976
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
2973
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
2977
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
2974
2978
  TokenGateway: "0x8b536105b6Fae2aE9199f5146D3C57Dfe53b614E",
2975
2979
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
2976
2980
  UniswapRouter02: "0xd2f9496824951D5237cC71245D659E48d0d5f9E8",
@@ -2986,6 +2990,7 @@ var chainConfigs = {
2986
2990
  Permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
2987
2991
  EntryPointV08: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
2988
2992
  CirclePaymaster: "0x0578cFB241215b77442a541325d6A4E6dFE700Ec",
2993
+ SimplexPaymaster: "0xe99acFe0f5fC4C8ea54A187D8D3b05f136150095",
2989
2994
  Usdt0Oft: "0x6BA10300f0DC58B7a1e4c0e41f5daBb7D7829e13"
2990
2995
  },
2991
2996
  rpcEnvKey: "POLYGON_MAINNET",
@@ -3081,7 +3086,7 @@ var chainConfigs = {
3081
3086
  },
3082
3087
  addresses: {
3083
3088
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3084
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3089
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3085
3090
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3086
3091
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3087
3092
  UniswapRouter02: "0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2",
@@ -3116,7 +3121,7 @@ var chainConfigs = {
3116
3121
  },
3117
3122
  addresses: {
3118
3123
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3119
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3124
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3120
3125
  TokenGateway: "0xFd413e3AFe560182C4471F4d143A96d3e259B6dE",
3121
3126
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3122
3127
  UniswapRouter02: "0xB2e26652e4BAd1e56055A051f922E06760cA0BFE",
@@ -3271,7 +3276,7 @@ var chainConfigs = {
3271
3276
  },
3272
3277
  addresses: {
3273
3278
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
3274
- SolverAccount: "0x3bdB532d91F165BeFad5542700205f5BB5152a82",
3279
+ SolverAccount: "0xfCd233b937D7622AAc63ced3C9A1A12F4a6B64E3",
3275
3280
  Host: "0x620128E2B19193d6Bd244a3AC8D3bBa0541B19c3",
3276
3281
  Calldispatcher: "0xE2C7e576E26E0bE7aC97c6fE925bcDAbD87c4bEd"
3277
3282
  },
@@ -5157,6 +5162,24 @@ var ChainConfigService = class {
5157
5162
  getAssetAddress(chain, symbol) {
5158
5163
  return this.getConfig(chain)?.assets?.[symbol];
5159
5164
  }
5165
+ /**
5166
+ * Resolves configured token metadata from an address on a specific chain.
5167
+ * This is used by SDK helpers that accept token addresses rather than caller-
5168
+ * supplied symbols or decimals.
5169
+ */
5170
+ getAssetMetadataByAddress(chain, address) {
5171
+ const config = this.getConfig(chain);
5172
+ if (!config?.assets) return;
5173
+ const normalizedAddress = address.toLowerCase();
5174
+ for (const [symbol, configuredAddress] of Object.entries(config.assets)) {
5175
+ if (configuredAddress.toLowerCase() !== normalizedAddress) continue;
5176
+ return {
5177
+ symbol,
5178
+ address: configuredAddress,
5179
+ decimals: config.tokenDecimals?.[symbol]
5180
+ };
5181
+ }
5182
+ }
5160
5183
  getUsdtAsset(chain) {
5161
5184
  return this.getConfig(chain)?.assets?.USDT ?? "0x";
5162
5185
  }
@@ -5263,6 +5286,9 @@ var ChainConfigService = class {
5263
5286
  getCirclePaymasterAddress(chain) {
5264
5287
  return this.getConfig(chain)?.addresses.CirclePaymaster;
5265
5288
  }
5289
+ getSimplexPaymasterAddress(chain) {
5290
+ return this.getConfig(chain)?.addresses.SimplexPaymaster;
5291
+ }
5266
5292
  getHyperbridgeAddress() {
5267
5293
  return hyperbridgeAddress;
5268
5294
  }
@@ -7083,6 +7109,16 @@ var ExpectedError = class _ExpectedError extends Error {
7083
7109
  return error instanceof _ExpectedError;
7084
7110
  }
7085
7111
  };
7112
+ var MISSING_CONSENSUS_UPDATE_TIME_MESSAGE = "Consensus update time not found";
7113
+ var MissingConsensusUpdateTimeError = class _MissingConsensusUpdateTimeError extends Error {
7114
+ constructor(message = MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, options) {
7115
+ super(message, options);
7116
+ this.name = "Hyperbridge/SDK/MissingConsensusUpdateTimeError";
7117
+ }
7118
+ static isError(error) {
7119
+ return error instanceof _MissingConsensusUpdateTimeError || error instanceof Error && error.message.toLowerCase().includes(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE.toLowerCase());
7120
+ }
7121
+ };
7086
7122
 
7087
7123
  // src/configs/constants.ts
7088
7124
  var ISMP_PREFIX = ":child_storage:default:ISMPv2";
@@ -7396,8 +7432,15 @@ var SubstrateChain = class _SubstrateChain {
7396
7432
  id: stateMachineId,
7397
7433
  height: Number(stateMachineHeight.height)
7398
7434
  };
7399
- const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7400
- return BigInt(updateTime);
7435
+ try {
7436
+ const updateTime = await this.rpcClient.call("ismp_queryStateMachineUpdateTime", [payload]);
7437
+ return BigInt(updateTime);
7438
+ } catch (error) {
7439
+ if (MissingConsensusUpdateTimeError.isError(error)) {
7440
+ throw new MissingConsensusUpdateTimeError(MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, { cause: error });
7441
+ }
7442
+ throw error;
7443
+ }
7401
7444
  }
7402
7445
  /**
7403
7446
  * Get the challenge period for a given state machine id.
@@ -7830,7 +7873,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7830
7873
  */
7831
7874
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
7832
7875
  const keyPair = this.getKeyPair();
7833
- let baseTip = 500000000000n;
7876
+ const baseTip = 1000000000n;
7834
7877
  let attempt = 0;
7835
7878
  while (attempt < maxRetries) {
7836
7879
  const currentTip = baseTip * BigInt(2 ** attempt);
@@ -8143,26 +8186,76 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8143
8186
  };
8144
8187
  }
8145
8188
  /**
8146
- * Subscribes to PhantomOrderRegistered events from the intents coprocessor pallet.
8147
- * Calls the callback for each new phantom order as blocks arrive.
8148
- * Returns an unsubscribe function to stop the subscription.
8149
- */
8150
- async subscribePhantomOrders(callback) {
8151
- const unsub = await this.api.query.system.events((records) => {
8152
- for (const { event } of records) {
8153
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8154
- const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8155
- callback({
8156
- commitment: commitment.toHex(),
8157
- chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8158
- createdAt: createdAt.toNumber(),
8159
- tokenA: tokenA.toHex(),
8160
- tokenB: tokenB.toHex(),
8161
- standardAmount: BigInt(standardAmount.toString())
8162
- });
8189
+ * Reads the PhantomOrderRegistered events emitted in a single block.
8190
+ */
8191
+ async getPhantomOrdersInBlock(blockNumber) {
8192
+ const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8193
+ const apiAt = await this.api.at(blockHash);
8194
+ const records = await apiAt.query.system.events();
8195
+ const orders = [];
8196
+ for (const { event } of records) {
8197
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8198
+ const [commitment, chain, createdAt, tokenA, tokenB, standardAmount] = event.data;
8199
+ orders.push({
8200
+ commitment: commitment.toHex(),
8201
+ chain: new TextDecoder().decode(hexToU8a(chain.toHex())),
8202
+ createdAt: createdAt.toNumber(),
8203
+ tokenA: tokenA.toHex(),
8204
+ tokenB: tokenB.toHex(),
8205
+ standardAmount: BigInt(standardAmount.toString())
8206
+ });
8207
+ }
8208
+ return orders;
8209
+ }
8210
+ /**
8211
+ * Polls for newly registered phantom orders, invoking the callback once per order.
8212
+ *
8213
+ * Each tick reads the current head and scans every block between the last one processed and that
8214
+ * head, so the block cursor — not the connection — determines what has been seen. This replaced a
8215
+ * system.events subscription, which was only as reliable as its socket: polkadot-js reconnects
8216
+ * the transport but does not reliably re-establish storage subscriptions, and anything emitted
8217
+ * while disconnected was lost silently. With a bid window measured in a handful of blocks, that
8218
+ * meant silently missed bids.
8219
+ *
8220
+ * Scanning a block range is gap-free rather than merely self-healing: an outage delays orders but
8221
+ * cannot drop them, because the cursor only advances past a block whose events were actually
8222
+ * read. Recovery replays the backlog.
8223
+ *
8224
+ * Returns a function that stops polling.
8225
+ */
8226
+ pollPhantomOrders(callback, options = {}) {
8227
+ const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8228
+ let cursor = null;
8229
+ let inFlight = false;
8230
+ let stopped = false;
8231
+ const tick = async () => {
8232
+ if (inFlight || stopped) return;
8233
+ inFlight = true;
8234
+ try {
8235
+ const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8236
+ if (cursor === null) {
8237
+ cursor = Math.max(head - 1 - lookbackBlocks, -1);
8238
+ }
8239
+ if (head <= cursor) return;
8240
+ const to = Math.min(head, cursor + maxBlocksPerPoll);
8241
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8242
+ if (stopped) return;
8243
+ const orders = await this.getPhantomOrdersInBlock(blockNumber);
8244
+ for (const order of orders) callback(order);
8245
+ cursor = blockNumber;
8246
+ }
8247
+ } catch (err) {
8248
+ onError?.(err);
8249
+ } finally {
8250
+ inFlight = false;
8163
8251
  }
8164
- });
8165
- return unsub;
8252
+ };
8253
+ void tick();
8254
+ const timer = setInterval(() => void tick(), intervalMs);
8255
+ return () => {
8256
+ stopped = true;
8257
+ clearInterval(timer);
8258
+ };
8166
8259
  }
8167
8260
  };
8168
8261
  var TronChain = class _TronChain {
@@ -9295,7 +9388,12 @@ function sleep(ms) {
9295
9388
  async function waitForChallengePeriod(chain, stateMachineHeight) {
9296
9389
  const challengePeriod = await chain.challengePeriod(stateMachineHeight.id);
9297
9390
  if (challengePeriod === BigInt(0)) return;
9298
- const updateTime = await chain.stateMachineUpdateTime(stateMachineHeight);
9391
+ const updateTime = await retryPromise(() => chain.stateMachineUpdateTime(stateMachineHeight), {
9392
+ maxRetries: 3,
9393
+ backoffMs: 500,
9394
+ logMessage: "Fetching consensus update time while waiting for the challenge period",
9395
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
9396
+ });
9299
9397
  let currentTimestamp = await chain.timestamp();
9300
9398
  let timeElapsed = currentTimestamp - updateTime;
9301
9399
  if (timeElapsed > challengePeriod) return;
@@ -9442,6 +9540,10 @@ function normalizeAddressForEvmBytes32(address) {
9442
9540
  if (address.length === 66) return address;
9443
9541
  return bytes20ToBytes32(address);
9444
9542
  }
9543
+ function normalizeEvmAddress(address, field = "address") {
9544
+ if (!isAddress(address)) throw new TypeError(`${field} must be a valid EVM address`);
9545
+ return address.toLowerCase();
9546
+ }
9445
9547
  function normalizeAddressForStateMachine(address, stateMachineId) {
9446
9548
  return isEvmChain(normalizeStateMachineId(stateMachineId)) ? normalizeAddressForEvmBytes32(address) : address;
9447
9549
  }
@@ -9449,14 +9551,16 @@ var DEFAULT_LOGGER = createConsola({
9449
9551
  level: LogLevels.silent
9450
9552
  });
9451
9553
  async function retryPromise(operation, retryConfig) {
9452
- const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed" } = retryConfig;
9554
+ const { logger = DEFAULT_LOGGER, logMessage = "Retry operation failed", shouldRetry } = retryConfig;
9453
9555
  let lastError;
9454
9556
  for (let i = 0; i < retryConfig.maxRetries; i++) {
9455
9557
  try {
9456
9558
  return await operation();
9457
9559
  } catch (error) {
9560
+ if (shouldRetry && !shouldRetry(error)) throw error;
9458
9561
  logger.trace(`Retrying(${i}) > ${logMessage}`);
9459
9562
  lastError = error;
9563
+ if (i === retryConfig.maxRetries - 1) break;
9460
9564
  await new Promise((resolve) => setTimeout(resolve, retryConfig.backoffMs * 2 ** i));
9461
9565
  }
9462
9566
  }
@@ -11500,6 +11604,82 @@ query OrderStatus($commitment: String!) {
11500
11604
  }
11501
11605
  }
11502
11606
  }`;
11607
+ var LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT = `
11608
+ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11609
+ phantomOrderPriceSnapshots(
11610
+ filter: {
11611
+ and: [
11612
+ { tokenA: { equalTo: $tokenA } }
11613
+ { tokenB: { equalTo: $tokenB } }
11614
+ { medianPrice: { isNull: false } }
11615
+ ]
11616
+ }
11617
+ orderBy: BLOCK_NUMBER_DESC
11618
+ first: 1
11619
+ ) {
11620
+ nodes {
11621
+ commitment
11622
+ tokenA
11623
+ tokenB
11624
+ standardAmount
11625
+ blockNumber
11626
+ medianPrice
11627
+ lowestPrice
11628
+ highestPrice
11629
+ bidCount
11630
+ snapshotTime
11631
+ }
11632
+ }
11633
+ }`;
11634
+ var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11635
+ query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11636
+ phantomOrderPriceSnapshots(
11637
+ filter: {
11638
+ and: [
11639
+ { tokenA: { equalTo: $tokenA } }
11640
+ { tokenB: { equalTo: $tokenB } }
11641
+ ]
11642
+ }
11643
+ orderBy: SNAPSHOT_TIME_DESC
11644
+ first: 1
11645
+ ) {
11646
+ nodes {
11647
+ commitment
11648
+ tokenA
11649
+ tokenB
11650
+ snapshotTime
11651
+ }
11652
+ }
11653
+ }`;
11654
+ var LIQUIDITY_PROVIDER_BALANCES = `
11655
+ query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11656
+ liquidityProviderBalances(
11657
+ filter: {
11658
+ and: [
11659
+ { commitment: { equalTo: $commitment } }
11660
+ { tokenAddress: { equalTo: $tokenAddress } }
11661
+ ]
11662
+ }
11663
+ ) {
11664
+ aggregates {
11665
+ sum {
11666
+ balance
11667
+ }
11668
+ distinctCount {
11669
+ providerId
11670
+ }
11671
+ }
11672
+ groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11673
+ keys
11674
+ sum {
11675
+ balance
11676
+ }
11677
+ distinctCount {
11678
+ providerId
11679
+ }
11680
+ }
11681
+ }
11682
+ }`;
11503
11683
  var TOKEN_GATEWAY_ASSET_TELEPORTED_STATUS = `
11504
11684
  query TokenGatewayAssetTeleportedStatus($commitment: String!) {
11505
11685
  tokenGatewayAssetTeleportedV2s(
@@ -11561,6 +11741,29 @@ async function queryAssetTeleported(params) {
11561
11741
  function queryGetRequest(params) {
11562
11742
  return _queryGetRequestInternal(params);
11563
11743
  }
11744
+ async function _queryLatestPhantomOrderPriceSnapshot(params) {
11745
+ const response = await params.queryClient.request(
11746
+ LATEST_PHANTOM_ORDER_PRICE_SNAPSHOT,
11747
+ {
11748
+ tokenA: params.tokenA.toLowerCase(),
11749
+ tokenB: params.tokenB.toLowerCase()
11750
+ }
11751
+ );
11752
+ const snapshot = response.phantomOrderPriceSnapshots.nodes[0];
11753
+ if (!snapshot?.medianPrice) return;
11754
+ return {
11755
+ commitment: snapshot.commitment,
11756
+ tokenA: snapshot.tokenA,
11757
+ tokenB: snapshot.tokenB,
11758
+ standardAmount: BigInt(snapshot.standardAmount),
11759
+ blockNumber: BigInt(snapshot.blockNumber),
11760
+ medianPrice: BigInt(snapshot.medianPrice),
11761
+ lowestPrice: snapshot.lowestPrice === null ? void 0 : BigInt(snapshot.lowestPrice),
11762
+ highestPrice: snapshot.highestPrice === null ? void 0 : BigInt(snapshot.highestPrice),
11763
+ bidCount: snapshot.bidCount,
11764
+ snapshotTime: new Date(snapshot.snapshotTime)
11765
+ };
11766
+ }
11564
11767
  async function _queryTokenGatewayAssetTeleportedInternal(params) {
11565
11768
  const { commitmentHash, queryClient: client, logger = DEFAULT_LOGGER } = params;
11566
11769
  const response = await retryPromise(
@@ -12071,6 +12274,7 @@ function createCancellationStorage(options = {}) {
12071
12274
  const decoded = decodeIProof(value);
12072
12275
  return decoded;
12073
12276
  }
12277
+ if (key2.includes("postCommitment")) return value;
12074
12278
  throw new Error(`Unknown storage key type: ${key2}`);
12075
12279
  };
12076
12280
  const setItem = async (key2, value) => {
@@ -12084,6 +12288,10 @@ function createCancellationStorage(options = {}) {
12084
12288
  await baseStorage.setItem(key2, encoded);
12085
12289
  return;
12086
12290
  }
12291
+ if (key2.includes("postCommitment") && typeof value === "string") {
12292
+ await baseStorage.setItem(key2, value);
12293
+ return;
12294
+ }
12087
12295
  throw new Error(`Unknown storage key type: ${key2}`);
12088
12296
  };
12089
12297
  const removeItem = async (key2) => {
@@ -12097,11 +12305,25 @@ function createCancellationStorage(options = {}) {
12097
12305
  });
12098
12306
  }
12099
12307
  var STORAGE_KEYS = Object.freeze({
12100
- destProof: (orderId) => `cancel-order:${orderId}:destProof`,
12101
- getRequest: (orderId) => `cancel-order:${orderId}:getRequest`,
12102
- sourceProof: (orderId) => `cancel-order:${orderId}:sourceProof`,
12103
- postCommitment: (orderId) => `cancel-order:${orderId}:postCommitment`
12308
+ destProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:destProof`,
12309
+ getRequest: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:getRequest`,
12310
+ sourceProof: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:sourceProof`,
12311
+ postCommitment: (orderId, source, destination) => `cancel-order:${cancellationScope(orderId, source, destination)}:postCommitment`
12104
12312
  });
12313
+ var LEGACY_STORAGE_KEYS = Object.freeze({
12314
+ destProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:destProof`,
12315
+ getRequest: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:getRequest`,
12316
+ sourceProof: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:sourceProof`,
12317
+ postCommitment: (orderId, source, destination) => `cancel-order:${legacyCancellationScope(orderId, source, destination)}:postCommitment`
12318
+ });
12319
+ function cancellationScope(orderId, source, destination) {
12320
+ if (!source || !destination) return orderId;
12321
+ return `${orderId}:${encodeURIComponent(normalizeStateMachineId(source))}:${encodeURIComponent(normalizeStateMachineId(destination))}`;
12322
+ }
12323
+ function legacyCancellationScope(orderId, source, destination) {
12324
+ if (!source || !destination) return orderId;
12325
+ return `${orderId}:${encodeURIComponent(source)}:${encodeURIComponent(destination)}`;
12326
+ }
12105
12327
  function createUsedUserOpsStorage(options = {}) {
12106
12328
  const key = options.env ?? detectEnvironment();
12107
12329
  const driver = loadDriver({ key}) ?? inMemoryDriver();
@@ -15267,6 +15489,29 @@ async function convertFeeTokenToWei(ctx, feeTokenAmount, feeTokenIn, evmChainID)
15267
15489
  }
15268
15490
 
15269
15491
  // src/protocols/intents/OrderPlacer.ts
15492
+ function deriveCanonicalPlacedOrder(order, args) {
15493
+ const canonicalOrder = {
15494
+ ...order,
15495
+ user: args.user,
15496
+ source: args.source,
15497
+ destination: args.destination,
15498
+ deadline: args.deadline,
15499
+ nonce: args.nonce,
15500
+ fees: args.fees,
15501
+ session: args.session,
15502
+ predispatch: {
15503
+ assets: args.predispatch.map((asset) => ({ ...asset })),
15504
+ call: order.predispatch.call
15505
+ },
15506
+ inputs: args.inputs.map((asset) => ({ ...asset })),
15507
+ output: {
15508
+ beneficiary: args.beneficiary,
15509
+ assets: args.outputs.map((asset) => ({ ...asset })),
15510
+ call: order.output.call
15511
+ }
15512
+ };
15513
+ return { ...canonicalOrder, id: orderCommitment(canonicalOrder) };
15514
+ }
15270
15515
  var OrderPlacer = class {
15271
15516
  /**
15272
15517
  * @param ctx - Shared IntentsV2 context providing the source chain client,
@@ -15283,17 +15528,16 @@ var OrderPlacer = class {
15283
15528
  * 1. Yields `{ to, data, sessionPrivateKey }` — the caller must sign a
15284
15529
  * transaction sending `data` to `to` and pass the signed transaction back
15285
15530
  * via `gen.next(signedTx)`.
15286
- * 2. Returns `{ order, transactionHash }` — the finalized order with its
15287
- * on-chain `nonce`, `inputs`, and computed `id`, plus the placement
15288
- * transaction hash.
15531
+ * 2. Returns `{ order, transactionHash }` — the finalized order rebuilt from
15532
+ * the canonical `OrderPlaced` fields with its computed `id`, plus the
15533
+ * placement transaction hash.
15289
15534
  *
15290
15535
  * A fresh ephemeral session key is generated for every call. The key is
15291
15536
  * stored immediately (without a commitment) so it can be retrieved by
15292
15537
  * address, then updated with the commitment once the `OrderPlaced` event
15293
- * confirms the nonce.
15538
+ * confirms the canonical order.
15294
15539
  *
15295
- * @param order - The order to place. `order.session` is mutated in-place
15296
- * with the generated session key address before yielding.
15540
+ * @param order - The order to place. It is not mutated.
15297
15541
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
15298
15542
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI} (bytes32 zero).
15299
15543
  * @yields `{ to, data, sessionPrivateKey }` — target contract address,
@@ -15308,7 +15552,7 @@ var OrderPlacer = class {
15308
15552
  const account = privateKeyToAccount(privateKey);
15309
15553
  const sessionKeyAddress = account.address;
15310
15554
  const createdAt = Date.now();
15311
- order.session = sessionKeyAddress;
15555
+ const placementOrder = { ...order, session: sessionKeyAddress };
15312
15556
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, {
15313
15557
  privateKey,
15314
15558
  address: sessionKeyAddress,
@@ -15317,7 +15561,7 @@ var OrderPlacer = class {
15317
15561
  const data = encodeFunctionData({
15318
15562
  abi: ABI3,
15319
15563
  functionName: "placeOrder",
15320
- args: [transformOrderForContract(order), graffiti]
15564
+ args: [transformOrderForContract(placementOrder), graffiti]
15321
15565
  });
15322
15566
  const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15323
15567
  normalizeStateMachineId(order.source)
@@ -15337,20 +15581,15 @@ var OrderPlacer = class {
15337
15581
  if (!orderPlacedEvent) {
15338
15582
  throw new Error("OrderPlaced event not found in transaction receipt");
15339
15583
  }
15340
- order.nonce = orderPlacedEvent.args.nonce;
15341
- order.inputs = orderPlacedEvent.args.inputs.map((input) => ({
15342
- token: input.token,
15343
- amount: input.amount
15344
- }));
15345
- order.id = orderCommitment(order);
15584
+ const finalizedOrder = deriveCanonicalPlacedOrder(placementOrder, orderPlacedEvent.args);
15346
15585
  const sessionKeyData = {
15347
15586
  privateKey,
15348
15587
  address: sessionKeyAddress,
15349
- commitment: order.id,
15588
+ commitment: finalizedOrder.id,
15350
15589
  createdAt
15351
15590
  };
15352
15591
  await this.ctx.sessionKeyStorage.setSessionKeyByAddress(sessionKeyAddress, sessionKeyData);
15353
- return { order, receipt };
15592
+ return { order: finalizedOrder, receipt };
15354
15593
  }
15355
15594
  };
15356
15595
 
@@ -15680,7 +15919,7 @@ var OrderExecutor = class {
15680
15919
  }
15681
15920
  }
15682
15921
  };
15683
- var OrderCanceller = class {
15922
+ var OrderCanceller = class _OrderCanceller {
15684
15923
  /**
15685
15924
  * @param ctx - Shared IntentsV2 context providing the source and destination
15686
15925
  * chain clients, config service, and cancellation storage.
@@ -15689,6 +15928,9 @@ var OrderCanceller = class {
15689
15928
  this.ctx = ctx;
15690
15929
  }
15691
15930
  ctx;
15931
+ static DEFAULT_MAX_RECOVERY_RESTARTS = 1;
15932
+ static PROOF_FRESHNESS_MAX_RETRIES = 3;
15933
+ static PROOF_FRESHNESS_BACKOFF_MS = 500;
15692
15934
  logger = createConsola({
15693
15935
  level: LogLevels.info,
15694
15936
  formatOptions: { columns: 80, colors: true, compact: true, date: false }
@@ -15720,11 +15962,14 @@ var OrderCanceller = class {
15720
15962
  * @returns The native token dispatch fee in wei.
15721
15963
  */
15722
15964
  async quoteCancelFromSource(order) {
15723
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15724
15965
  const sourceStateMachine = normalizeStateMachineId(order.source);
15966
+ const destStateMachine = normalizeStateMachineId(order.destination);
15967
+ if (sourceStateMachine === destStateMachine) {
15968
+ return { nativeValue: 0n, relayerFee: 0n };
15969
+ }
15725
15970
  const height = order.deadline + 1n;
15726
15971
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(
15727
- normalizeStateMachineId(order.destination)
15972
+ destStateMachine
15728
15973
  );
15729
15974
  const slotHash = await this.ctx.dest.client.readContract({
15730
15975
  abi: ABI3,
@@ -15736,8 +15981,8 @@ var OrderCanceller = class {
15736
15981
  const context = encodeWithdrawalRequest(order, order.user);
15737
15982
  const getRequest = {
15738
15983
  source: sourceStateMachine,
15739
- dest: normalizeStateMachineId(order.destination),
15740
- from: this.ctx.source.configService.getIntentGatewayAddress(normalizeStateMachineId(order.destination)),
15984
+ dest: destStateMachine,
15985
+ from: this.ctx.source.configService.getIntentGatewayAddress(destStateMachine),
15741
15986
  nonce: await this.ctx.source.getHostNonce(),
15742
15987
  height,
15743
15988
  keys: [key],
@@ -15754,7 +15999,9 @@ var OrderCanceller = class {
15754
15999
  * cancellation is complete.
15755
16000
  *
15756
16001
  * Delegates to `cancelOrderFromSource` or `cancelOrderFromDest` based on
15757
- * the `from` parameter.
16002
+ * the `from` parameter. If Hyperbridge has pruned consensus data needed for
16003
+ * source-side GET recovery, clears its stale checkpoint and restarts the
16004
+ * source-side stream internally.
15758
16005
  *
15759
16006
  * @param order - The order to cancel.
15760
16007
  * @param indexerClient - Indexer client used to stream ISMP request status
@@ -15764,12 +16011,32 @@ var OrderCanceller = class {
15764
16011
  * cancellation lifecycle.
15765
16012
  */
15766
16013
  async *cancelOrder(order, indexerClient, options = {}) {
15767
- const isSameChain = order.source === order.destination;
16014
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16015
+ const destStateMachine = normalizeStateMachineId(order.destination);
16016
+ const isSameChain = sourceStateMachine === destStateMachine;
15768
16017
  if (options.from === "destination" && !isSameChain) {
15769
16018
  yield* this.cancelOrderFromDest(order, indexerClient);
15770
16019
  return;
15771
16020
  }
15772
- yield* this.cancelOrderFromSource(order, indexerClient);
16021
+ let recoveryRestarts = 0;
16022
+ while (true) {
16023
+ try {
16024
+ yield* this.cancelOrderFromSource(order, indexerClient);
16025
+ return;
16026
+ } catch (error) {
16027
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16028
+ if (recoveryRestarts >= _OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS) {
16029
+ throw new Error(
16030
+ `Cancellation recovery stopped after ${recoveryRestarts} restart(s): Hyperbridge no longer retains a required consensus update.`
16031
+ );
16032
+ }
16033
+ recoveryRestarts += 1;
16034
+ this.logger.warn(
16035
+ `Restarting cancellation recovery (${recoveryRestarts}/${_OrderCanceller.DEFAULT_MAX_RECOVERY_RESTARTS}) after a required consensus update was pruned`
16036
+ );
16037
+ await this.clearGetRecoveryCache(order);
16038
+ }
16039
+ }
15773
16040
  }
15774
16041
  /**
15775
16042
  * Async generator that cancels an order by initiating the cancel from the
@@ -15795,11 +16062,10 @@ var OrderCanceller = class {
15795
16062
  * @throws If the cancel transaction does not contain the expected on-chain event.
15796
16063
  */
15797
16064
  async *cancelOrderFromSource(order, indexerClient) {
15798
- const orderId = order.id;
15799
- const isSameChain = order.source === order.destination;
15800
- const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(
15801
- normalizeStateMachineId(order.source)
15802
- );
16065
+ const sourceStateMachine = normalizeStateMachineId(order.source);
16066
+ const destStateMachine = normalizeStateMachineId(order.destination);
16067
+ const isSameChain = sourceStateMachine === destStateMachine;
16068
+ const intentGatewayAddress = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15803
16069
  if (isSameChain) {
15804
16070
  const data = encodeFunctionData({
15805
16071
  abi: ABI3,
@@ -15828,18 +16094,37 @@ var OrderCanceller = class {
15828
16094
  };
15829
16095
  return;
15830
16096
  }
16097
+ const storageKeys = this.recoveryStorageKeys(order);
16098
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
15831
16099
  const hyperbridge = indexerClient.hyperbridge;
15832
- const sourceStateMachine = normalizeStateMachineId(order.source);
15833
16100
  const sourceConsensusStateId = this.ctx.source.configService.getConsensusStateId(sourceStateMachine);
15834
- let destIProof = await this.ctx.cancellationStorage.getItem(STORAGE_KEYS.destProof(orderId));
16101
+ let destIProof = await this.getRecoveryItem(
16102
+ storageKeys.destProof,
16103
+ legacyStorageKeys.map((keys) => keys.destProof)
16104
+ );
15835
16105
  if (!destIProof) {
15836
16106
  destIProof = yield* this.fetchDestinationProof(order, indexerClient);
15837
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.destProof(orderId), destIProof);
16107
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
15838
16108
  } else {
15839
- yield { status: "DESTINATION_FINALIZED", proof: destIProof };
16109
+ let refreshed = false;
16110
+ try {
16111
+ await this.assertProofFresh(hyperbridge, destIProof);
16112
+ } catch (error) {
16113
+ if (!MissingConsensusUpdateTimeError.isError(error)) throw error;
16114
+ await this.removeRecoveryItems(
16115
+ storageKeys.destProof,
16116
+ ...legacyStorageKeys.map((keys) => keys.destProof)
16117
+ );
16118
+ destIProof = yield* this.fetchDestinationProof(order, indexerClient);
16119
+ await this.ctx.cancellationStorage.setItem(storageKeys.destProof, destIProof);
16120
+ refreshed = true;
16121
+ }
16122
+ if (!refreshed) yield { status: "DESTINATION_FINALIZED", proof: destIProof };
15840
16123
  }
15841
- let getRequest = await this.ctx.cancellationStorage.getItem(
15842
- STORAGE_KEYS.getRequest(orderId)
16124
+ await this.assertProofFresh(hyperbridge, destIProof);
16125
+ let getRequest = await this.getRecoveryItem(
16126
+ storageKeys.getRequest,
16127
+ legacyStorageKeys.map((keys) => keys.getRequest)
15843
16128
  );
15844
16129
  if (!getRequest) {
15845
16130
  const quote = await this.quoteCancelFromSource(order);
@@ -15859,7 +16144,7 @@ var OrderCanceller = class {
15859
16144
  const request = events.find((e) => e.eventName === "GetRequestEvent");
15860
16145
  if (!request) throw new Error("GetRequest missing");
15861
16146
  getRequest = request.args;
15862
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.getRequest(orderId), getRequest);
16147
+ await this.ctx.cancellationStorage.setItem(storageKeys.getRequest, getRequest);
15863
16148
  yield {
15864
16149
  status: "CANCEL_STARTED",
15865
16150
  receipt
@@ -15878,8 +16163,9 @@ var OrderCanceller = class {
15878
16163
  metadata: statusUpdate.metadata
15879
16164
  };
15880
16165
  const sourceHeight = BigInt(statusUpdate.metadata.blockNumber);
15881
- let sourceIProof = await this.ctx.cancellationStorage.getItem(
15882
- STORAGE_KEYS.sourceProof(orderId)
16166
+ let sourceIProof = await this.getRecoveryItem(
16167
+ storageKeys.sourceProof,
16168
+ legacyStorageKeys.map((keys) => keys.sourceProof)
15883
16169
  );
15884
16170
  if (!sourceIProof) {
15885
16171
  sourceIProof = await fetchSourceProof(
@@ -15889,8 +16175,9 @@ var OrderCanceller = class {
15889
16175
  sourceConsensusStateId,
15890
16176
  sourceHeight
15891
16177
  );
15892
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.sourceProof(orderId), sourceIProof);
16178
+ await this.ctx.cancellationStorage.setItem(storageKeys.sourceProof, sourceIProof);
15893
16179
  }
16180
+ await this.assertProofFresh(hyperbridge, sourceIProof);
15894
16181
  await waitForChallengePeriod(hyperbridge, {
15895
16182
  height: sourceIProof.height,
15896
16183
  id: {
@@ -15898,6 +16185,10 @@ var OrderCanceller = class {
15898
16185
  consensusStateId: sourceConsensusStateId
15899
16186
  }
15900
16187
  });
16188
+ await Promise.all([
16189
+ this.assertProofFresh(hyperbridge, sourceIProof),
16190
+ this.assertProofFresh(hyperbridge, destIProof)
16191
+ ]);
15901
16192
  const getRequestMessage = {
15902
16193
  kind: "GetRequest",
15903
16194
  requests: [getRequest],
@@ -15919,9 +16210,12 @@ var OrderCanceller = class {
15919
16210
  status: "HYPERBRIDGE_FINALIZED",
15920
16211
  metadata: statusUpdate.metadata
15921
16212
  };
15922
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.destProof(orderId));
15923
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.getRequest(orderId));
15924
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.sourceProof(orderId));
16213
+ await this.removeRecoveryItems(
16214
+ storageKeys.destProof,
16215
+ storageKeys.getRequest,
16216
+ storageKeys.sourceProof,
16217
+ ...legacyStorageKeys.flatMap((keys) => [keys.destProof, keys.getRequest, keys.sourceProof])
16218
+ );
15925
16219
  return;
15926
16220
  }
15927
16221
  }
@@ -15938,9 +16232,11 @@ var OrderCanceller = class {
15938
16232
  * @returns The native token dispatch fee in wei.
15939
16233
  */
15940
16234
  async quoteCancelFromDest(order) {
15941
- if (order.source === order.destination) return { nativeValue: 0n, relayerFee: 0n };
15942
- const destStateMachine = normalizeStateMachineId(order.destination);
15943
16235
  const sourceStateMachine = normalizeStateMachineId(order.source);
16236
+ const destStateMachine = normalizeStateMachineId(order.destination);
16237
+ if (sourceStateMachine === destStateMachine) {
16238
+ return { nativeValue: 0n, relayerFee: 0n };
16239
+ }
15944
16240
  const destIntentGateway = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15945
16241
  const sourceIntentGateway = this.ctx.source.configService.getIntentGatewayAddress(sourceStateMachine);
15946
16242
  const relayerFee = await this.estimateRelayerFee(sourceStateMachine, destStateMachine);
@@ -15978,11 +16274,13 @@ var OrderCanceller = class {
15978
16274
  * @throws If the cancel transaction does not contain a `PostRequestEvent`.
15979
16275
  */
15980
16276
  async *cancelOrderFromDest(order, indexerClient) {
15981
- const orderId = order.id;
16277
+ const storageKeys = this.recoveryStorageKeys(order);
15982
16278
  const destStateMachine = normalizeStateMachineId(order.destination);
15983
16279
  const intentGatewayAddress = this.ctx.dest.configService.getIntentGatewayAddress(destStateMachine);
15984
- let commitment = await this.ctx.cancellationStorage.getItem(
15985
- STORAGE_KEYS.postCommitment(orderId)
16280
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16281
+ let commitment = await this.getRecoveryItem(
16282
+ storageKeys.postCommitment,
16283
+ legacyStorageKeys.map((keys) => keys.postCommitment)
15986
16284
  );
15987
16285
  if (!commitment) {
15988
16286
  const quote = await this.quoteCancelFromDest(order);
@@ -16007,7 +16305,7 @@ var OrderCanceller = class {
16007
16305
  if (!postEvent) throw new Error("PostRequestEvent not found in cancel transaction receipt");
16008
16306
  const postArgs = postEvent.args;
16009
16307
  commitment = postRequestCommitment(postArgs).commitment;
16010
- await this.ctx.cancellationStorage.setItem(STORAGE_KEYS.postCommitment(orderId), commitment);
16308
+ await this.ctx.cancellationStorage.setItem(storageKeys.postCommitment, commitment);
16011
16309
  }
16012
16310
  const statusStream = indexerClient.postRequestStatusStream(commitment);
16013
16311
  for await (const statusUpdate of statusStream) {
@@ -16041,7 +16339,10 @@ var OrderCanceller = class {
16041
16339
  if (refundEvents.length === 0) {
16042
16340
  throw new Error("EscrowRefunded event not found in source-chain delivery receipt");
16043
16341
  }
16044
- await this.ctx.cancellationStorage.removeItem(STORAGE_KEYS.postCommitment(orderId));
16342
+ await this.removeRecoveryItems(
16343
+ storageKeys.postCommitment,
16344
+ ...legacyStorageKeys.map((keys) => keys.postCommitment)
16345
+ );
16045
16346
  yield {
16046
16347
  status: "CANCELLATION_COMPLETE",
16047
16348
  blockNumber: statusUpdate.metadata.blockNumber,
@@ -16084,7 +16385,7 @@ var OrderCanceller = class {
16084
16385
  const intentGatewayV2Address = this.ctx.dest.configService.getIntentGatewayAddress(
16085
16386
  this.ctx.dest.config.stateMachineId
16086
16387
  );
16087
- const orderId = order.id;
16388
+ const orderId = this.orderId(order);
16088
16389
  const slotHash = await this.ctx.dest.client.readContract({
16089
16390
  abi: ABI3,
16090
16391
  address: intentGatewayV2Address,
@@ -16101,6 +16402,7 @@ var OrderCanceller = class {
16101
16402
  yield { status: "DESTINATION_FINALIZED", proof };
16102
16403
  return proof;
16103
16404
  } catch (e) {
16405
+ if (MissingConsensusUpdateTimeError.isError(e)) throw e;
16104
16406
  lastFailedHeight = latestHeight;
16105
16407
  await sleep(1e4);
16106
16408
  }
@@ -16131,6 +16433,7 @@ var OrderCanceller = class {
16131
16433
  this.logger.info(`Unsigned GET ${commitment} submitted; waiting for Hyperbridge response receipt`);
16132
16434
  await sleep(3e4);
16133
16435
  } catch (error) {
16436
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16134
16437
  this.logger.warn(
16135
16438
  `Unsigned GET submit failed for ${commitment}; polling response receipt before failing: ${String(error)}`
16136
16439
  );
@@ -16139,14 +16442,128 @@ var OrderCanceller = class {
16139
16442
  await this.pollDeliveredReceipt(hyperbridge, commitment);
16140
16443
  this.logger.info(`Confirmed Hyperbridge GET delivery for ${commitment}`);
16141
16444
  } catch (error) {
16445
+ if (MissingConsensusUpdateTimeError.isError(error)) throw error;
16142
16446
  const message2 = `Failed to deliver GET request to Hyperbridge; no response receipt found for ${commitment}: ${String(error)}`;
16143
16447
  this.logger.error(message2);
16144
16448
  throw new Error(message2);
16145
16449
  }
16146
16450
  }
16451
+ /** Verify that Hyperbridge still retains the consensus update for a proof. */
16452
+ async assertProofFresh(hyperbridge, proof) {
16453
+ await retryPromise(
16454
+ () => hyperbridge.stateMachineUpdateTime({
16455
+ height: proof.height,
16456
+ id: {
16457
+ stateId: parseStateMachineId(proof.stateMachine).stateId,
16458
+ consensusStateId: proof.consensusStateId
16459
+ }
16460
+ }),
16461
+ {
16462
+ maxRetries: _OrderCanceller.PROOF_FRESHNESS_MAX_RETRIES,
16463
+ backoffMs: _OrderCanceller.PROOF_FRESHNESS_BACKOFF_MS,
16464
+ logger: this.logger,
16465
+ logMessage: `Checking consensus update time for proof at height ${proof.height}`,
16466
+ shouldRetry: (error) => !MissingConsensusUpdateTimeError.isError(error)
16467
+ }
16468
+ );
16469
+ }
16470
+ /**
16471
+ * Drops the source-initiated GET recovery checkpoint after Hyperbridge prunes
16472
+ * a consensus update required by that recovery attempt.
16473
+ */
16474
+ async clearGetRecoveryCache(order) {
16475
+ const keys = this.recoveryStorageKeys(order);
16476
+ const legacyStorageKeys = this.legacyRecoveryStorageKeys(order);
16477
+ await this.removeRecoveryItems(
16478
+ keys.destProof,
16479
+ keys.sourceProof,
16480
+ keys.getRequest,
16481
+ ...legacyStorageKeys.flatMap((legacyKeys) => [
16482
+ legacyKeys.destProof,
16483
+ legacyKeys.sourceProof,
16484
+ legacyKeys.getRequest
16485
+ ])
16486
+ );
16487
+ }
16488
+ /**
16489
+ * Returns the pair-scoped storage keys used to resume cancellation recovery
16490
+ * for an order.
16491
+ *
16492
+ * @param order - The order whose source/destination recovery state is stored.
16493
+ */
16494
+ recoveryStorageKeys(order) {
16495
+ const orderId = this.orderId(order);
16496
+ return {
16497
+ destProof: STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16498
+ getRequest: STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16499
+ sourceProof: STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16500
+ postCommitment: STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16501
+ };
16502
+ }
16503
+ /** Older SDK key shapes, checked once and migrated to the normalized pair-scoped key. */
16504
+ legacyRecoveryStorageKeys(order) {
16505
+ const orderId = this.orderId(order);
16506
+ return [
16507
+ {
16508
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId, order.source, order.destination),
16509
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId, order.source, order.destination),
16510
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId, order.source, order.destination),
16511
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId, order.source, order.destination)
16512
+ },
16513
+ {
16514
+ destProof: LEGACY_STORAGE_KEYS.destProof(orderId),
16515
+ getRequest: LEGACY_STORAGE_KEYS.getRequest(orderId),
16516
+ sourceProof: LEGACY_STORAGE_KEYS.sourceProof(orderId),
16517
+ postCommitment: LEGACY_STORAGE_KEYS.postCommitment(orderId)
16518
+ }
16519
+ ];
16520
+ }
16521
+ async getRecoveryItem(currentKey, legacyKeys) {
16522
+ const current = await this.ctx.cancellationStorage.getItem(currentKey);
16523
+ if (current) return current;
16524
+ for (const legacyKey of new Set(legacyKeys)) {
16525
+ if (legacyKey === currentKey) continue;
16526
+ const legacy = await this.ctx.cancellationStorage.getItem(legacyKey);
16527
+ if (!legacy) continue;
16528
+ await this.ctx.cancellationStorage.setItem(currentKey, legacy);
16529
+ await this.ctx.cancellationStorage.removeItem(legacyKey);
16530
+ return legacy;
16531
+ }
16532
+ return null;
16533
+ }
16534
+ async removeRecoveryItems(...keys) {
16535
+ await Promise.all(
16536
+ [...new Set(keys)].map((key) => this.ctx.cancellationStorage.removeItem(key))
16537
+ );
16538
+ }
16539
+ /**
16540
+ * Returns the order identifier required to persist or clear recovery state.
16541
+ *
16542
+ * @param order - The order being cancelled.
16543
+ * @throws If the order has no identifier.
16544
+ */
16545
+ orderId(order) {
16546
+ if (!order.id) throw new Error("An order id is required to recover cancellation");
16547
+ return order.id;
16548
+ }
16549
+ /**
16550
+ * Reads the Hyperbridge response receipt for a GET request commitment.
16551
+ *
16552
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16553
+ * @param commitment - Commitment of the GET request.
16554
+ * @returns The receipt commitment when delivered, otherwise `undefined`.
16555
+ */
16147
16556
  async queryDeliveredReceipt(hyperbridge, commitment) {
16148
16557
  return hyperbridge.queryResponseReceipt(commitment);
16149
16558
  }
16559
+ /**
16560
+ * Polls Hyperbridge until it records a response receipt for the GET request.
16561
+ *
16562
+ * @param hyperbridge - Hyperbridge chain client used to query the receipt.
16563
+ * @param commitment - Commitment of the GET request.
16564
+ * @returns The delivered response receipt commitment.
16565
+ * @throws If no receipt is observed within the configured retry limit.
16566
+ */
16150
16567
  async pollDeliveredReceipt(hyperbridge, commitment) {
16151
16568
  this.logger.info(`Polling Hyperbridge GET response receipt for ${commitment}`);
16152
16569
  return retryPromise(
@@ -17359,6 +17776,178 @@ var OrderStatusChecker = class {
17359
17776
  return true;
17360
17777
  }
17361
17778
  };
17779
+ var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
17780
+ var LiquidityEngine = class {
17781
+ /**
17782
+ * @param queryClient - Nexus GraphQL client attached to the gateway.
17783
+ * @param chainConfigService - Resolves token decimals for formatted results.
17784
+ */
17785
+ constructor(queryClient, chainConfigService) {
17786
+ this.queryClient = queryClient;
17787
+ this.chainConfigService = chainConfigService;
17788
+ }
17789
+ queryClient;
17790
+ chainConfigService;
17791
+ /**
17792
+ * Retrieves the newest directional Phantom snapshot for a pair and its
17793
+ * indexed output-token liquidity.
17794
+ *
17795
+ * Nexus filters balances to the canonical output token, then aggregates them
17796
+ * overall and by chain. The returned amounts are decimal strings: the total
17797
+ * uses the canonical Base output-token decimals, while each chain group uses
17798
+ * that chain's token decimals. They describe `snapshotTime`, not live
17799
+ * reservations or fill guarantees.
17800
+ *
17801
+ * @param params - Canonical Phantom-market input and output token addresses.
17802
+ * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17803
+ * the directional pair.
17804
+ * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
17805
+ * or internally inconsistent snapshot data.
17806
+ */
17807
+ async getAvailableLiquiditySnapshot(params) {
17808
+ const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
17809
+ const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
17810
+ const response = await this.queryClient.request(
17811
+ LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
17812
+ { tokenA: tokenIn, tokenB: tokenOut }
17813
+ );
17814
+ const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
17815
+ if (!node) return;
17816
+ const commitment = node.commitment.toLowerCase();
17817
+ if (!COMMITMENT_PATTERN.test(commitment)) {
17818
+ throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
17819
+ }
17820
+ if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
17821
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
17822
+ }
17823
+ const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
17824
+ if (Number.isNaN(snapshotTime.getTime())) {
17825
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
17826
+ }
17827
+ const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
17828
+ commitment,
17829
+ tokenAddress: tokenOut
17830
+ });
17831
+ return {
17832
+ totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
17833
+ providerCount,
17834
+ tokenAddress: tokenOut,
17835
+ snapshotTime,
17836
+ liquidityByChain: liquidityByChain.map((group) => ({
17837
+ ...group,
17838
+ totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
17839
+ }))
17840
+ };
17841
+ }
17842
+ /**
17843
+ * Requests server-side sums and distinct provider counts for one immutable
17844
+ * snapshot/output-token pair, including chain-level aggregate groups.
17845
+ *
17846
+ * `commitment` uniquely identifies the selected snapshot
17847
+ */
17848
+ async querySnapshotLiquidityAggregates(params) {
17849
+ const response = await this.queryClient.request(
17850
+ LIQUIDITY_PROVIDER_BALANCES,
17851
+ { commitment: params.commitment, tokenAddress: params.tokenAddress }
17852
+ );
17853
+ const connection = response?.liquidityProviderBalances;
17854
+ const aggregates = connection?.aggregates;
17855
+ if (!connection || !aggregates) {
17856
+ throw new InvalidAvailableLiquiditySnapshotError(
17857
+ params.commitment,
17858
+ "liquidityProviderBalances aggregates are missing"
17859
+ );
17860
+ }
17861
+ const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
17862
+ const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
17863
+ const liquidityByChain = connection.groupedAggregates.map((group, index) => {
17864
+ const [chain, tokenAddress] = group.keys;
17865
+ if (!chain?.trim() || !tokenAddress) {
17866
+ throw new InvalidAvailableLiquiditySnapshotError(
17867
+ params.commitment,
17868
+ `liquidity group ${index} has invalid keys`
17869
+ );
17870
+ }
17871
+ const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
17872
+ tokenAddress,
17873
+ params.commitment,
17874
+ `liquidity group ${index} tokenAddress`
17875
+ );
17876
+ if (normalizedTokenAddress !== params.tokenAddress) {
17877
+ throw new InvalidAvailableLiquiditySnapshotError(
17878
+ params.commitment,
17879
+ `liquidity group ${index} tokenAddress does not match the snapshot output token`
17880
+ );
17881
+ }
17882
+ return {
17883
+ chain: chain.trim(),
17884
+ tokenAddress: normalizedTokenAddress,
17885
+ totalLiquidity: parseSnapshotBigInt(
17886
+ group.sum.balance ?? "0",
17887
+ params.commitment,
17888
+ `liquidity group ${index} balance`
17889
+ ),
17890
+ providerCount: parseProviderCount(
17891
+ group.distinctCount.providerId,
17892
+ params.commitment,
17893
+ `liquidity group ${index}`
17894
+ )
17895
+ };
17896
+ });
17897
+ const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
17898
+ if (groupedTotal !== totalLiquidity) {
17899
+ throw new InvalidAvailableLiquiditySnapshotError(
17900
+ params.commitment,
17901
+ "grouped liquidity does not match the total liquidity"
17902
+ );
17903
+ }
17904
+ return { totalLiquidity, providerCount, liquidityByChain };
17905
+ }
17906
+ /** Formats a raw amount using the configured decimals for its chain/token. */
17907
+ formatLiquidity(amount, chain, tokenAddress, commitment) {
17908
+ const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
17909
+ if (decimals === void 0) {
17910
+ throw new InvalidAvailableLiquiditySnapshotError(
17911
+ commitment,
17912
+ `token decimals are not configured for ${tokenAddress} on ${chain}`
17913
+ );
17914
+ }
17915
+ return formatUnits(amount, decimals);
17916
+ }
17917
+ };
17918
+ var InvalidAvailableLiquiditySnapshotError = class extends Error {
17919
+ /** Creates an error that identifies the invalid snapshot and field/reason. */
17920
+ constructor(commitment, reason) {
17921
+ super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
17922
+ this.name = "InvalidAvailableLiquiditySnapshotError";
17923
+ }
17924
+ };
17925
+ function parseSnapshotBigInt(value, commitment, field) {
17926
+ try {
17927
+ const amount = BigInt(value);
17928
+ if (amount < 0n) {
17929
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
17930
+ }
17931
+ return amount;
17932
+ } catch (error) {
17933
+ if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
17934
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
17935
+ }
17936
+ }
17937
+ function parseProviderCount(value, commitment, field) {
17938
+ const count = Number(value);
17939
+ if (!Number.isSafeInteger(count) || count < 0) {
17940
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
17941
+ }
17942
+ return count;
17943
+ }
17944
+ function normalizeIndexedLiquidityAddress(address, commitment, field) {
17945
+ try {
17946
+ return normalizeEvmAddress(address, field);
17947
+ } catch {
17948
+ throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
17949
+ }
17950
+ }
17362
17951
 
17363
17952
  // src/protocols/intents/quote/types.ts
17364
17953
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -17370,15 +17959,67 @@ var UnsupportedIntentQuoteStrategyError = class extends Error {
17370
17959
  var UnsupportedIntentQuotePairError = class extends Error {
17371
17960
  constructor(params) {
17372
17961
  super(
17373
- `No Uniswap v4 pool config found for ${params.tokenIn.symbol ?? params.tokenIn.address} -> ${params.tokenOut.symbol ?? params.tokenOut.address} on ${params.source} -> ${params.destination}`
17962
+ `No ${params.quoteSource ?? "Uniswap v4 pool config"} found for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}`
17374
17963
  );
17375
17964
  this.name = "UnsupportedIntentQuotePairError";
17376
17965
  }
17377
17966
  };
17967
+ var PhantomSnapshotUnavailableError = class extends Error {
17968
+ constructor(tokenA, tokenB) {
17969
+ super(`No Phantom order price snapshot found for ${tokenA} -> ${tokenB}`);
17970
+ this.name = "PhantomSnapshotUnavailableError";
17971
+ }
17972
+ };
17973
+ var InvalidPhantomSnapshotError = class extends Error {
17974
+ constructor(commitment, reason) {
17975
+ super(`Invalid Phantom order price snapshot ${commitment}: ${reason}`);
17976
+ this.name = "InvalidPhantomSnapshotError";
17977
+ }
17978
+ };
17979
+ var BPS_DENOMINATOR = 10000n;
17980
+ function validateQuoteParams(params) {
17981
+ const hasAmountIn = params.amountIn !== void 0;
17982
+ const hasAmountOut = params.amountOut !== void 0;
17983
+ if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
17984
+ if (params.amountIn !== void 0 && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
17985
+ if (params.amountOut !== void 0 && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
17986
+ if (params.tokenIn.toLowerCase() === params.tokenOut.toLowerCase()) {
17987
+ throw new Error("tokenIn and tokenOut cannot be the same");
17988
+ }
17989
+ }
17990
+ async function readProtocolFeeBps(chainConfigService, source) {
17991
+ const gatewayAddress = chainConfigService.getIntentGatewayAddress(source.stateMachineId);
17992
+ if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === zeroAddress) {
17993
+ throw new Error(`IntentGatewayV2 is not configured for chain ${source.stateMachineId}`);
17994
+ }
17995
+ const gatewayParams = await source.client.readContract({
17996
+ address: gatewayAddress,
17997
+ abi: IntentGatewayV2_default.ABI,
17998
+ functionName: "params"
17999
+ });
18000
+ const protocolFeeBps = Array.isArray(gatewayParams) ? BigInt(gatewayParams[4]) : BigInt(gatewayParams.protocolFeeBps ?? 0);
18001
+ if (protocolFeeBps < 0n || protocolFeeBps >= BPS_DENOMINATOR) {
18002
+ throw new Error(`Invalid IntentGateway protocol fee: ${protocolFeeBps} bps`);
18003
+ }
18004
+ return protocolFeeBps;
18005
+ }
18006
+ function deductProtocolFee(amount, protocolFeeBps) {
18007
+ if (protocolFeeBps <= 0n) return amount;
18008
+ const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
18009
+ return amount - fee;
18010
+ }
18011
+ function grossUpForProtocolFee(netAmount, protocolFeeBps) {
18012
+ if (protocolFeeBps <= 0n) return netAmount;
18013
+ if (protocolFeeBps >= BPS_DENOMINATOR) throw new Error("protocolFeeBps must be less than 10,000");
18014
+ return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
18015
+ }
18016
+ function divCeil(numerator, denominator) {
18017
+ if (denominator <= 0n) throw new Error("denominator must be greater than zero");
18018
+ return (numerator + denominator - 1n) / denominator;
18019
+ }
17378
18020
 
17379
18021
  // src/protocols/intents/quote/uniswapV4.ts
17380
18022
  var UNISWAP_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
17381
- var BPS_DENOMINATOR = 10000n;
17382
18023
  var UniswapV4IntentQuoteStrategy = class {
17383
18024
  constructor(chainConfigService) {
17384
18025
  this.chainConfigService = chainConfigService;
@@ -17386,10 +18027,10 @@ var UniswapV4IntentQuoteStrategy = class {
17386
18027
  chainConfigService;
17387
18028
  baseQuoteClient;
17388
18029
  async quote(params, source, destination) {
17389
- this.validateQuoteParams(params);
17390
- const protocolFeeBps = await this.readProtocolFeeBps(source.client, source.stateMachineId);
18030
+ validateQuoteParams(params);
18031
+ const protocolFeeBps = await readProtocolFeeBps(this.chainConfigService, source);
17391
18032
  const quoteClient = this.resolveQuoteClient(source, destination);
17392
- const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, UNISWAP_INTENT_QUOTE_CHAIN);
18033
+ const poolConfig = this.resolvePoolConfig(params, source.stateMachineId, destination.stateMachineId);
17393
18034
  return params.amountIn !== void 0 ? this.quoteExactInput({ params, client: quoteClient, protocolFeeBps, poolConfig }) : this.quoteExactOutput({ params, client: quoteClient, protocolFeeBps, poolConfig });
17394
18035
  }
17395
18036
  resolveQuoteClient(source, destination) {
@@ -17405,34 +18046,11 @@ var UniswapV4IntentQuoteStrategy = class {
17405
18046
  this.baseQuoteClient = baseQuoteClient;
17406
18047
  return baseQuoteClient;
17407
18048
  }
17408
- validateQuoteParams(params) {
17409
- const hasAmountIn = params.amountIn !== void 0;
17410
- const hasAmountOut = params.amountOut !== void 0;
17411
- if (hasAmountIn === hasAmountOut) throw new Error("Provide exactly one of amountIn or amountOut");
17412
- if (hasAmountIn && params.amountIn <= 0n) throw new Error("amountIn must be greater than zero");
17413
- if (hasAmountOut && params.amountOut <= 0n) throw new Error("amountOut must be greater than zero");
17414
- if (params.tokenIn.address.toLowerCase() === params.tokenOut.address.toLowerCase()) {
17415
- throw new Error("tokenIn and tokenOut cannot be the same");
17416
- }
17417
- }
17418
- async readProtocolFeeBps(client, chain) {
17419
- const gatewayAddress = this.chainConfigService.getIntentGatewayAddress(chain);
17420
- if (!gatewayAddress || gatewayAddress === "0x" || gatewayAddress === zeroAddress) {
17421
- throw new Error(`IntentGatewayV2 is not configured for chain ${chain}`);
17422
- }
17423
- const gatewayParams = await client.readContract({
17424
- address: gatewayAddress,
17425
- abi: IntentGatewayV2_default.ABI,
17426
- functionName: "params"
17427
- });
17428
- if (isGatewayParamsTuple(gatewayParams)) return BigInt(gatewayParams[4]);
17429
- return BigInt(gatewayParams.protocolFeeBps ?? 0);
17430
- }
17431
18049
  resolvePoolConfig(params, source, destination) {
17432
18050
  const override = params.uniswapV4?.poolKey;
17433
18051
  if (override) {
17434
18052
  const poolKey = normalizePoolKey(override);
17435
- const tokenInForQuote = getAddress(override.currencyIn ?? params.tokenIn.address);
18053
+ const tokenInForQuote = getAddress(override.currencyIn ?? params.tokenIn);
17436
18054
  if (tokenInForQuote !== poolKey.currency0 && tokenInForQuote !== poolKey.currency1) {
17437
18055
  throw new Error(
17438
18056
  `Input currency ${tokenInForQuote} is not part of the override pool (${poolKey.currency0}, ${poolKey.currency1}). For cross-chain quotes pass uniswapV4.poolKey.currencyIn with the Base-side input currency address.`
@@ -17440,11 +18058,11 @@ var UniswapV4IntentQuoteStrategy = class {
17440
18058
  }
17441
18059
  return {
17442
18060
  poolKey,
17443
- quoterAddress: this.resolveQuoterAddress(destination, override.quoterAddress),
18061
+ quoterAddress: this.resolveQuoterAddress(UNISWAP_INTENT_QUOTE_CHAIN, override.quoterAddress),
17444
18062
  tokenInForQuote
17445
18063
  };
17446
18064
  }
17447
- const poolConfig = this.resolveConfiguredPool(params, destination);
18065
+ const poolConfig = this.resolveConfiguredPool(params, source, destination, UNISWAP_INTENT_QUOTE_CHAIN);
17448
18066
  if (poolConfig) return poolConfig;
17449
18067
  throw new UnsupportedIntentQuotePairError({
17450
18068
  source,
@@ -17453,12 +18071,12 @@ var UniswapV4IntentQuoteStrategy = class {
17453
18071
  tokenOut: params.tokenOut
17454
18072
  });
17455
18073
  }
17456
- resolveConfiguredPool(params, chain) {
17457
- for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(chain)) {
17458
- const resolvedPool = this.resolveConfiguredPoolTokens(chain, pool);
18074
+ resolveConfiguredPool(params, sourceChain, destinationChain, quoteChain) {
18075
+ for (const pool of this.chainConfigService.getUniswapV4PoolConfigs(quoteChain)) {
18076
+ const resolvedPool = this.resolveConfiguredPoolTokens(quoteChain, pool);
17459
18077
  if (!resolvedPool) continue;
17460
- const tokenInForQuote = matchPoolToken(params.tokenIn, resolvedPool);
17461
- const tokenOutForQuote = matchPoolToken(params.tokenOut, resolvedPool);
18078
+ const tokenInForQuote = this.matchPoolToken(params.tokenIn, sourceChain, resolvedPool);
18079
+ const tokenOutForQuote = this.matchPoolToken(params.tokenOut, destinationChain, resolvedPool);
17462
18080
  if (!tokenInForQuote || !tokenOutForQuote || tokenInForQuote.symbol === tokenOutForQuote.symbol) continue;
17463
18081
  const { currency0, currency1 } = sortCurrencies(resolvedPool[0].address, resolvedPool[1].address);
17464
18082
  return {
@@ -17469,7 +18087,7 @@ var UniswapV4IntentQuoteStrategy = class {
17469
18087
  tickSpacing: pool.tickSpacing,
17470
18088
  hooks: getAddress(pool.hooks ?? zeroAddress)
17471
18089
  },
17472
- quoterAddress: this.resolveQuoterAddress(chain),
18090
+ quoterAddress: this.resolveQuoterAddress(quoteChain),
17473
18091
  tokenInForQuote: tokenInForQuote.address
17474
18092
  };
17475
18093
  }
@@ -17492,8 +18110,18 @@ var UniswapV4IntentQuoteStrategy = class {
17492
18110
  if (!address || address === "0x") return null;
17493
18111
  return { symbol, address: getAddress(address) };
17494
18112
  }
18113
+ matchPoolToken(tokenAddress, orderChain, poolTokens) {
18114
+ const addressMatch = poolTokens.find(
18115
+ (poolToken) => poolToken.address.toLowerCase() === tokenAddress.toLowerCase()
18116
+ );
18117
+ if (addressMatch) return addressMatch;
18118
+ const metadata = this.chainConfigService.getAssetMetadataByAddress(orderChain, tokenAddress);
18119
+ if (!metadata) return null;
18120
+ return poolTokens.find((poolToken) => poolToken.symbol === metadata.symbol) ?? null;
18121
+ }
17495
18122
  async quoteExactInput(args) {
17496
18123
  const amountIn = args.params.amountIn;
18124
+ if (amountIn === void 0) throw new Error("amountIn is required for an exact-input quote");
17497
18125
  const swapAmountIn = deductProtocolFee(amountIn, args.protocolFeeBps);
17498
18126
  const amountOut = await this.readV4QuoteExactInput(args.client, args.poolConfig, swapAmountIn);
17499
18127
  return {
@@ -17511,6 +18139,7 @@ var UniswapV4IntentQuoteStrategy = class {
17511
18139
  }
17512
18140
  async quoteExactOutput(args) {
17513
18141
  const amountOut = args.params.amountOut;
18142
+ if (amountOut === void 0) throw new Error("amountOut is required for an exact-output quote");
17514
18143
  const swapAmountIn = await this.readV4QuoteExactOutput(args.client, args.poolConfig, amountOut);
17515
18144
  const amountIn = grossUpForProtocolFee(swapAmountIn, args.protocolFeeBps);
17516
18145
  return {
@@ -17589,34 +18218,133 @@ function sortCurrencies(tokenIn, tokenOut) {
17589
18218
  const output = getAddress(tokenOut);
17590
18219
  return BigInt(input) < BigInt(output) ? { currency0: input, currency1: output } : { currency0: output, currency1: input };
17591
18220
  }
17592
- function matchPoolToken(token, poolTokens) {
17593
- const tokenAddress = token.address.toLowerCase();
17594
- const addressMatch = poolTokens.find((poolToken) => poolToken.address.toLowerCase() === tokenAddress);
17595
- if (addressMatch) return addressMatch;
17596
- const tokenSymbol = token.symbol?.toUpperCase();
17597
- if (!tokenSymbol) return null;
17598
- return poolTokens.find((poolToken) => poolToken.symbol.toUpperCase() === tokenSymbol) ?? null;
17599
- }
17600
18221
  function getZeroForOne(tokenIn, poolKey) {
17601
18222
  return getAddress(tokenIn).toLowerCase() === getAddress(poolKey.currency0).toLowerCase();
17602
18223
  }
17603
- function isGatewayParamsTuple(value) {
17604
- return Array.isArray(value);
17605
- }
17606
- function deductProtocolFee(amount, protocolFeeBps) {
17607
- if (protocolFeeBps <= 0n) return amount;
17608
- const fee = amount * protocolFeeBps / BPS_DENOMINATOR;
17609
- return amount - fee;
17610
- }
17611
- function grossUpForProtocolFee(netAmount, protocolFeeBps) {
17612
- if (protocolFeeBps <= 0n) return netAmount;
17613
- return divCeil(netAmount * BPS_DENOMINATOR, BPS_DENOMINATOR - protocolFeeBps);
18224
+
18225
+ // src/protocols/intents/quote/phantomSnapshot.ts
18226
+ var PHANTOM_INTENT_QUOTE_CHAIN = "EVM-8453" /* BASE_MAINNET */;
18227
+ var BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET = {
18228
+ USDC: "USDC",
18229
+ USDT: "USDC",
18230
+ cNGN: "cNGN"
18231
+ };
18232
+ var SUPPORTED_BASE_SNAPSHOT_PAIRS = /* @__PURE__ */ new Set(["USDC:cNGN", "cNGN:USDC"]);
18233
+ var PhantomSnapshotIntentQuoteStrategy = class {
18234
+ constructor(chainConfigService, getQueryClient) {
18235
+ this.chainConfigService = chainConfigService;
18236
+ this.getQueryClient = getQueryClient;
18237
+ this.pairResolver = new PhantomSnapshotPairResolver(chainConfigService);
18238
+ }
18239
+ chainConfigService;
18240
+ getQueryClient;
18241
+ pairResolver;
18242
+ async quote(params, source, destination) {
18243
+ validateQuoteParams(params);
18244
+ const pair = this.pairResolver.resolve(params, source.stateMachineId, destination.stateMachineId);
18245
+ if (!pair) {
18246
+ throw new UnsupportedIntentQuotePairError({
18247
+ source: source.stateMachineId,
18248
+ destination: destination.stateMachineId,
18249
+ tokenIn: params.tokenIn,
18250
+ tokenOut: params.tokenOut,
18251
+ quoteSource: "Phantom snapshot pair"
18252
+ });
18253
+ }
18254
+ const queryClient = this.getQueryClient();
18255
+ const [protocolFeeBps, snapshot] = await Promise.all([
18256
+ readProtocolFeeBps(this.chainConfigService, source),
18257
+ _queryLatestPhantomOrderPriceSnapshot({
18258
+ tokenA: pair.tokenA,
18259
+ tokenB: pair.tokenB,
18260
+ queryClient
18261
+ })
18262
+ ]);
18263
+ if (!snapshot) throw new PhantomSnapshotUnavailableError(pair.tokenA, pair.tokenB);
18264
+ this.validateSnapshot(snapshot);
18265
+ return this.quoteSnapshot(params, protocolFeeBps, snapshot);
18266
+ }
18267
+ quoteSnapshot(params, protocolFeeBps, snapshot) {
18268
+ if (params.amountIn !== void 0) {
18269
+ const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
18270
+ const amountOut = netAmountIn2 * snapshot.medianPrice / snapshot.standardAmount;
18271
+ if (amountOut <= 0n) {
18272
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "quote rounds down to zero output");
18273
+ }
18274
+ return this.buildResult("EXACT_INPUT", params.amountIn, amountOut, protocolFeeBps, snapshot);
18275
+ }
18276
+ if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
18277
+ const netAmountIn = divCeil(params.amountOut * snapshot.standardAmount, snapshot.medianPrice);
18278
+ const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
18279
+ return this.buildResult("EXACT_OUTPUT", amountIn, params.amountOut, protocolFeeBps, snapshot);
18280
+ }
18281
+ buildResult(tradeType, amountIn, amountOut, protocolFeeBps, snapshot) {
18282
+ return {
18283
+ strategy: "phantom_snapshot",
18284
+ tradeType,
18285
+ amountIn,
18286
+ amountOut,
18287
+ quoteMetadata: {
18288
+ quoteChain: PHANTOM_INTENT_QUOTE_CHAIN,
18289
+ commitment: snapshot.commitment,
18290
+ tokenA: snapshot.tokenA,
18291
+ tokenB: snapshot.tokenB,
18292
+ standardAmount: snapshot.standardAmount,
18293
+ medianPrice: snapshot.medianPrice,
18294
+ lowestPrice: snapshot.lowestPrice,
18295
+ highestPrice: snapshot.highestPrice,
18296
+ blockNumber: snapshot.blockNumber,
18297
+ snapshotTime: snapshot.snapshotTime,
18298
+ bidCount: snapshot.bidCount,
18299
+ protocolFeeBps
18300
+ }
18301
+ };
18302
+ }
18303
+ validateSnapshot(snapshot) {
18304
+ if (snapshot.standardAmount <= 0n) {
18305
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "standardAmount must be greater than zero");
18306
+ }
18307
+ if (snapshot.medianPrice <= 0n) {
18308
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "medianPrice must be greater than zero");
18309
+ }
18310
+ if (snapshot.bidCount <= 0) {
18311
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "bidCount must be greater than zero");
18312
+ }
18313
+ if (Number.isNaN(snapshot.snapshotTime.getTime())) {
18314
+ throw new InvalidPhantomSnapshotError(snapshot.commitment, "snapshotTime is invalid");
18315
+ }
18316
+ }
18317
+ };
18318
+ var PhantomSnapshotPairResolver = class {
18319
+ constructor(chainConfigService) {
18320
+ this.chainConfigService = chainConfigService;
18321
+ }
18322
+ chainConfigService;
18323
+ resolve(params, sourceStateMachineId, destinationStateMachineId) {
18324
+ const tokenIn = this.resolveBaseSnapshotToken(sourceStateMachineId, params.tokenIn);
18325
+ const tokenOut = this.resolveBaseSnapshotToken(destinationStateMachineId, params.tokenOut);
18326
+ if (!tokenIn || !tokenOut || !isSupportedSnapshotPair(tokenIn.symbol, tokenOut.symbol)) return;
18327
+ return { tokenA: tokenIn.address, tokenB: tokenOut.address };
18328
+ }
18329
+ resolveBaseSnapshotToken(chain, tokenAddress) {
18330
+ const orderAsset = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.symbol;
18331
+ if (!orderAsset) return;
18332
+ const snapshotAsset = BASE_SNAPSHOT_ASSET_BY_ORDER_ASSET[orderAsset];
18333
+ if (!snapshotAsset) return;
18334
+ const address = this.chainConfigService.getAssetAddress(PHANTOM_INTENT_QUOTE_CHAIN, snapshotAsset);
18335
+ if (!isConfiguredAddress(address)) return;
18336
+ return { symbol: snapshotAsset, address };
18337
+ }
18338
+ };
18339
+ function isSupportedSnapshotPair(tokenA, tokenB) {
18340
+ return SUPPORTED_BASE_SNAPSHOT_PAIRS.has(`${tokenA}:${tokenB}`);
17614
18341
  }
17615
- function divCeil(numerator, denominator) {
17616
- return (numerator + denominator - 1n) / denominator;
18342
+ function isConfiguredAddress(address) {
18343
+ return Boolean(address && address !== "0x" && !/^0x0{40}$/i.test(address));
17617
18344
  }
17618
18345
 
17619
18346
  // src/protocols/intents/IntentGateway.ts
18347
+ var CROSS_CHAIN_ORDER_FEE_GAS_BUMP = 600000n;
17620
18348
  var IntentGateway = class _IntentGateway {
17621
18349
  /** EVM chain on which orders are placed and escrowed. */
17622
18350
  source;
@@ -17644,6 +18372,8 @@ var IntentGateway = class _IntentGateway {
17644
18372
  gasEstimator;
17645
18373
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
17646
18374
  quoteStrategies;
18375
+ /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18376
+ phantomSnapshotPairResolver;
17647
18377
  /**
17648
18378
  * Private constructor — use {@link IntentGateway.create} instead.
17649
18379
  *
@@ -17687,7 +18417,12 @@ var IntentGateway = class _IntentGateway {
17687
18417
  this.bidManager = bidManager;
17688
18418
  this.gasEstimator = gasEstimator;
17689
18419
  this._crypto = crypto;
18420
+ this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
17690
18421
  this.quoteStrategies = {
18422
+ phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18423
+ dest.configService,
18424
+ () => this.requireIndexer().queryClient
18425
+ ),
17691
18426
  uniswap_v4: new UniswapV4IntentQuoteStrategy(dest.configService)
17692
18427
  };
17693
18428
  }
@@ -17737,37 +18472,69 @@ var IntentGateway = class _IntentGateway {
17737
18472
  /**
17738
18473
  * Quotes an intent between this gateway's source and destination chains.
17739
18474
  *
17740
- * `strategy` defaults to `uniswap_v4`, currently the only supported
17741
- * strategy. Provide exactly one of `amountIn` or `amountOut`.
18475
+ * Uses the latest directional Phantom order price snapshot from the attached
18476
+ * indexer by default. Pass `strategy: "uniswap_v4"` only when explicitly
18477
+ * requesting a Uniswap quote. Provide exactly one of `amountIn` or `amountOut`.
17742
18478
  *
17743
- * The Uniswap quote strategy always prices against the configured Base
17744
- * pool, regardless of this gateway's destination chain. Returned
18479
+ * Both built-in strategies resolve their canonical market on Base,
18480
+ * regardless of this gateway's destination chain. Returned
17745
18481
  * `amountIn`/`amountOut` already account for the gateway's protocol fee
17746
18482
  * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
17747
- * inputs; apply only your own slippage tolerance before placing the order.
18483
+ * inputs; use the returned amounts directly when placing the order.
17748
18484
  *
17749
18485
  * @param params - Token pair, amount, and optional strategy/pool overrides.
17750
18486
  * @returns The quoted amounts plus strategy-specific metadata.
17751
18487
  * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
17752
- * @throws {UnsupportedIntentQuotePairError} When no pool is configured for the pair.
18488
+ * @throws {UnsupportedIntentQuotePairError} When the selected strategy does not support the pair.
18489
+ * @throws {PhantomSnapshotUnavailableError} When an eligible cNGN pair has no snapshot.
17753
18490
  */
17754
18491
  async quoteIntent(params) {
17755
- const strategy = params.strategy ?? "uniswap_v4";
18492
+ const source = { stateMachineId: this.source.config.stateMachineId, client: this.source.client };
18493
+ const destination = { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client };
18494
+ const strategy = params.strategy ?? "phantom_snapshot";
17756
18495
  const handler = this.quoteStrategies[strategy];
17757
18496
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
17758
- return handler.quote(
17759
- { ...params, strategy },
17760
- { stateMachineId: this.source.config.stateMachineId, client: this.source.client },
17761
- { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client }
17762
- );
18497
+ return handler.quote({ ...params, strategy }, source, destination);
18498
+ }
18499
+ /**
18500
+ * Returns the output-token liquidity measured in the latest directional
18501
+ * Phantom snapshot for this gateway's source and destination.
18502
+ *
18503
+ * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18504
+ * The snapshot itself determines the output token and chain to aggregate. The
18505
+ * amount is in the token's smallest unit and reflects the indexer's
18506
+ * `snapshotTime`; it is not a live reservation or fill guarantee.
18507
+ *
18508
+ * Requires a prior call to {@link withQueryClient}.
18509
+ */
18510
+ async queryAvailableLiquidity(params) {
18511
+ const { queryClient } = this.requireIndexer();
18512
+ const sourceStateMachineId = this.source.config.stateMachineId;
18513
+ const destinationStateMachineId = this.dest.config.stateMachineId;
18514
+ const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18515
+ if (!pair) {
18516
+ throw new UnsupportedIntentQuotePairError({
18517
+ source: sourceStateMachineId,
18518
+ destination: destinationStateMachineId,
18519
+ tokenIn: params.tokenIn,
18520
+ tokenOut: params.tokenOut,
18521
+ quoteSource: "Phantom snapshot pair"
18522
+ });
18523
+ }
18524
+ return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18525
+ tokenIn: pair.tokenA,
18526
+ tokenOut: pair.tokenB
18527
+ });
17763
18528
  }
17764
18529
  /**
17765
18530
  * Bidirectional async generator that orchestrates the full order lifecycle:
17766
18531
  * placement, fee estimation, bid collection, and execution.
17767
18532
  *
17768
18533
  * **Yield/receive protocol:**
17769
- * 1. If `order.fees` is unset or zero, estimates gas and sets `order.fees`
17770
- * with a 1% buffer and the wei cost with a 2% buffer for the `value` field.
18534
+ * 1. If `order.fees` is unset or zero, estimates gas on an internal copy and sets
18535
+ * same-chain fees to twice the estimate. Cross-chain orders retain a 1% buffer
18536
+ * and include an additional 600k-gas fee uplift, while the wei cost used for
18537
+ * the `value` field receives a 2% buffer.
17771
18538
  * 2. Yields `AWAITING_PLACE_ORDER` with `{ to, data, value, sessionPrivateKey }`.
17772
18539
  * The caller must sign the transaction and pass it back via `gen.next(signedTx)`.
17773
18540
  * 3. Yields `ORDER_PLACED` with the finalised order and transaction hash once
@@ -17775,8 +18542,8 @@ var IntentGateway = class _IntentGateway {
17775
18542
  * 4. Delegates to {@link OrderExecutor.executeOrder} and forwards all
17776
18543
  * subsequent status updates until the order is filled, exhausted, or fails.
17777
18544
  *
17778
- * @param order - The order to place and execute. `order.fees` may be 0; it
17779
- * will be estimated automatically if so.
18545
+ * @param order - The order to place and execute. It is not mutated. `order.fees`
18546
+ * may be 0; fees are estimated automatically if so.
17780
18547
  * @param graffiti - Optional bytes32 tag for orderflow attribution /
17781
18548
  * revenue share. Defaults to {@link DEFAULT_GRAFFITI}.
17782
18549
  * @param options - Optional tuning parameters:
@@ -17789,20 +18556,28 @@ var IntentGateway = class _IntentGateway {
17789
18556
  * estimation returns zero.
17790
18557
  */
17791
18558
  async *execute(order, graffiti = DEFAULT_GRAFFITI, options) {
18559
+ const executionOrder = { ...order };
17792
18560
  let value;
17793
- if (!order.fees || order.fees === 0n) {
18561
+ if (!executionOrder.fees || executionOrder.fees === 0n) {
17794
18562
  const estimate = await this.gasEstimator.estimateFillOrder({
17795
- order,
18563
+ order: executionOrder,
17796
18564
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
17797
18565
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
17798
18566
  });
17799
18567
  if (estimate.totalGasCostWei === 0n || estimate.totalGasInFeeToken === 0n) {
17800
18568
  throw new Error("Gas estimation failed");
17801
18569
  }
18570
+ const isSameChain = this.source.config.stateMachineId === this.dest.config.stateMachineId;
17802
18571
  value = estimate.totalGasCostWei + estimate.totalGasCostWei * 2n / 100n;
17803
- order.fees = estimate.totalGasInFeeToken + estimate.totalGasInFeeToken * 1n / 100n;
18572
+ const crossChainFeeBump = isSameChain ? 0n : await convertGasToFeeToken(
18573
+ this.ctx,
18574
+ CROSS_CHAIN_ORDER_FEE_GAS_BUMP,
18575
+ "source",
18576
+ this.source.config.stateMachineId
18577
+ );
18578
+ executionOrder.fees = isSameChain ? estimate.totalGasInFeeToken * 2n : estimate.totalGasInFeeToken + (crossChainFeeBump + estimate.totalGasInFeeToken * 1n / 100n);
17804
18579
  }
17805
- const placeOrderGen = this.orderPlacer.placeOrder(order, graffiti);
18580
+ const placeOrderGen = this.orderPlacer.placeOrder(executionOrder, graffiti);
17806
18581
  const placeOrderFirst = await placeOrderGen.next();
17807
18582
  if (placeOrderFirst.done) {
17808
18583
  throw new Error("placeOrder generator completed without yielding");
@@ -17913,13 +18688,20 @@ var IntentGateway = class _IntentGateway {
17913
18688
  const gen = this.execute(order, graffiti, options);
17914
18689
  try {
17915
18690
  let input;
18691
+ let finalizedOrder;
17916
18692
  while (true) {
17917
18693
  const { value, done } = await gen.next(input);
17918
18694
  input = void 0;
17919
18695
  if (done) break;
17920
- if (value.status === "BIDS_RECEIVED") {
18696
+ if (value.status === "ORDER_PLACED") {
18697
+ finalizedOrder = value.order;
17921
18698
  yield value;
17922
- input = await this.autoSelect(order, value.bids);
18699
+ } else if (value.status === "BIDS_RECEIVED") {
18700
+ if (!finalizedOrder) {
18701
+ throw new Error("Received bids before the order was finalized");
18702
+ }
18703
+ yield value;
18704
+ input = await this.autoSelect(finalizedOrder, value.bids);
17923
18705
  } else if (value.status === "AWAITING_PLACE_ORDER") {
17924
18706
  input = yield value;
17925
18707
  } else {
@@ -18181,7 +18963,9 @@ var IntentGateway = class _IntentGateway {
18181
18963
  }
18182
18964
  requireIndexer() {
18183
18965
  if (!this.indexer) {
18184
- throw new Error("IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods");
18966
+ throw new Error(
18967
+ "IntentGateway: call withQueryClient(queryClient) before using indexer-backed methods or Phantom quotes"
18968
+ );
18185
18969
  }
18186
18970
  return this.indexer;
18187
18971
  }
@@ -22695,6 +23479,6 @@ async function teleportDot(param_) {
22695
23479
  return stream;
22696
23480
  }
22697
23481
 
22698
- export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PLACE_ORDER_SELECTOR, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeERC7821ExecuteBatch, decodeUserOpScale, encodeERC7821ExecuteBatch, encodeISMPMessage, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
23482
+ export { ADDRESS_ZERO2 as ADDRESS_ZERO, BundlerMethod, ChainConfigService, Chains, CryptoUtils, DEFAULT_ADDRESS, DEFAULT_GRAFFITI, DOMAIN_TYPEHASH, DUMMY_PRIVATE_KEY, ERC20Method, ERC7821_BATCH_MODE, EvmChain, ABI as EvmHostABI, EvmLanguage, HyperClientStatus, HyperFungibleToken, HyperFungibleTokenABI, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidPhantomSnapshotError, IsmpClient, MOCK_ADDRESS, ORDER_V2_PARAM_TYPE, OrderStatus, OrderStatusChecker, PACKED_USEROP_TYPEHASH, PLACE_ORDER_SELECTOR, PhantomSnapshotUnavailableError, PharosChain, PolkadotHubChain, REQUEST_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, RESPONSE_COMMITMENTS_SLOT, RESPONSE_RECEIPTS_SLOT, RequestKind, RequestStatus, SELECT_SOLVER_TYPEHASH, STATE_COMMITMENTS_SLOT, SubstrateChain, Swap, TESTNET_CHAINS, TeleportStatus, TimeoutStatus, TokenGateway, TronChain, USE_ETHERSCAN_CHAINS, UnsupportedIntentQuotePairError, UnsupportedIntentQuoteStrategyError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeERC7821ExecuteBatch, decodeUserOpScale, encodeERC7821ExecuteBatch, encodeISMPMessage, encodeStateMachineId, encodeUserOpScale, encodeWithdrawalRequest, estimateGasForPost, fetchPrice, fetchSourceProof, generateRootWithProof, getChainId, getConfigByStateMachineId, getContractCallInput, getContractCallInputs, getGasPriceFromEtherscan, getOrFetchStorageSlot, getOrderPlacedFromTx, getPostRequestEventFromTx, getPostResponseEventFromTx, getRequestCommitment, getStateCommitmentFieldSlot, getStateCommitmentSlot, getStorageSlot, getViemChain, hexToString, hyperbridgeAddress, maxBigInt, normalizeAddressForEvmBytes32, normalizeAddressForStateMachine, normalizeEvmAddress, normalizeEvmChainId, normalizeStateMachineId, orderCommitment, parseStateMachineId, pharosAtlantic, pharosMainnet, polkadotAssetHubPaseo, polkadotHubMainnet, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
22699
23483
  //# sourceMappingURL=index.js.map
22700
23484
  //# sourceMappingURL=index.js.map