@hyperbridge/sdk 2.8.3 → 2.8.4

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.
@@ -5250,11 +5250,21 @@ var ChainConfigService = class {
5250
5250
  * it, so a new asset is added once in `chain.ts` and nowhere else.
5251
5251
  */
5252
5252
  getAssetBySymbol(chain, symbol) {
5253
- const assets = this.getConfig(chain)?.assets;
5253
+ return this.getAssetMetadataBySymbol(chain, symbol)?.address;
5254
+ }
5255
+ /** Resolves a configured token symbol case-insensitively on a specific chain. */
5256
+ getAssetMetadataBySymbol(chain, symbol) {
5257
+ const config = this.getConfig(chain);
5258
+ const assets = config?.assets;
5254
5259
  if (!assets) return void 0;
5255
5260
  const target = symbol.trim().toUpperCase();
5256
5261
  for (const [key, address] of Object.entries(assets)) {
5257
- if (key.toUpperCase() === target) return address;
5262
+ if (key.toUpperCase() !== target) continue;
5263
+ return {
5264
+ symbol: key,
5265
+ address,
5266
+ decimals: config.tokenDecimals?.[key]
5267
+ };
5258
5268
  }
5259
5269
  return void 0;
5260
5270
  }
@@ -7825,6 +7835,7 @@ var HYPERBRIDGE_TYPES_BUNDLE = {
7825
7835
  };
7826
7836
  var BASE_TIP = 1000000000n;
7827
7837
  var HTTP_CONNECT_TIMEOUT_MS = 2e4;
7838
+ var INCLUSION_TIMEOUT_MS = 2e4;
7828
7839
  var PHANTOM_POLL_INTERVAL_MS = 15e3;
7829
7840
  var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
7830
7841
  function rejectAfter(ms, message) {
@@ -8043,14 +8054,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8043
8054
  /**
8044
8055
  * Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
8045
8056
  * concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
8046
- * (or is confirmed still pooled and returned as `pending`) before the next is signed; auto-nonce
8047
- * via `system.accountNextIndex` counts pooled extrinsics, so a pending one is never re-used.
8057
+ * (or is confirmed still pooled and returned as `pending`) before the next is signed. The
8058
+ * auto-nonce is the account's on-chain nonce, so a still-pooled extrinsic does not advance it: a
8059
+ * submission signed behind a pending one bounces off it (1013/1014) and is reported as pending
8060
+ * too, rather than landing as a second copy.
8048
8061
  *
8049
8062
  * The extrinsic is built rather than passed in because the api it is built on decides where it
8050
8063
  * is signed and sent: a websocket that is down when the queue reaches this submission diverts it
8051
8064
  * to {@link sendViaHttp}, which needs the call bound to the HTTP api instead.
8052
8065
  */
8053
- async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = 3e4) {
8066
+ async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = INCLUSION_TIMEOUT_MS) {
8054
8067
  const result = await this.submissionQueue.add(async () => {
8055
8068
  if (!this.api.isConnected) {
8056
8069
  try {
@@ -8088,36 +8101,72 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8088
8101
  * Signs and sends an extrinsic, handling status updates and errors.
8089
8102
  * Implements retry logic with progressive tip increases for stuck transactions.
8090
8103
  *
8091
- * A retry only happens when the previous attempt verifiably went nowhere. Once an attempt's
8092
- * extrinsic is known to be pooled (`pending`), re-signing the same call would race our own
8093
- * submission: the copy either bounces off the pool (1014, same nonce below the replacement
8094
- * priority bump) or if the original lands first, freeing the nonce — executes as a duplicate
8095
- * and fails on-chain (e.g. `BidNotFound` for a retraction). Neither can succeed, so the pending
8096
- * result is returned for the caller to confirm later.
8104
+ * Two kinds of failure are retried, and the difference is the nonce.
8105
+ *
8106
+ * An attempt that verifiably went nowhere (rejected before the pool, dropped, invalid) leaves
8107
+ * the account nonce free, so the next attempt simply re-signs with the auto-nonce.
8108
+ *
8109
+ * An attempt that reached the pool and was still there when the watch timed out (`stalled`) is
8110
+ * retried as a *replacement*: the same nonce it was signed with, and double the tip. Substrate's
8111
+ * pool evicts a pooled extrinsic in favour of a higher-priority one at the same (account, nonce),
8112
+ * so exactly one of the two can ever execute. This matters for a bid, which is worth nothing once
8113
+ * its window closes — waiting out a stalled extrinsic usually means not bidding at all.
8114
+ *
8115
+ * Re-signing without pinning the nonce is what must never happen here. The auto-nonce is read
8116
+ * from on-chain state, so it is only the stalled extrinsic's nonce for as long as that extrinsic
8117
+ * stays out of a block — and a stall is precisely the case where it may land at any moment. Once
8118
+ * it does, an unpinned retry takes the *next* nonce and both execute: a duplicate `placeBid` that
8119
+ * fails on-chain, and a second `retractBid` that pulls the bid just placed. When the signed nonce
8120
+ * cannot be read, the stalled result is returned rather than guessed at.
8121
+ *
8122
+ * A rejection that bounced off a copy already pooled (1013/1014) is likewise left alone: that
8123
+ * copy is in flight and its outcome is unknown here, so the `pending` result goes back to the
8124
+ * caller to confirm later.
8097
8125
  */
8098
8126
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
8099
8127
  const keyPair = this.getKeyPair();
8100
8128
  let attempt = 0;
8129
+ let nonce;
8130
+ let stalled;
8101
8131
  while (attempt < maxRetries) {
8102
8132
  const currentTip = BASE_TIP * BigInt(2 ** attempt);
8103
8133
  attempt++;
8104
8134
  try {
8105
- const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
8106
- if (result.success || result.pending || result.error?.includes("Dispatch error")) {
8135
+ const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs, nonce);
8136
+ if (result.success || result.error?.includes("Dispatch error")) {
8107
8137
  return result;
8108
8138
  }
8139
+ if (result.stalled) {
8140
+ stalled = result;
8141
+ nonce ??= this.signedNonce(extrinsic);
8142
+ if (nonce === void 0) return result;
8143
+ continue;
8144
+ }
8145
+ if (result.pending) return stalled ?? result;
8109
8146
  } catch (err) {
8110
- return {
8147
+ return stalled ?? {
8111
8148
  success: false,
8112
8149
  error: err instanceof Error ? err.message : "Unknown error"
8113
8150
  };
8114
8151
  }
8115
8152
  }
8116
- return {
8153
+ return stalled ?? {
8117
8154
  success: false,
8118
8155
  error: `Transaction failed after ${maxRetries} attempts`
8119
8156
  };
8120
8157
  }
8158
+ /**
8159
+ * The nonce an extrinsic was signed with, or undefined if it carries no readable one — which is
8160
+ * the case before it has ever been signed, and for a stub api in tests.
8161
+ */
8162
+ signedNonce(extrinsic) {
8163
+ try {
8164
+ const nonce = extrinsic.nonce?.toNumber?.();
8165
+ return typeof nonce === "number" && Number.isFinite(nonce) ? nonce : void 0;
8166
+ } catch {
8167
+ return void 0;
8168
+ }
8169
+ }
8121
8170
  /**
8122
8171
  * Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
8123
8172
  * too low") both mean a copy of this account+nonce is already in the pool — almost always our
@@ -8135,10 +8184,15 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8135
8184
  *
8136
8185
  * A timeout is only a failure when the extrinsic never made it into the transaction pool.
8137
8186
  * Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
8138
- * in flight and may well execute after the watch is abandoned — the result is then `pending`,
8139
- * telling the caller to confirm the outcome later instead of re-signing the same call.
8187
+ * in flight and may well execute after the watch is abandoned — the result is then `pending`
8188
+ * and `stalled`, telling the caller to replace it under the same nonce or confirm it later,
8189
+ * never to re-sign the same call under a fresh one.
8190
+ *
8191
+ * `nonce` pins the submission to a specific account nonce, which is what makes a retry a pool
8192
+ * replacement rather than a second extrinsic queued behind the first. Left undefined on the
8193
+ * first attempt, where the api's auto-nonce is correct.
8140
8194
  */
8141
- async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
8195
+ async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs, nonce) {
8142
8196
  return new Promise((resolve) => {
8143
8197
  let resolved = false;
8144
8198
  let unsubscribe = null;
@@ -8152,12 +8206,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8152
8206
  resolve({
8153
8207
  success: false,
8154
8208
  pending: enteredPool || void 0,
8209
+ stalled: enteredPool || void 0,
8155
8210
  extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
8156
8211
  error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
8157
8212
  });
8158
8213
  }
8159
8214
  }, timeoutMs);
8160
- extrinsic.signAndSend(keyPair, { tip }, (result) => {
8215
+ extrinsic.signAndSend(keyPair, nonce === void 0 ? { tip } : { tip, nonce }, (result) => {
8161
8216
  if (resolved) return;
8162
8217
  if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
8163
8218
  enteredPool = true;
@@ -11994,52 +12049,85 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11994
12049
  }
11995
12050
  }
11996
12051
  }`;
11997
- var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11998
- query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11999
- phantomOrderPriceSnapshots(
12052
+ var AVAILABLE_LIQUIDITY = `
12053
+ query AvailableLiquidity(
12054
+ $poolId: String!
12055
+ $sourceChain: String!
12056
+ $destinationChain: String!
12057
+ $direction: String!
12058
+ ) {
12059
+ poolChainLiquidities(
12000
12060
  filter: {
12001
12061
  and: [
12002
- { tokenA: { equalTo: $tokenA } }
12003
- { tokenB: { equalTo: $tokenB } }
12062
+ { poolId: { equalToInsensitive: $poolId } }
12063
+ { chain: { equalTo: $destinationChain } }
12064
+ { direction: { equalTo: $direction } }
12004
12065
  ]
12005
12066
  }
12006
- orderBy: SNAPSHOT_TIME_DESC
12007
12067
  first: 1
12008
12068
  ) {
12009
12069
  nodes {
12010
- commitment
12011
- tokenA
12012
- tokenB
12013
- snapshotTime
12070
+ depth
12071
+ bidCount
12072
+ unrestrictedDepth
12073
+ unrestrictedBidCount
12074
+ lastUpdatedAt
12075
+ }
12076
+ }
12077
+ poolRoutes(
12078
+ filter: {
12079
+ and: [
12080
+ { poolId: { equalToInsensitive: $poolId } }
12081
+ { sourceChain: { equalTo: $sourceChain } }
12082
+ { chain: { equalTo: $destinationChain } }
12083
+ { direction: { equalTo: $direction } }
12084
+ ]
12085
+ }
12086
+ first: 1
12087
+ ) {
12088
+ nodes {
12089
+ depth
12090
+ bidCount
12091
+ lastUpdatedAt
12014
12092
  }
12015
12093
  }
12016
12094
  }`;
12017
- var LIQUIDITY_PROVIDER_BALANCES = `
12018
- query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
12019
- liquidityProviderBalances(
12095
+ var BUY_AND_SELL_RATES = `
12096
+ query BuyAndSellRates(
12097
+ $poolId: String!
12098
+ $directChain: String!
12099
+ $directDirection: String!
12100
+ $reverseChain: String!
12101
+ $reverseDirection: String!
12102
+ ) {
12103
+ direct: poolChainLiquidities(
12020
12104
  filter: {
12021
12105
  and: [
12022
- { commitment: { equalTo: $commitment } }
12023
- { tokenAddress: { equalTo: $tokenAddress } }
12106
+ { poolId: { equalToInsensitive: $poolId } }
12107
+ { chain: { equalTo: $directChain } }
12108
+ { direction: { equalTo: $directDirection } }
12024
12109
  ]
12025
12110
  }
12111
+ first: 1
12026
12112
  ) {
12027
- aggregates {
12028
- sum {
12029
- balance
12030
- }
12031
- distinctCount {
12032
- providerId
12033
- }
12113
+ nodes {
12114
+ rate
12115
+ lastUpdatedAt
12034
12116
  }
12035
- groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
12036
- keys
12037
- sum {
12038
- balance
12039
- }
12040
- distinctCount {
12041
- providerId
12042
- }
12117
+ }
12118
+ reverse: poolChainLiquidities(
12119
+ filter: {
12120
+ and: [
12121
+ { poolId: { equalToInsensitive: $poolId } }
12122
+ { chain: { equalTo: $reverseChain } }
12123
+ { direction: { equalTo: $reverseDirection } }
12124
+ ]
12125
+ }
12126
+ first: 1
12127
+ ) {
12128
+ nodes {
12129
+ rate
12130
+ lastUpdatedAt
12043
12131
  }
12044
12132
  }
12045
12133
  }`;
@@ -18183,178 +18271,191 @@ var OrderStatusChecker = class {
18183
18271
  return true;
18184
18272
  }
18185
18273
  };
18186
- var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
18274
+
18275
+ // src/protocols/intents/liquidity-pool.ts
18276
+ function sortPoolSymbols(symbolA, symbolB) {
18277
+ return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
18278
+ }
18279
+ function poolSlug(symbolA, symbolB) {
18280
+ return sortPoolSymbols(symbolA, symbolB).join("-");
18281
+ }
18282
+ function resolveLiquidityPool(symbolA, symbolB) {
18283
+ const [token0Symbol, token1Symbol] = sortPoolSymbols(symbolA, symbolB);
18284
+ return {
18285
+ poolId: `${token0Symbol}-${token1Symbol}`,
18286
+ token0Symbol,
18287
+ token1Symbol
18288
+ };
18289
+ }
18290
+
18291
+ // src/protocols/intents/LiquidityEngine.ts
18292
+ var INDEXER_FIXED_POINT_DECIMALS = 18;
18293
+ var POOL_RATE_SCALE = 10n ** 18n;
18294
+ var SELL = "SELL";
18295
+ var BUY = "BUY";
18296
+ var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT"]);
18187
18297
  var LiquidityEngine = class {
18188
- /**
18189
- * @param queryClient - Nexus GraphQL client attached to the gateway.
18190
- * @param chainConfigService - Resolves token decimals for formatted results.
18191
- */
18192
- constructor(queryClient, chainConfigService) {
18298
+ constructor(queryClient) {
18193
18299
  this.queryClient = queryClient;
18194
- this.chainConfigService = chainConfigService;
18195
18300
  }
18196
18301
  queryClient;
18197
- chainConfigService;
18198
18302
  /**
18199
- * Retrieves the newest directional Phantom snapshot for a pair and its
18200
- * indexed output-token liquidity.
18303
+ * Returns liquidity reachable from one source chain on one destination.
18201
18304
  *
18202
- * Nexus filters balances to the canonical output token, then aggregates them
18203
- * overall and by chain. The returned amounts are decimal strings: the total
18204
- * uses the canonical Base output-token decimals, while each chain group uses
18205
- * that chain's token decimals. They describe `snapshotTime`, not live
18206
- * reservations or fill guarantees.
18305
+ * The caller resolves chain-specific token addresses through chain
18306
+ * configuration; this layer only maps those configured symbols onto the
18307
+ * indexer's canonical pool and route fields.
18207
18308
  *
18208
- * @param params - Canonical Phantom-market input and output token addresses.
18209
- * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
18210
- * the directional pair.
18211
- * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
18212
- * or internally inconsistent snapshot data.
18213
- */
18214
- async getAvailableLiquiditySnapshot(params) {
18215
- const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
18216
- const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
18217
- const response = await this.queryClient.request(
18218
- LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
18219
- { tokenA: tokenIn, tokenB: tokenOut }
18220
- );
18221
- const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
18222
- if (!node) return;
18223
- const commitment = node.commitment.toLowerCase();
18224
- if (!COMMITMENT_PATTERN.test(commitment)) {
18225
- throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
18226
- }
18227
- if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
18228
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
18229
- }
18230
- const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
18231
- if (Number.isNaN(snapshotTime.getTime())) {
18232
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
18309
+ * Destination, unrestricted, and explicit-route capacity are returned as
18310
+ * separate values so callers can apply their own source-chain policy.
18311
+ *
18312
+ * @returns `undefined` only when the indexer has not published a destination
18313
+ * pool sample yet.
18314
+ */
18315
+ async getAvailableLiquidity(params) {
18316
+ const pool = resolveLiquidityPool(params.source.symbol, params.destination.symbol);
18317
+ const direction = params.source.symbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18318
+ const variables = {
18319
+ poolId: pool.poolId,
18320
+ sourceChain: params.source.chain,
18321
+ destinationChain: params.destination.chain,
18322
+ direction
18323
+ };
18324
+ const response = await this.queryClient.request(AVAILABLE_LIQUIDITY, variables);
18325
+ if (!response?.poolChainLiquidities?.nodes || !response?.poolRoutes?.nodes) {
18326
+ throw new InvalidLiquidityIndexerResponseError("liquidity connections are missing");
18233
18327
  }
18234
- const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
18235
- commitment,
18236
- tokenAddress: tokenOut
18237
- });
18328
+ const chainLiquidity = response.poolChainLiquidities.nodes[0];
18329
+ if (!chainLiquidity) return void 0;
18330
+ const route = response.poolRoutes.nodes[0];
18238
18331
  return {
18239
- totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
18240
- providerCount,
18241
- tokenAddress: tokenOut,
18242
- snapshotTime,
18243
- liquidityByChain: liquidityByChain.map((group) => ({
18244
- ...group,
18245
- totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
18246
- }))
18332
+ sourceChain: params.source.chain,
18333
+ destinationChain: params.destination.chain,
18334
+ tokenAddress: normalizeEvmAddress(params.destination.address, "destination token"),
18335
+ updatedAt: readIndexerDate(chainLiquidity.lastUpdatedAt, "destination lastUpdatedAt"),
18336
+ destination: readLiquiditySlice(chainLiquidity.depth, chainLiquidity.bidCount, "destination"),
18337
+ unrestricted: readLiquiditySlice(
18338
+ chainLiquidity.unrestrictedDepth,
18339
+ chainLiquidity.unrestrictedBidCount,
18340
+ "unrestricted"
18341
+ ),
18342
+ explicitRoute: route ? {
18343
+ ...readLiquiditySlice(route.depth, route.bidCount, "explicit route"),
18344
+ updatedAt: readIndexerDate(route.lastUpdatedAt, "route lastUpdatedAt")
18345
+ } : null
18247
18346
  };
18248
18347
  }
18249
18348
  /**
18250
- * Requests server-side sums and distinct provider counts for one immutable
18251
- * snapshot/output-token pair, including chain-level aggregate groups.
18349
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
18350
+ * per one base token.
18252
18351
  *
18253
- * `commitment` uniquely identifies the selected snapshot
18254
- */
18255
- async querySnapshotLiquidityAggregates(params) {
18256
- const response = await this.queryClient.request(
18257
- LIQUIDITY_PROVIDER_BALANCES,
18258
- { commitment: params.commitment, tokenAddress: params.tokenAddress }
18259
- );
18260
- const connection = response?.liquidityProviderBalances;
18261
- const aggregates = connection?.aggregates;
18262
- if (!connection || !aggregates) {
18263
- throw new InvalidAvailableLiquiditySnapshotError(
18264
- params.commitment,
18265
- "liquidityProviderBalances aggregates are missing"
18266
- );
18267
- }
18268
- const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
18269
- const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
18270
- const liquidityByChain = connection.groupedAggregates.map((group, index) => {
18271
- const [chain, tokenAddress] = group.keys;
18272
- if (!chain?.trim() || !tokenAddress) {
18273
- throw new InvalidAvailableLiquiditySnapshotError(
18274
- params.commitment,
18275
- `liquidity group ${index} has invalid keys`
18276
- );
18277
- }
18278
- const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
18279
- tokenAddress,
18280
- params.commitment,
18281
- `liquidity group ${index} tokenAddress`
18282
- );
18283
- if (normalizedTokenAddress !== params.tokenAddress) {
18284
- throw new InvalidAvailableLiquiditySnapshotError(
18285
- params.commitment,
18286
- `liquidity group ${index} tokenAddress does not match the snapshot output token`
18287
- );
18288
- }
18289
- return {
18290
- chain: chain.trim(),
18291
- tokenAddress: normalizedTokenAddress,
18292
- totalLiquidity: parseSnapshotBigInt(
18293
- group.sum.balance ?? "0",
18294
- params.commitment,
18295
- `liquidity group ${index} balance`
18296
- ),
18297
- providerCount: parseProviderCount(
18298
- group.distinctCount.providerId,
18299
- params.commitment,
18300
- `liquidity group ${index}`
18301
- )
18302
- };
18352
+ * The requested direction is read on the destination chain; its reverse is
18353
+ * read on the source chain. This mirrors where each direction's output token
18354
+ * must be delivered for a cross-chain trade.
18355
+ */
18356
+ async getBuyAndSellRates(params) {
18357
+ const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
18358
+ const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18359
+ const reverseDirection = directDirection === SELL ? BUY : SELL;
18360
+ const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
18361
+ poolId: pool.poolId,
18362
+ directChain: params.destinationChain,
18363
+ directDirection,
18364
+ reverseChain: params.sourceChain,
18365
+ reverseDirection
18303
18366
  });
18304
- const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
18305
- if (groupedTotal !== totalLiquidity) {
18306
- throw new InvalidAvailableLiquiditySnapshotError(
18307
- params.commitment,
18308
- "grouped liquidity does not match the total liquidity"
18309
- );
18310
- }
18311
- return { totalLiquidity, providerCount, liquidityByChain };
18367
+ if (!response?.direct?.nodes || !response?.reverse?.nodes) {
18368
+ throw new InvalidLiquidityIndexerResponseError("rate connections are missing");
18369
+ }
18370
+ const direct = readIndexedRate(response.direct.nodes[0], "direct rate");
18371
+ const reverse = readIndexedRate(response.reverse.nodes[0], "reverse rate");
18372
+ if (!direct && !reverse) return void 0;
18373
+ const quoteTokenSymbol = resolveQuoteTokenSymbol(
18374
+ params.tokenInSymbol,
18375
+ params.tokenOutSymbol,
18376
+ direct?.scaledRate,
18377
+ reverse?.scaledRate
18378
+ );
18379
+ const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
18380
+ const buy = quoteIsTokenOut ? direct : reverse;
18381
+ const sell = quoteIsTokenOut ? reverse : direct;
18382
+ return {
18383
+ baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
18384
+ quoteTokenSymbol,
18385
+ sourceChain: params.sourceChain,
18386
+ destinationChain: params.destinationChain,
18387
+ buyRate: buy ? viem.formatUnits(buy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
18388
+ sellRate: sell ? viem.formatUnits(reciprocalRate(sell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
18389
+ buyRateUpdatedAt: buy?.updatedAt ?? null,
18390
+ sellRateUpdatedAt: sell?.updatedAt ?? null
18391
+ };
18312
18392
  }
18313
- /** Formats a raw amount using the configured decimals for its chain/token. */
18314
- formatLiquidity(amount, chain, tokenAddress, commitment) {
18315
- const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
18316
- if (decimals === void 0) {
18317
- throw new InvalidAvailableLiquiditySnapshotError(
18318
- commitment,
18319
- `token decimals are not configured for ${tokenAddress} on ${chain}`
18320
- );
18321
- }
18322
- return viem.formatUnits(amount, decimals);
18393
+ };
18394
+ var InvalidLiquidityIndexerResponseError = class extends Error {
18395
+ constructor(reason) {
18396
+ super(`Invalid liquidity indexer response: ${reason}`);
18397
+ this.name = "InvalidLiquidityIndexerResponseError";
18323
18398
  }
18324
18399
  };
18325
- var InvalidAvailableLiquiditySnapshotError = class extends Error {
18326
- /** Creates an error that identifies the invalid snapshot and field/reason. */
18327
- constructor(commitment, reason) {
18328
- super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
18329
- this.name = "InvalidAvailableLiquiditySnapshotError";
18400
+ var UnsupportedLiquidityAssetError = class extends Error {
18401
+ constructor(chain, asset) {
18402
+ super(`No configured liquidity asset found for ${asset} on ${chain}`);
18403
+ this.name = "UnsupportedLiquidityAssetError";
18404
+ }
18405
+ };
18406
+ var UnsupportedLiquidityChainError = class extends Error {
18407
+ constructor(chainId) {
18408
+ super(`No configured liquidity chain found for chain ID ${chainId}`);
18409
+ this.name = "UnsupportedLiquidityChainError";
18330
18410
  }
18331
18411
  };
18332
- function parseSnapshotBigInt(value, commitment, field) {
18412
+ function readLiquiditySlice(depth, providerCount, label) {
18413
+ if (!Number.isSafeInteger(providerCount) || providerCount < 0) {
18414
+ throw new InvalidLiquidityIndexerResponseError(`${label} provider count is invalid`);
18415
+ }
18416
+ return { totalLiquidity: formatIndexerAmount(depth, `${label} depth`), providerCount };
18417
+ }
18418
+ function formatIndexerAmount(value, label) {
18333
18419
  try {
18334
18420
  const amount = BigInt(value);
18335
- if (amount < 0n) {
18336
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
18337
- }
18338
- return amount;
18339
- } catch (error) {
18340
- if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
18341
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
18421
+ if (amount < 0n) throw new Error();
18422
+ return viem.formatUnits(amount, INDEXER_FIXED_POINT_DECIMALS);
18423
+ } catch {
18424
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a non-negative integer`);
18342
18425
  }
18343
18426
  }
18344
- function parseProviderCount(value, commitment, field) {
18345
- const count = Number(value);
18346
- if (!Number.isSafeInteger(count) || count < 0) {
18347
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
18348
- }
18349
- return count;
18427
+ function readIndexerDate(value, label) {
18428
+ const date = new Date(dateStringtoTimestamp(value));
18429
+ if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
18430
+ return date;
18350
18431
  }
18351
- function normalizeIndexedLiquidityAddress(address, commitment, field) {
18432
+ function readIndexedRate(node, label) {
18433
+ if (!node) return void 0;
18352
18434
  try {
18353
- return normalizeEvmAddress(address, field);
18354
- } catch {
18355
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
18435
+ const scaledRate = BigInt(node.rate);
18436
+ if (scaledRate <= 0n) throw new Error();
18437
+ return {
18438
+ scaledRate,
18439
+ updatedAt: readIndexerDate(node.lastUpdatedAt, `${label} lastUpdatedAt`)
18440
+ };
18441
+ } catch (error) {
18442
+ if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
18443
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
18356
18444
  }
18357
18445
  }
18446
+ function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
18447
+ const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
18448
+ const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
18449
+ if (inputIsUsdStable !== outputIsUsdStable) return inputIsUsdStable ? tokenOutSymbol : tokenInSymbol;
18450
+ if (directRate !== void 0) return directRate >= POOL_RATE_SCALE ? tokenOutSymbol : tokenInSymbol;
18451
+ if (reverseRate !== void 0) return reverseRate >= POOL_RATE_SCALE ? tokenInSymbol : tokenOutSymbol;
18452
+ throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
18453
+ }
18454
+ function reciprocalRate(rate, label) {
18455
+ const reciprocal = POOL_RATE_SCALE * POOL_RATE_SCALE / rate;
18456
+ if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
18457
+ return reciprocal;
18458
+ }
18358
18459
 
18359
18460
  // src/protocols/intents/quote/types.ts
18360
18461
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -18779,8 +18880,6 @@ var IntentGateway = class _IntentGateway {
18779
18880
  gasEstimator;
18780
18881
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
18781
18882
  quoteStrategies;
18782
- /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18783
- phantomSnapshotPairResolver;
18784
18883
  /**
18785
18884
  * Private constructor — use {@link IntentGateway.create} instead.
18786
18885
  *
@@ -18824,7 +18923,6 @@ var IntentGateway = class _IntentGateway {
18824
18923
  this.bidManager = bidManager;
18825
18924
  this.gasEstimator = gasEstimator;
18826
18925
  this._crypto = crypto;
18827
- this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
18828
18926
  this.quoteStrategies = {
18829
18927
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18830
18928
  dest.configService,
@@ -18904,33 +19002,62 @@ var IntentGateway = class _IntentGateway {
18904
19002
  return handler.quote({ ...params, strategy }, source, destination);
18905
19003
  }
18906
19004
  /**
18907
- * Returns the output-token liquidity measured in the latest directional
18908
- * Phantom snapshot for this gateway's source and destination.
19005
+ * Returns indexed destination liquidity and its source-routing slices.
18909
19006
  *
18910
- * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18911
- * The snapshot itself determines the output token and chain to aggregate. The
18912
- * amount is in the token's smallest unit and reflects the indexer's
18913
- * `snapshotTime`; it is not a live reservation or fill guarantee.
19007
+ * Destination, unrestricted, and explicit-route capacity come exclusively
19008
+ * from the indexer's pair-centric liquidity entities. The SDK does not decide
19009
+ * whether unrestricted bidders cover the source chain. Amounts reflect the
19010
+ * latest rolling sample; they are not reservations or fill guarantees.
18914
19011
  *
18915
19012
  * Requires a prior call to {@link withQueryClient}.
18916
19013
  */
18917
19014
  async queryAvailableLiquidity(params) {
18918
19015
  const { queryClient } = this.requireIndexer();
18919
- const sourceStateMachineId = this.source.config.stateMachineId;
18920
- const destinationStateMachineId = this.dest.config.stateMachineId;
18921
- const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18922
- if (!pair) {
18923
- throw new UnsupportedIntentQuotePairError({
18924
- source: sourceStateMachineId,
18925
- destination: destinationStateMachineId,
18926
- tokenIn: params.tokenIn,
18927
- tokenOut: params.tokenOut,
18928
- quoteSource: "Phantom snapshot pair"
18929
- });
18930
- }
18931
- return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18932
- tokenIn: pair.tokenA,
18933
- tokenOut: pair.tokenB
19016
+ const sourceStateMachineId = getConfigByStateMachineId(this.source.config.stateMachineId)?.stateMachineId;
19017
+ const destinationStateMachineId = getConfigByStateMachineId(this.dest.config.stateMachineId)?.stateMachineId;
19018
+ if (!sourceStateMachineId) throw new UnsupportedLiquidityChainError(this.source.config.stateMachineId);
19019
+ if (!destinationStateMachineId) throw new UnsupportedLiquidityChainError(this.dest.config.stateMachineId);
19020
+ const sourceToken = this.source.configService.getAssetMetadataByAddress(sourceStateMachineId, params.tokenIn);
19021
+ const destinationToken = this.dest.configService.getAssetMetadataByAddress(
19022
+ destinationStateMachineId,
19023
+ params.tokenOut
19024
+ );
19025
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceStateMachineId, params.tokenIn);
19026
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationStateMachineId, params.tokenOut);
19027
+ return new LiquidityEngine(queryClient).getAvailableLiquidity({
19028
+ source: {
19029
+ chain: sourceStateMachineId,
19030
+ ...sourceToken
19031
+ },
19032
+ destination: {
19033
+ chain: destinationStateMachineId,
19034
+ ...destinationToken
19035
+ }
19036
+ });
19037
+ }
19038
+ /**
19039
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
19040
+ * without requiring token addresses. Symbols are matched case-insensitively;
19041
+ * chain IDs are numeric IDs for chains configured in the SDK.
19042
+ */
19043
+ async queryBuyAndSellRates(params) {
19044
+ const { queryClient } = this.requireIndexer();
19045
+ const sourceChain = chainConfigs[params.sourceChainId]?.stateMachineId;
19046
+ const destinationChain = chainConfigs[params.destinationChainId]?.stateMachineId;
19047
+ if (!sourceChain) throw new UnsupportedLiquidityChainError(params.sourceChainId);
19048
+ if (!destinationChain) throw new UnsupportedLiquidityChainError(params.destinationChainId);
19049
+ const sourceToken = this.source.configService.getAssetMetadataBySymbol(sourceChain, params.tokenInSymbol);
19050
+ const destinationToken = this.dest.configService.getAssetMetadataBySymbol(
19051
+ destinationChain,
19052
+ params.tokenOutSymbol
19053
+ );
19054
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceChain, params.tokenInSymbol);
19055
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationChain, params.tokenOutSymbol);
19056
+ return new LiquidityEngine(queryClient).getBuyAndSellRates({
19057
+ sourceChain,
19058
+ destinationChain,
19059
+ tokenInSymbol: sourceToken.symbol,
19060
+ tokenOutSymbol: destinationToken.symbol
18934
19061
  });
18935
19062
  }
18936
19063
  /**
@@ -24049,10 +24176,12 @@ exports.EvmLanguage = EvmLanguage;
24049
24176
  exports.HyperClientStatus = HyperClientStatus;
24050
24177
  exports.HyperFungibleToken = HyperFungibleToken;
24051
24178
  exports.HyperFungibleTokenABI = HyperFungibleTokenABI;
24179
+ exports.INCLUSION_TIMEOUT_MS = INCLUSION_TIMEOUT_MS;
24052
24180
  exports.IntentGateway = IntentGateway;
24053
24181
  exports.IntentGatewayABI = ABI3;
24054
24182
  exports.IntentOrderStatus = IntentOrderStatus;
24055
24183
  exports.IntentsCoprocessor = IntentsCoprocessor;
24184
+ exports.InvalidLiquidityIndexerResponseError = InvalidLiquidityIndexerResponseError;
24056
24185
  exports.InvalidPhantomSnapshotError = InvalidPhantomSnapshotError;
24057
24186
  exports.IsmpClient = IsmpClient;
24058
24187
  exports.MOCK_ADDRESS = MOCK_ADDRESS;
@@ -24082,6 +24211,8 @@ exports.TronChain = TronChain;
24082
24211
  exports.USE_ETHERSCAN_CHAINS = USE_ETHERSCAN_CHAINS;
24083
24212
  exports.UnsupportedIntentQuotePairError = UnsupportedIntentQuotePairError;
24084
24213
  exports.UnsupportedIntentQuoteStrategyError = UnsupportedIntentQuoteStrategyError;
24214
+ exports.UnsupportedLiquidityAssetError = UnsupportedLiquidityAssetError;
24215
+ exports.UnsupportedLiquidityChainError = UnsupportedLiquidityChainError;
24085
24216
  exports.WrappedHyperFungibleTokenABI = WrappedHyperFungibleTokenABI;
24086
24217
  exports.__test = __test;
24087
24218
  exports.adjustDecimals = adjustDecimals;
@@ -24145,6 +24276,7 @@ exports.pharosAtlantic = pharosAtlantic;
24145
24276
  exports.pharosMainnet = pharosMainnet;
24146
24277
  exports.polkadotAssetHubPaseo = polkadotAssetHubPaseo;
24147
24278
  exports.polkadotHubMainnet = polkadotHubMainnet;
24279
+ exports.poolSlug = poolSlug;
24148
24280
  exports.postRequestCommitment = postRequestCommitment;
24149
24281
  exports.queryAssetTeleported = queryAssetTeleported;
24150
24282
  exports.queryGetRequest = queryGetRequest;
@@ -24153,6 +24285,7 @@ exports.quoteUniswap = quoteUniswap;
24153
24285
  exports.requestCommitmentKey = requestCommitmentKey;
24154
24286
  exports.responseCommitmentKey = responseCommitmentKey;
24155
24287
  exports.retryPromise = retryPromise;
24288
+ exports.sortPoolSymbols = sortPoolSymbols;
24156
24289
  exports.teleport = teleport;
24157
24290
  exports.teleportDot = teleportDot;
24158
24291
  exports.transformOrderForContract = transformOrderForContract;