@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.
@@ -5239,11 +5239,21 @@ var ChainConfigService = class {
5239
5239
  * it, so a new asset is added once in `chain.ts` and nowhere else.
5240
5240
  */
5241
5241
  getAssetBySymbol(chain, symbol) {
5242
- const assets = this.getConfig(chain)?.assets;
5242
+ return this.getAssetMetadataBySymbol(chain, symbol)?.address;
5243
+ }
5244
+ /** Resolves a configured token symbol case-insensitively on a specific chain. */
5245
+ getAssetMetadataBySymbol(chain, symbol) {
5246
+ const config = this.getConfig(chain);
5247
+ const assets = config?.assets;
5243
5248
  if (!assets) return void 0;
5244
5249
  const target = symbol.trim().toUpperCase();
5245
5250
  for (const [key, address] of Object.entries(assets)) {
5246
- if (key.toUpperCase() === target) return address;
5251
+ if (key.toUpperCase() !== target) continue;
5252
+ return {
5253
+ symbol: key,
5254
+ address,
5255
+ decimals: config.tokenDecimals?.[key]
5256
+ };
5247
5257
  }
5248
5258
  return void 0;
5249
5259
  }
@@ -7814,6 +7824,7 @@ var HYPERBRIDGE_TYPES_BUNDLE = {
7814
7824
  };
7815
7825
  var BASE_TIP = 1000000000n;
7816
7826
  var HTTP_CONNECT_TIMEOUT_MS = 2e4;
7827
+ var INCLUSION_TIMEOUT_MS = 2e4;
7817
7828
  var PHANTOM_POLL_INTERVAL_MS = 15e3;
7818
7829
  var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
7819
7830
  function rejectAfter(ms, message) {
@@ -8032,14 +8043,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8032
8043
  /**
8033
8044
  * Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
8034
8045
  * concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
8035
- * (or is confirmed still pooled and returned as `pending`) before the next is signed; auto-nonce
8036
- * via `system.accountNextIndex` counts pooled extrinsics, so a pending one is never re-used.
8046
+ * (or is confirmed still pooled and returned as `pending`) before the next is signed. The
8047
+ * auto-nonce is the account's on-chain nonce, so a still-pooled extrinsic does not advance it: a
8048
+ * submission signed behind a pending one bounces off it (1013/1014) and is reported as pending
8049
+ * too, rather than landing as a second copy.
8037
8050
  *
8038
8051
  * The extrinsic is built rather than passed in because the api it is built on decides where it
8039
8052
  * is signed and sent: a websocket that is down when the queue reaches this submission diverts it
8040
8053
  * to {@link sendViaHttp}, which needs the call bound to the HTTP api instead.
8041
8054
  */
8042
- async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = 3e4) {
8055
+ async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = INCLUSION_TIMEOUT_MS) {
8043
8056
  const result = await this.submissionQueue.add(async () => {
8044
8057
  if (!this.api.isConnected) {
8045
8058
  try {
@@ -8077,36 +8090,72 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8077
8090
  * Signs and sends an extrinsic, handling status updates and errors.
8078
8091
  * Implements retry logic with progressive tip increases for stuck transactions.
8079
8092
  *
8080
- * A retry only happens when the previous attempt verifiably went nowhere. Once an attempt's
8081
- * extrinsic is known to be pooled (`pending`), re-signing the same call would race our own
8082
- * submission: the copy either bounces off the pool (1014, same nonce below the replacement
8083
- * priority bump) or if the original lands first, freeing the nonce — executes as a duplicate
8084
- * and fails on-chain (e.g. `BidNotFound` for a retraction). Neither can succeed, so the pending
8085
- * result is returned for the caller to confirm later.
8093
+ * Two kinds of failure are retried, and the difference is the nonce.
8094
+ *
8095
+ * An attempt that verifiably went nowhere (rejected before the pool, dropped, invalid) leaves
8096
+ * the account nonce free, so the next attempt simply re-signs with the auto-nonce.
8097
+ *
8098
+ * An attempt that reached the pool and was still there when the watch timed out (`stalled`) is
8099
+ * retried as a *replacement*: the same nonce it was signed with, and double the tip. Substrate's
8100
+ * pool evicts a pooled extrinsic in favour of a higher-priority one at the same (account, nonce),
8101
+ * so exactly one of the two can ever execute. This matters for a bid, which is worth nothing once
8102
+ * its window closes — waiting out a stalled extrinsic usually means not bidding at all.
8103
+ *
8104
+ * Re-signing without pinning the nonce is what must never happen here. The auto-nonce is read
8105
+ * from on-chain state, so it is only the stalled extrinsic's nonce for as long as that extrinsic
8106
+ * stays out of a block — and a stall is precisely the case where it may land at any moment. Once
8107
+ * it does, an unpinned retry takes the *next* nonce and both execute: a duplicate `placeBid` that
8108
+ * fails on-chain, and a second `retractBid` that pulls the bid just placed. When the signed nonce
8109
+ * cannot be read, the stalled result is returned rather than guessed at.
8110
+ *
8111
+ * A rejection that bounced off a copy already pooled (1013/1014) is likewise left alone: that
8112
+ * copy is in flight and its outcome is unknown here, so the `pending` result goes back to the
8113
+ * caller to confirm later.
8086
8114
  */
8087
8115
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
8088
8116
  const keyPair = this.getKeyPair();
8089
8117
  let attempt = 0;
8118
+ let nonce;
8119
+ let stalled;
8090
8120
  while (attempt < maxRetries) {
8091
8121
  const currentTip = BASE_TIP * BigInt(2 ** attempt);
8092
8122
  attempt++;
8093
8123
  try {
8094
- const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
8095
- if (result.success || result.pending || result.error?.includes("Dispatch error")) {
8124
+ const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs, nonce);
8125
+ if (result.success || result.error?.includes("Dispatch error")) {
8096
8126
  return result;
8097
8127
  }
8128
+ if (result.stalled) {
8129
+ stalled = result;
8130
+ nonce ??= this.signedNonce(extrinsic);
8131
+ if (nonce === void 0) return result;
8132
+ continue;
8133
+ }
8134
+ if (result.pending) return stalled ?? result;
8098
8135
  } catch (err) {
8099
- return {
8136
+ return stalled ?? {
8100
8137
  success: false,
8101
8138
  error: err instanceof Error ? err.message : "Unknown error"
8102
8139
  };
8103
8140
  }
8104
8141
  }
8105
- return {
8142
+ return stalled ?? {
8106
8143
  success: false,
8107
8144
  error: `Transaction failed after ${maxRetries} attempts`
8108
8145
  };
8109
8146
  }
8147
+ /**
8148
+ * The nonce an extrinsic was signed with, or undefined if it carries no readable one — which is
8149
+ * the case before it has ever been signed, and for a stub api in tests.
8150
+ */
8151
+ signedNonce(extrinsic) {
8152
+ try {
8153
+ const nonce = extrinsic.nonce?.toNumber?.();
8154
+ return typeof nonce === "number" && Number.isFinite(nonce) ? nonce : void 0;
8155
+ } catch {
8156
+ return void 0;
8157
+ }
8158
+ }
8110
8159
  /**
8111
8160
  * Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
8112
8161
  * too low") both mean a copy of this account+nonce is already in the pool — almost always our
@@ -8124,10 +8173,15 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8124
8173
  *
8125
8174
  * A timeout is only a failure when the extrinsic never made it into the transaction pool.
8126
8175
  * Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
8127
- * in flight and may well execute after the watch is abandoned — the result is then `pending`,
8128
- * telling the caller to confirm the outcome later instead of re-signing the same call.
8176
+ * in flight and may well execute after the watch is abandoned — the result is then `pending`
8177
+ * and `stalled`, telling the caller to replace it under the same nonce or confirm it later,
8178
+ * never to re-sign the same call under a fresh one.
8179
+ *
8180
+ * `nonce` pins the submission to a specific account nonce, which is what makes a retry a pool
8181
+ * replacement rather than a second extrinsic queued behind the first. Left undefined on the
8182
+ * first attempt, where the api's auto-nonce is correct.
8129
8183
  */
8130
- async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
8184
+ async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs, nonce) {
8131
8185
  return new Promise((resolve) => {
8132
8186
  let resolved = false;
8133
8187
  let unsubscribe = null;
@@ -8141,12 +8195,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8141
8195
  resolve({
8142
8196
  success: false,
8143
8197
  pending: enteredPool || void 0,
8198
+ stalled: enteredPool || void 0,
8144
8199
  extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
8145
8200
  error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
8146
8201
  });
8147
8202
  }
8148
8203
  }, timeoutMs);
8149
- extrinsic.signAndSend(keyPair, { tip }, (result) => {
8204
+ extrinsic.signAndSend(keyPair, nonce === void 0 ? { tip } : { tip, nonce }, (result) => {
8150
8205
  if (resolved) return;
8151
8206
  if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
8152
8207
  enteredPool = true;
@@ -11983,52 +12038,85 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11983
12038
  }
11984
12039
  }
11985
12040
  }`;
11986
- var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11987
- query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11988
- phantomOrderPriceSnapshots(
12041
+ var AVAILABLE_LIQUIDITY = `
12042
+ query AvailableLiquidity(
12043
+ $poolId: String!
12044
+ $sourceChain: String!
12045
+ $destinationChain: String!
12046
+ $direction: String!
12047
+ ) {
12048
+ poolChainLiquidities(
11989
12049
  filter: {
11990
12050
  and: [
11991
- { tokenA: { equalTo: $tokenA } }
11992
- { tokenB: { equalTo: $tokenB } }
12051
+ { poolId: { equalToInsensitive: $poolId } }
12052
+ { chain: { equalTo: $destinationChain } }
12053
+ { direction: { equalTo: $direction } }
11993
12054
  ]
11994
12055
  }
11995
- orderBy: SNAPSHOT_TIME_DESC
11996
12056
  first: 1
11997
12057
  ) {
11998
12058
  nodes {
11999
- commitment
12000
- tokenA
12001
- tokenB
12002
- snapshotTime
12059
+ depth
12060
+ bidCount
12061
+ unrestrictedDepth
12062
+ unrestrictedBidCount
12063
+ lastUpdatedAt
12064
+ }
12065
+ }
12066
+ poolRoutes(
12067
+ filter: {
12068
+ and: [
12069
+ { poolId: { equalToInsensitive: $poolId } }
12070
+ { sourceChain: { equalTo: $sourceChain } }
12071
+ { chain: { equalTo: $destinationChain } }
12072
+ { direction: { equalTo: $direction } }
12073
+ ]
12074
+ }
12075
+ first: 1
12076
+ ) {
12077
+ nodes {
12078
+ depth
12079
+ bidCount
12080
+ lastUpdatedAt
12003
12081
  }
12004
12082
  }
12005
12083
  }`;
12006
- var LIQUIDITY_PROVIDER_BALANCES = `
12007
- query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
12008
- liquidityProviderBalances(
12084
+ var BUY_AND_SELL_RATES = `
12085
+ query BuyAndSellRates(
12086
+ $poolId: String!
12087
+ $directChain: String!
12088
+ $directDirection: String!
12089
+ $reverseChain: String!
12090
+ $reverseDirection: String!
12091
+ ) {
12092
+ direct: poolChainLiquidities(
12009
12093
  filter: {
12010
12094
  and: [
12011
- { commitment: { equalTo: $commitment } }
12012
- { tokenAddress: { equalTo: $tokenAddress } }
12095
+ { poolId: { equalToInsensitive: $poolId } }
12096
+ { chain: { equalTo: $directChain } }
12097
+ { direction: { equalTo: $directDirection } }
12013
12098
  ]
12014
12099
  }
12100
+ first: 1
12015
12101
  ) {
12016
- aggregates {
12017
- sum {
12018
- balance
12019
- }
12020
- distinctCount {
12021
- providerId
12022
- }
12102
+ nodes {
12103
+ rate
12104
+ lastUpdatedAt
12023
12105
  }
12024
- groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
12025
- keys
12026
- sum {
12027
- balance
12028
- }
12029
- distinctCount {
12030
- providerId
12031
- }
12106
+ }
12107
+ reverse: poolChainLiquidities(
12108
+ filter: {
12109
+ and: [
12110
+ { poolId: { equalToInsensitive: $poolId } }
12111
+ { chain: { equalTo: $reverseChain } }
12112
+ { direction: { equalTo: $reverseDirection } }
12113
+ ]
12114
+ }
12115
+ first: 1
12116
+ ) {
12117
+ nodes {
12118
+ rate
12119
+ lastUpdatedAt
12032
12120
  }
12033
12121
  }
12034
12122
  }`;
@@ -18172,178 +18260,191 @@ var OrderStatusChecker = class {
18172
18260
  return true;
18173
18261
  }
18174
18262
  };
18175
- var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
18263
+
18264
+ // src/protocols/intents/liquidity-pool.ts
18265
+ function sortPoolSymbols(symbolA, symbolB) {
18266
+ return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
18267
+ }
18268
+ function poolSlug(symbolA, symbolB) {
18269
+ return sortPoolSymbols(symbolA, symbolB).join("-");
18270
+ }
18271
+ function resolveLiquidityPool(symbolA, symbolB) {
18272
+ const [token0Symbol, token1Symbol] = sortPoolSymbols(symbolA, symbolB);
18273
+ return {
18274
+ poolId: `${token0Symbol}-${token1Symbol}`,
18275
+ token0Symbol,
18276
+ token1Symbol
18277
+ };
18278
+ }
18279
+
18280
+ // src/protocols/intents/LiquidityEngine.ts
18281
+ var INDEXER_FIXED_POINT_DECIMALS = 18;
18282
+ var POOL_RATE_SCALE = 10n ** 18n;
18283
+ var SELL = "SELL";
18284
+ var BUY = "BUY";
18285
+ var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT"]);
18176
18286
  var LiquidityEngine = class {
18177
- /**
18178
- * @param queryClient - Nexus GraphQL client attached to the gateway.
18179
- * @param chainConfigService - Resolves token decimals for formatted results.
18180
- */
18181
- constructor(queryClient, chainConfigService) {
18287
+ constructor(queryClient) {
18182
18288
  this.queryClient = queryClient;
18183
- this.chainConfigService = chainConfigService;
18184
18289
  }
18185
18290
  queryClient;
18186
- chainConfigService;
18187
18291
  /**
18188
- * Retrieves the newest directional Phantom snapshot for a pair and its
18189
- * indexed output-token liquidity.
18292
+ * Returns liquidity reachable from one source chain on one destination.
18190
18293
  *
18191
- * Nexus filters balances to the canonical output token, then aggregates them
18192
- * overall and by chain. The returned amounts are decimal strings: the total
18193
- * uses the canonical Base output-token decimals, while each chain group uses
18194
- * that chain's token decimals. They describe `snapshotTime`, not live
18195
- * reservations or fill guarantees.
18294
+ * The caller resolves chain-specific token addresses through chain
18295
+ * configuration; this layer only maps those configured symbols onto the
18296
+ * indexer's canonical pool and route fields.
18196
18297
  *
18197
- * @param params - Canonical Phantom-market input and output token addresses.
18198
- * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
18199
- * the directional pair.
18200
- * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
18201
- * or internally inconsistent snapshot data.
18202
- */
18203
- async getAvailableLiquiditySnapshot(params) {
18204
- const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
18205
- const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
18206
- const response = await this.queryClient.request(
18207
- LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
18208
- { tokenA: tokenIn, tokenB: tokenOut }
18209
- );
18210
- const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
18211
- if (!node) return;
18212
- const commitment = node.commitment.toLowerCase();
18213
- if (!COMMITMENT_PATTERN.test(commitment)) {
18214
- throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
18215
- }
18216
- if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
18217
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
18218
- }
18219
- const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
18220
- if (Number.isNaN(snapshotTime.getTime())) {
18221
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
18298
+ * Destination, unrestricted, and explicit-route capacity are returned as
18299
+ * separate values so callers can apply their own source-chain policy.
18300
+ *
18301
+ * @returns `undefined` only when the indexer has not published a destination
18302
+ * pool sample yet.
18303
+ */
18304
+ async getAvailableLiquidity(params) {
18305
+ const pool = resolveLiquidityPool(params.source.symbol, params.destination.symbol);
18306
+ const direction = params.source.symbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18307
+ const variables = {
18308
+ poolId: pool.poolId,
18309
+ sourceChain: params.source.chain,
18310
+ destinationChain: params.destination.chain,
18311
+ direction
18312
+ };
18313
+ const response = await this.queryClient.request(AVAILABLE_LIQUIDITY, variables);
18314
+ if (!response?.poolChainLiquidities?.nodes || !response?.poolRoutes?.nodes) {
18315
+ throw new InvalidLiquidityIndexerResponseError("liquidity connections are missing");
18222
18316
  }
18223
- const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
18224
- commitment,
18225
- tokenAddress: tokenOut
18226
- });
18317
+ const chainLiquidity = response.poolChainLiquidities.nodes[0];
18318
+ if (!chainLiquidity) return void 0;
18319
+ const route = response.poolRoutes.nodes[0];
18227
18320
  return {
18228
- totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
18229
- providerCount,
18230
- tokenAddress: tokenOut,
18231
- snapshotTime,
18232
- liquidityByChain: liquidityByChain.map((group) => ({
18233
- ...group,
18234
- totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
18235
- }))
18321
+ sourceChain: params.source.chain,
18322
+ destinationChain: params.destination.chain,
18323
+ tokenAddress: normalizeEvmAddress(params.destination.address, "destination token"),
18324
+ updatedAt: readIndexerDate(chainLiquidity.lastUpdatedAt, "destination lastUpdatedAt"),
18325
+ destination: readLiquiditySlice(chainLiquidity.depth, chainLiquidity.bidCount, "destination"),
18326
+ unrestricted: readLiquiditySlice(
18327
+ chainLiquidity.unrestrictedDepth,
18328
+ chainLiquidity.unrestrictedBidCount,
18329
+ "unrestricted"
18330
+ ),
18331
+ explicitRoute: route ? {
18332
+ ...readLiquiditySlice(route.depth, route.bidCount, "explicit route"),
18333
+ updatedAt: readIndexerDate(route.lastUpdatedAt, "route lastUpdatedAt")
18334
+ } : null
18236
18335
  };
18237
18336
  }
18238
18337
  /**
18239
- * Requests server-side sums and distinct provider counts for one immutable
18240
- * snapshot/output-token pair, including chain-level aggregate groups.
18338
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
18339
+ * per one base token.
18241
18340
  *
18242
- * `commitment` uniquely identifies the selected snapshot
18243
- */
18244
- async querySnapshotLiquidityAggregates(params) {
18245
- const response = await this.queryClient.request(
18246
- LIQUIDITY_PROVIDER_BALANCES,
18247
- { commitment: params.commitment, tokenAddress: params.tokenAddress }
18248
- );
18249
- const connection = response?.liquidityProviderBalances;
18250
- const aggregates = connection?.aggregates;
18251
- if (!connection || !aggregates) {
18252
- throw new InvalidAvailableLiquiditySnapshotError(
18253
- params.commitment,
18254
- "liquidityProviderBalances aggregates are missing"
18255
- );
18256
- }
18257
- const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
18258
- const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
18259
- const liquidityByChain = connection.groupedAggregates.map((group, index) => {
18260
- const [chain, tokenAddress] = group.keys;
18261
- if (!chain?.trim() || !tokenAddress) {
18262
- throw new InvalidAvailableLiquiditySnapshotError(
18263
- params.commitment,
18264
- `liquidity group ${index} has invalid keys`
18265
- );
18266
- }
18267
- const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
18268
- tokenAddress,
18269
- params.commitment,
18270
- `liquidity group ${index} tokenAddress`
18271
- );
18272
- if (normalizedTokenAddress !== params.tokenAddress) {
18273
- throw new InvalidAvailableLiquiditySnapshotError(
18274
- params.commitment,
18275
- `liquidity group ${index} tokenAddress does not match the snapshot output token`
18276
- );
18277
- }
18278
- return {
18279
- chain: chain.trim(),
18280
- tokenAddress: normalizedTokenAddress,
18281
- totalLiquidity: parseSnapshotBigInt(
18282
- group.sum.balance ?? "0",
18283
- params.commitment,
18284
- `liquidity group ${index} balance`
18285
- ),
18286
- providerCount: parseProviderCount(
18287
- group.distinctCount.providerId,
18288
- params.commitment,
18289
- `liquidity group ${index}`
18290
- )
18291
- };
18341
+ * The requested direction is read on the destination chain; its reverse is
18342
+ * read on the source chain. This mirrors where each direction's output token
18343
+ * must be delivered for a cross-chain trade.
18344
+ */
18345
+ async getBuyAndSellRates(params) {
18346
+ const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
18347
+ const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18348
+ const reverseDirection = directDirection === SELL ? BUY : SELL;
18349
+ const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
18350
+ poolId: pool.poolId,
18351
+ directChain: params.destinationChain,
18352
+ directDirection,
18353
+ reverseChain: params.sourceChain,
18354
+ reverseDirection
18292
18355
  });
18293
- const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
18294
- if (groupedTotal !== totalLiquidity) {
18295
- throw new InvalidAvailableLiquiditySnapshotError(
18296
- params.commitment,
18297
- "grouped liquidity does not match the total liquidity"
18298
- );
18299
- }
18300
- return { totalLiquidity, providerCount, liquidityByChain };
18356
+ if (!response?.direct?.nodes || !response?.reverse?.nodes) {
18357
+ throw new InvalidLiquidityIndexerResponseError("rate connections are missing");
18358
+ }
18359
+ const direct = readIndexedRate(response.direct.nodes[0], "direct rate");
18360
+ const reverse = readIndexedRate(response.reverse.nodes[0], "reverse rate");
18361
+ if (!direct && !reverse) return void 0;
18362
+ const quoteTokenSymbol = resolveQuoteTokenSymbol(
18363
+ params.tokenInSymbol,
18364
+ params.tokenOutSymbol,
18365
+ direct?.scaledRate,
18366
+ reverse?.scaledRate
18367
+ );
18368
+ const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
18369
+ const buy = quoteIsTokenOut ? direct : reverse;
18370
+ const sell = quoteIsTokenOut ? reverse : direct;
18371
+ return {
18372
+ baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
18373
+ quoteTokenSymbol,
18374
+ sourceChain: params.sourceChain,
18375
+ destinationChain: params.destinationChain,
18376
+ buyRate: buy ? formatUnits(buy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
18377
+ sellRate: sell ? formatUnits(reciprocalRate(sell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
18378
+ buyRateUpdatedAt: buy?.updatedAt ?? null,
18379
+ sellRateUpdatedAt: sell?.updatedAt ?? null
18380
+ };
18301
18381
  }
18302
- /** Formats a raw amount using the configured decimals for its chain/token. */
18303
- formatLiquidity(amount, chain, tokenAddress, commitment) {
18304
- const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
18305
- if (decimals === void 0) {
18306
- throw new InvalidAvailableLiquiditySnapshotError(
18307
- commitment,
18308
- `token decimals are not configured for ${tokenAddress} on ${chain}`
18309
- );
18310
- }
18311
- return formatUnits(amount, decimals);
18382
+ };
18383
+ var InvalidLiquidityIndexerResponseError = class extends Error {
18384
+ constructor(reason) {
18385
+ super(`Invalid liquidity indexer response: ${reason}`);
18386
+ this.name = "InvalidLiquidityIndexerResponseError";
18312
18387
  }
18313
18388
  };
18314
- var InvalidAvailableLiquiditySnapshotError = class extends Error {
18315
- /** Creates an error that identifies the invalid snapshot and field/reason. */
18316
- constructor(commitment, reason) {
18317
- super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
18318
- this.name = "InvalidAvailableLiquiditySnapshotError";
18389
+ var UnsupportedLiquidityAssetError = class extends Error {
18390
+ constructor(chain, asset) {
18391
+ super(`No configured liquidity asset found for ${asset} on ${chain}`);
18392
+ this.name = "UnsupportedLiquidityAssetError";
18393
+ }
18394
+ };
18395
+ var UnsupportedLiquidityChainError = class extends Error {
18396
+ constructor(chainId) {
18397
+ super(`No configured liquidity chain found for chain ID ${chainId}`);
18398
+ this.name = "UnsupportedLiquidityChainError";
18319
18399
  }
18320
18400
  };
18321
- function parseSnapshotBigInt(value, commitment, field) {
18401
+ function readLiquiditySlice(depth, providerCount, label) {
18402
+ if (!Number.isSafeInteger(providerCount) || providerCount < 0) {
18403
+ throw new InvalidLiquidityIndexerResponseError(`${label} provider count is invalid`);
18404
+ }
18405
+ return { totalLiquidity: formatIndexerAmount(depth, `${label} depth`), providerCount };
18406
+ }
18407
+ function formatIndexerAmount(value, label) {
18322
18408
  try {
18323
18409
  const amount = BigInt(value);
18324
- if (amount < 0n) {
18325
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
18326
- }
18327
- return amount;
18328
- } catch (error) {
18329
- if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
18330
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
18410
+ if (amount < 0n) throw new Error();
18411
+ return formatUnits(amount, INDEXER_FIXED_POINT_DECIMALS);
18412
+ } catch {
18413
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a non-negative integer`);
18331
18414
  }
18332
18415
  }
18333
- function parseProviderCount(value, commitment, field) {
18334
- const count = Number(value);
18335
- if (!Number.isSafeInteger(count) || count < 0) {
18336
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
18337
- }
18338
- return count;
18416
+ function readIndexerDate(value, label) {
18417
+ const date = new Date(dateStringtoTimestamp(value));
18418
+ if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
18419
+ return date;
18339
18420
  }
18340
- function normalizeIndexedLiquidityAddress(address, commitment, field) {
18421
+ function readIndexedRate(node, label) {
18422
+ if (!node) return void 0;
18341
18423
  try {
18342
- return normalizeEvmAddress(address, field);
18343
- } catch {
18344
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
18424
+ const scaledRate = BigInt(node.rate);
18425
+ if (scaledRate <= 0n) throw new Error();
18426
+ return {
18427
+ scaledRate,
18428
+ updatedAt: readIndexerDate(node.lastUpdatedAt, `${label} lastUpdatedAt`)
18429
+ };
18430
+ } catch (error) {
18431
+ if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
18432
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
18345
18433
  }
18346
18434
  }
18435
+ function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
18436
+ const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
18437
+ const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
18438
+ if (inputIsUsdStable !== outputIsUsdStable) return inputIsUsdStable ? tokenOutSymbol : tokenInSymbol;
18439
+ if (directRate !== void 0) return directRate >= POOL_RATE_SCALE ? tokenOutSymbol : tokenInSymbol;
18440
+ if (reverseRate !== void 0) return reverseRate >= POOL_RATE_SCALE ? tokenInSymbol : tokenOutSymbol;
18441
+ throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
18442
+ }
18443
+ function reciprocalRate(rate, label) {
18444
+ const reciprocal = POOL_RATE_SCALE * POOL_RATE_SCALE / rate;
18445
+ if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
18446
+ return reciprocal;
18447
+ }
18347
18448
 
18348
18449
  // src/protocols/intents/quote/types.ts
18349
18450
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -18768,8 +18869,6 @@ var IntentGateway = class _IntentGateway {
18768
18869
  gasEstimator;
18769
18870
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
18770
18871
  quoteStrategies;
18771
- /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18772
- phantomSnapshotPairResolver;
18773
18872
  /**
18774
18873
  * Private constructor — use {@link IntentGateway.create} instead.
18775
18874
  *
@@ -18813,7 +18912,6 @@ var IntentGateway = class _IntentGateway {
18813
18912
  this.bidManager = bidManager;
18814
18913
  this.gasEstimator = gasEstimator;
18815
18914
  this._crypto = crypto;
18816
- this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
18817
18915
  this.quoteStrategies = {
18818
18916
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18819
18917
  dest.configService,
@@ -18893,33 +18991,62 @@ var IntentGateway = class _IntentGateway {
18893
18991
  return handler.quote({ ...params, strategy }, source, destination);
18894
18992
  }
18895
18993
  /**
18896
- * Returns the output-token liquidity measured in the latest directional
18897
- * Phantom snapshot for this gateway's source and destination.
18994
+ * Returns indexed destination liquidity and its source-routing slices.
18898
18995
  *
18899
- * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18900
- * The snapshot itself determines the output token and chain to aggregate. The
18901
- * amount is in the token's smallest unit and reflects the indexer's
18902
- * `snapshotTime`; it is not a live reservation or fill guarantee.
18996
+ * Destination, unrestricted, and explicit-route capacity come exclusively
18997
+ * from the indexer's pair-centric liquidity entities. The SDK does not decide
18998
+ * whether unrestricted bidders cover the source chain. Amounts reflect the
18999
+ * latest rolling sample; they are not reservations or fill guarantees.
18903
19000
  *
18904
19001
  * Requires a prior call to {@link withQueryClient}.
18905
19002
  */
18906
19003
  async queryAvailableLiquidity(params) {
18907
19004
  const { queryClient } = this.requireIndexer();
18908
- const sourceStateMachineId = this.source.config.stateMachineId;
18909
- const destinationStateMachineId = this.dest.config.stateMachineId;
18910
- const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18911
- if (!pair) {
18912
- throw new UnsupportedIntentQuotePairError({
18913
- source: sourceStateMachineId,
18914
- destination: destinationStateMachineId,
18915
- tokenIn: params.tokenIn,
18916
- tokenOut: params.tokenOut,
18917
- quoteSource: "Phantom snapshot pair"
18918
- });
18919
- }
18920
- return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18921
- tokenIn: pair.tokenA,
18922
- tokenOut: pair.tokenB
19005
+ const sourceStateMachineId = getConfigByStateMachineId(this.source.config.stateMachineId)?.stateMachineId;
19006
+ const destinationStateMachineId = getConfigByStateMachineId(this.dest.config.stateMachineId)?.stateMachineId;
19007
+ if (!sourceStateMachineId) throw new UnsupportedLiquidityChainError(this.source.config.stateMachineId);
19008
+ if (!destinationStateMachineId) throw new UnsupportedLiquidityChainError(this.dest.config.stateMachineId);
19009
+ const sourceToken = this.source.configService.getAssetMetadataByAddress(sourceStateMachineId, params.tokenIn);
19010
+ const destinationToken = this.dest.configService.getAssetMetadataByAddress(
19011
+ destinationStateMachineId,
19012
+ params.tokenOut
19013
+ );
19014
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceStateMachineId, params.tokenIn);
19015
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationStateMachineId, params.tokenOut);
19016
+ return new LiquidityEngine(queryClient).getAvailableLiquidity({
19017
+ source: {
19018
+ chain: sourceStateMachineId,
19019
+ ...sourceToken
19020
+ },
19021
+ destination: {
19022
+ chain: destinationStateMachineId,
19023
+ ...destinationToken
19024
+ }
19025
+ });
19026
+ }
19027
+ /**
19028
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
19029
+ * without requiring token addresses. Symbols are matched case-insensitively;
19030
+ * chain IDs are numeric IDs for chains configured in the SDK.
19031
+ */
19032
+ async queryBuyAndSellRates(params) {
19033
+ const { queryClient } = this.requireIndexer();
19034
+ const sourceChain = chainConfigs[params.sourceChainId]?.stateMachineId;
19035
+ const destinationChain = chainConfigs[params.destinationChainId]?.stateMachineId;
19036
+ if (!sourceChain) throw new UnsupportedLiquidityChainError(params.sourceChainId);
19037
+ if (!destinationChain) throw new UnsupportedLiquidityChainError(params.destinationChainId);
19038
+ const sourceToken = this.source.configService.getAssetMetadataBySymbol(sourceChain, params.tokenInSymbol);
19039
+ const destinationToken = this.dest.configService.getAssetMetadataBySymbol(
19040
+ destinationChain,
19041
+ params.tokenOutSymbol
19042
+ );
19043
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceChain, params.tokenInSymbol);
19044
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationChain, params.tokenOutSymbol);
19045
+ return new LiquidityEngine(queryClient).getBuyAndSellRates({
19046
+ sourceChain,
19047
+ destinationChain,
19048
+ tokenInSymbol: sourceToken.symbol,
19049
+ tokenOutSymbol: destinationToken.symbol
18923
19050
  });
18924
19051
  }
18925
19052
  /**
@@ -24021,6 +24148,6 @@ async function teleportDot(param_) {
24021
24148
  return stream;
24022
24149
  }
24023
24150
 
24024
- 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, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodePhantomBidDeclaration, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeISMPMessage, encodePhantomBidDeclaration, 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 };
24151
+ 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, INCLUSION_TIMEOUT_MS, IntentGateway, ABI3 as IntentGatewayABI, IntentOrderStatus, IntentsCoprocessor, InvalidLiquidityIndexerResponseError, 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, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, WrappedHyperFungibleTokenABI, __test, adjustDecimals, bytes20ToBytes32, bytes32ToBytes20, calculateAllowanceMappingLocation, calculateBalanceMappingLocation, chainConfigs, constructRedeemEscrowRequestBody, constructRefundEscrowRequestBody, convertCodecToIGetRequest, convertCodecToIProof, convertIGetRequestToCodec, convertIProofToCodec, convertStateIdToStateMachineId, convertStateMachineEnumToString, convertStateMachineIdToEnum, createEvmChain, createQueryClient, decodeAcceptedSourceChains, decodeERC7821ExecuteBatch, decodePhantomBidDeclaration, decodeUserOpScale, deriveHttpUrl, encodeAcceptedSourceChains, encodeERC7821ExecuteBatch, encodeISMPMessage, encodePhantomBidDeclaration, 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, poolSlug, postRequestCommitment, queryAssetTeleported, queryGetRequest, queryPostRequest, quoteUniswap, requestCommitmentKey, responseCommitmentKey, retryPromise, sortPoolSymbols, teleport, teleportDot, transformOrderForContract, tronChainIds, tronNile };
24025
24152
  //# sourceMappingURL=index.js.map
24026
24153
  //# sourceMappingURL=index.js.map