@hyperbridge/sdk 2.8.2 → 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.
@@ -4,7 +4,7 @@ import { baseSepolia, optimismSepolia, arbitrumSepolia, soneium, gnosis, optimis
4
4
  import { TronWeb } from 'tronweb';
5
5
  import { flatten, zip, capitalize, maxBy, isNil } from 'lodash-es';
6
6
  import { match } from 'ts-pattern';
7
- import { WsProvider, ApiPromise, Keyring } from '@polkadot/api';
7
+ import { WsProvider, ApiPromise, HttpProvider, Keyring } from '@polkadot/api';
8
8
  import { Struct, Vector, u8, Bytes, Enum, Tuple, _void, u64, u32, Option, bool, u128 } from 'scale-ts';
9
9
  import { keccakAsU8a, decodeAddress, keccakAsHex, xxhashAsU8a, blake2AsU8a } from '@polkadot/util-crypto';
10
10
  import { hexToU8a, u8aToHex, u8aConcat, stringToU8a, isHex as isHex$1, u8aToString } from '@polkadot/util';
@@ -2806,18 +2806,25 @@ var chainConfigs = {
2806
2806
  DAI: "0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3",
2807
2807
  USDC: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
2808
2808
  USDT: "0x55d398326f99059ff775485246999027b3197955",
2809
- EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209"
2809
+ EXT: "0x7C8c11ADb8EF7cd3CFa718008Ea048445C6E7209",
2810
+ cNGN: "0xa8AEA66B361a8d53e8865c62D142167Af28Af058"
2810
2811
  },
2811
2812
  tokenDecimals: {
2812
2813
  USDC: 18,
2813
2814
  USDT: 18,
2815
+ // 6, not 18 — cNGN keeps the same decimals it has on every other chain, unlike the
2816
+ // Binance-pegged stables above. Every phantom standard_amount and pool rate divides
2817
+ // by this, so the divergence from its neighbours here is load-bearing, not a typo.
2818
+ cNGN: 6,
2814
2819
  EXT: 18
2815
2820
  },
2816
2821
  tokenStorageSlots: {
2817
2822
  USDT: { balanceSlot: 1, allowanceSlot: 2 },
2818
2823
  USDC: { balanceSlot: 1, allowanceSlot: 2 },
2819
2824
  WETH: { balanceSlot: 3, allowanceSlot: 4 },
2820
- DAI: { balanceSlot: 0, allowanceSlot: 0 }
2825
+ DAI: { balanceSlot: 0, allowanceSlot: 0 },
2826
+ cNGN: { balanceSlot: 201, allowanceSlot: 202 }
2827
+ // custom upgradeable layout, as on Base
2821
2828
  },
2822
2829
  addresses: {
2823
2830
  IntentGateway: "0xAe041F7B0CB581876832830baeB6a2Aa2a3C9716",
@@ -5282,11 +5289,21 @@ var ChainConfigService = class {
5282
5289
  * it, so a new asset is added once in `chain.ts` and nowhere else.
5283
5290
  */
5284
5291
  getAssetBySymbol(chain, symbol) {
5285
- const assets = this.getConfig(chain)?.assets;
5292
+ return this.getAssetMetadataBySymbol(chain, symbol)?.address;
5293
+ }
5294
+ /** Resolves a configured token symbol case-insensitively on a specific chain. */
5295
+ getAssetMetadataBySymbol(chain, symbol) {
5296
+ const config = this.getConfig(chain);
5297
+ const assets = config?.assets;
5286
5298
  if (!assets) return void 0;
5287
5299
  const target = symbol.trim().toUpperCase();
5288
5300
  for (const [key, address] of Object.entries(assets)) {
5289
- if (key.toUpperCase() === target) return address;
5301
+ if (key.toUpperCase() !== target) continue;
5302
+ return {
5303
+ symbol: key,
5304
+ address,
5305
+ decimals: config.tokenDecimals?.[key]
5306
+ };
5290
5307
  }
5291
5308
  return void 0;
5292
5309
  }
@@ -7849,6 +7866,28 @@ function encodeISMPMessage(message) {
7849
7866
  }
7850
7867
  var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
7851
7868
  var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
7869
+ var HYPERBRIDGE_TYPES_BUNDLE = {
7870
+ spec: {
7871
+ nexus: { hasher: keccakAsU8a },
7872
+ gargantua: { hasher: keccakAsU8a }
7873
+ }
7874
+ };
7875
+ var BASE_TIP = 1000000000n;
7876
+ var HTTP_CONNECT_TIMEOUT_MS = 2e4;
7877
+ var INCLUSION_TIMEOUT_MS = 2e4;
7878
+ var PHANTOM_POLL_INTERVAL_MS = 15e3;
7879
+ var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
7880
+ function rejectAfter(ms, message) {
7881
+ return new Promise((_resolve, reject) => {
7882
+ const timer = setTimeout(() => reject(new Error(message)), ms);
7883
+ timer.unref?.();
7884
+ });
7885
+ }
7886
+ function deriveHttpUrl(wsUrl) {
7887
+ if (wsUrl.startsWith("wss://")) return `https://${wsUrl.slice("wss://".length)}`;
7888
+ if (wsUrl.startsWith("ws://")) return `http://${wsUrl.slice("ws://".length)}`;
7889
+ throw new Error(`Cannot derive an HTTP endpoint from a non-websocket url: ${wsUrl}`);
7890
+ }
7852
7891
  var BidCodec = Struct({ filler: Bytes(32), user_op: Vector(u8) });
7853
7892
  var PackedUserOperationCodec = Struct({
7854
7893
  sender: Bytes(20),
@@ -7909,6 +7948,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7909
7948
  ownsConnection;
7910
7949
  /** Cached result of whether the node exposes intents_* RPC methods */
7911
7950
  hasIntentsRpc = null;
7951
+ /** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
7952
+ httpApi = null;
7912
7953
  // Serialises every extrinsic submission on this instance's substrate account. All submit/retract
7913
7954
  // methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
7914
7955
  // (bids for orders on different chains, or several phantom orders in one interval) they would grab
@@ -7925,12 +7966,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7925
7966
  static async connect(wsUrl, substratePrivateKey) {
7926
7967
  const api = await ApiPromise.create({
7927
7968
  provider: new WsProvider(wsUrl),
7928
- typesBundle: {
7929
- spec: {
7930
- nexus: { hasher: keccakAsU8a },
7931
- gargantua: { hasher: keccakAsU8a }
7932
- }
7933
- }
7969
+ typesBundle: HYPERBRIDGE_TYPES_BUNDLE
7934
7970
  });
7935
7971
  return new _IntentsCoprocessor(api, substratePrivateKey, true);
7936
7972
  }
@@ -7956,15 +7992,85 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7956
7992
  static fromApi(api, substratePrivateKey) {
7957
7993
  return new _IntentsCoprocessor(api, substratePrivateKey, false);
7958
7994
  }
7995
+ /**
7996
+ * The API every RPC query runs on: HTTP, connected to the same node as the websocket. Exposed so
7997
+ * callers query through this connection rather than opening one of their own.
7998
+ *
7999
+ * The split is by what each transport is for. Queries are one-shot request/response, which HTTP
8000
+ * serves without holding any state that can silently rot between calls. The websocket earns its
8001
+ * keep only where subscriptions do — watching a submitted extrinsic to inclusion.
8002
+ */
8003
+ async queryApi() {
8004
+ return await this.http();
8005
+ }
8006
+ /**
8007
+ * The websocket API, exposed so callers share this one connection instead of opening a second
8008
+ * socket to the same node. Only needed for subscriptions; use {@link queryApi} to read.
8009
+ */
8010
+ get apiConnection() {
8011
+ return this.api;
8012
+ }
7959
8013
  /**
7960
8014
  * Disconnects the underlying API connection if this instance owns it.
7961
- * Only disconnects if created via `connect()`, not when using shared connections.
8015
+ * Only disconnects the websocket if created via `connect()`, not when using shared connections.
8016
+ * The HTTP api is always created here, so it is always ours to close.
7962
8017
  */
7963
8018
  async disconnect() {
8019
+ const http4 = this.httpApi;
8020
+ this.httpApi = null;
8021
+ if (http4) {
8022
+ await http4.then((api) => api.disconnect()).catch(() => {
8023
+ });
8024
+ }
7964
8025
  if (this.ownsConnection) {
7965
8026
  await this.api.disconnect();
7966
8027
  }
7967
8028
  }
8029
+ /**
8030
+ * The HTTP api for this node, connected on first use. Every coprocessor has one — the endpoint
8031
+ * is derived from the websocket's own endpoint, so there is nothing to configure and nothing to
8032
+ * be absent.
8033
+ *
8034
+ * The connection attempt is bounded on both sides. `isReadyOrError` rejects on a failed
8035
+ * handshake, where plain `isReady` would simply never resolve, and the timeout covers an
8036
+ * endpoint that accepts the request and then goes quiet. An unbounded wait here would hang the
8037
+ * poll tick awaiting it, which is precisely the silent stall that polling exists to avoid. A
8038
+ * failed attempt is not cached, so the next call tries again.
8039
+ */
8040
+ async http() {
8041
+ if (!this.httpApi) {
8042
+ const httpUrl = deriveHttpUrl(this.wsEndpoint());
8043
+ const api = new ApiPromise({
8044
+ provider: new HttpProvider(httpUrl),
8045
+ typesBundle: HYPERBRIDGE_TYPES_BUNDLE,
8046
+ // A second connection to the node the ws api already reported on; its init warnings
8047
+ // would just be duplicates.
8048
+ noInitWarn: true
8049
+ });
8050
+ this.httpApi = Promise.race([
8051
+ api.isReadyOrError,
8052
+ rejectAfter(HTTP_CONNECT_TIMEOUT_MS, `HTTP RPC ${httpUrl} did not become ready`)
8053
+ ]).catch(async (err) => {
8054
+ await api.disconnect().catch(() => {
8055
+ });
8056
+ this.httpApi = null;
8057
+ throw new Error(`HTTP RPC ${httpUrl} is unavailable: ${err instanceof Error ? err.message : err}`);
8058
+ });
8059
+ }
8060
+ return await this.httpApi;
8061
+ }
8062
+ /**
8063
+ * The endpoint the websocket provider is connected to. Read from the provider rather than
8064
+ * remembered from a constructor argument, so it is the one endpoint in use no matter which
8065
+ * factory built this instance.
8066
+ */
8067
+ wsEndpoint() {
8068
+ const endpoint = this.api._rpcCore?.provider?.endpoint;
8069
+ if (!endpoint) {
8070
+ throw new Error("Cannot determine the Hyperbridge websocket endpoint to derive an HTTP endpoint from");
8071
+ }
8072
+ return endpoint;
8073
+ }
7968
8074
  /**
7969
8075
  * Creates a Substrate keypair from the configured private key.
7970
8076
  * Supports hex seed (with or without 0x), mnemonic phrases, and URI derivation paths (//Alice).
@@ -7987,50 +8093,119 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7987
8093
  /**
7988
8094
  * Signs and sends an extrinsic. Submissions are serialised through {@link submissionQueue} so
7989
8095
  * concurrent calls never collide on the substrate account nonce — each extrinsic reaches a block
7990
- * (or is confirmed still pooled and returned as `pending`) before the next is signed; auto-nonce
7991
- * via `system.accountNextIndex` counts pooled extrinsics, so a pending one is never re-used.
8096
+ * (or is confirmed still pooled and returned as `pending`) before the next is signed. The
8097
+ * auto-nonce is the account's on-chain nonce, so a still-pooled extrinsic does not advance it: a
8098
+ * submission signed behind a pending one bounces off it (1013/1014) and is reported as pending
8099
+ * too, rather than landing as a second copy.
8100
+ *
8101
+ * The extrinsic is built rather than passed in because the api it is built on decides where it
8102
+ * is signed and sent: a websocket that is down when the queue reaches this submission diverts it
8103
+ * to {@link sendViaHttp}, which needs the call bound to the HTTP api instead.
7992
8104
  */
7993
- async signAndSendExtrinsic(extrinsic, maxRetries = 3, timeoutMs = 3e4) {
7994
- const result = await this.submissionQueue.add(
7995
- () => this.sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs)
7996
- );
8105
+ async signAndSendExtrinsic(build, maxRetries = 3, timeoutMs = INCLUSION_TIMEOUT_MS) {
8106
+ const result = await this.submissionQueue.add(async () => {
8107
+ if (!this.api.isConnected) {
8108
+ try {
8109
+ return await this.sendViaHttp(await this.http(), build);
8110
+ } catch (err) {
8111
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
8112
+ }
8113
+ }
8114
+ return await this.sendExtrinsicWithRetries(build(this.api), maxRetries, timeoutMs);
8115
+ });
7997
8116
  return result ?? { success: false, error: "Submission queue returned no result" };
7998
8117
  }
8118
+ /**
8119
+ * Last-resort submission for when the websocket is down at signing time. A bid is only worth
8120
+ * anything inside its window, so waiting for a reconnect usually means not bidding at all.
8121
+ *
8122
+ * HTTP has no subscriptions, so this is `author_submitExtrinsic`: the node accepts the extrinsic
8123
+ * into its pool and returns its hash, and nothing further is observable from here. That is
8124
+ * exactly the `pending` contract — in flight, outcome unknown, do not re-sign — so the result
8125
+ * says so rather than claiming a success it cannot see.
8126
+ *
8127
+ * Only reached when the socket was already down before signing. A submission that got as far as
8128
+ * the pool over the websocket is never retried here: that is the duplicate-nonce race the
8129
+ * `pending` result exists to prevent.
8130
+ */
8131
+ async sendViaHttp(api, build) {
8132
+ try {
8133
+ const hash = await build(api).signAndSend(this.getKeyPair(), { tip: BASE_TIP });
8134
+ return { success: false, pending: true, extrinsicHash: hash.toHex() };
8135
+ } catch (err) {
8136
+ return this.classifySubmissionError(err instanceof Error ? err : new Error(String(err)));
8137
+ }
8138
+ }
7999
8139
  /**
8000
8140
  * Signs and sends an extrinsic, handling status updates and errors.
8001
8141
  * Implements retry logic with progressive tip increases for stuck transactions.
8002
8142
  *
8003
- * A retry only happens when the previous attempt verifiably went nowhere. Once an attempt's
8004
- * extrinsic is known to be pooled (`pending`), re-signing the same call would race our own
8005
- * submission: the copy either bounces off the pool (1014, same nonce below the replacement
8006
- * priority bump) or if the original lands first, freeing the nonce — executes as a duplicate
8007
- * and fails on-chain (e.g. `BidNotFound` for a retraction). Neither can succeed, so the pending
8008
- * result is returned for the caller to confirm later.
8143
+ * Two kinds of failure are retried, and the difference is the nonce.
8144
+ *
8145
+ * An attempt that verifiably went nowhere (rejected before the pool, dropped, invalid) leaves
8146
+ * the account nonce free, so the next attempt simply re-signs with the auto-nonce.
8147
+ *
8148
+ * An attempt that reached the pool and was still there when the watch timed out (`stalled`) is
8149
+ * retried as a *replacement*: the same nonce it was signed with, and double the tip. Substrate's
8150
+ * pool evicts a pooled extrinsic in favour of a higher-priority one at the same (account, nonce),
8151
+ * so exactly one of the two can ever execute. This matters for a bid, which is worth nothing once
8152
+ * its window closes — waiting out a stalled extrinsic usually means not bidding at all.
8153
+ *
8154
+ * Re-signing without pinning the nonce is what must never happen here. The auto-nonce is read
8155
+ * from on-chain state, so it is only the stalled extrinsic's nonce for as long as that extrinsic
8156
+ * stays out of a block — and a stall is precisely the case where it may land at any moment. Once
8157
+ * it does, an unpinned retry takes the *next* nonce and both execute: a duplicate `placeBid` that
8158
+ * fails on-chain, and a second `retractBid` that pulls the bid just placed. When the signed nonce
8159
+ * cannot be read, the stalled result is returned rather than guessed at.
8160
+ *
8161
+ * A rejection that bounced off a copy already pooled (1013/1014) is likewise left alone: that
8162
+ * copy is in flight and its outcome is unknown here, so the `pending` result goes back to the
8163
+ * caller to confirm later.
8009
8164
  */
8010
8165
  async sendExtrinsicWithRetries(extrinsic, maxRetries, timeoutMs) {
8011
8166
  const keyPair = this.getKeyPair();
8012
- const baseTip = 1000000000n;
8013
8167
  let attempt = 0;
8168
+ let nonce;
8169
+ let stalled;
8014
8170
  while (attempt < maxRetries) {
8015
- const currentTip = baseTip * BigInt(2 ** attempt);
8171
+ const currentTip = BASE_TIP * BigInt(2 ** attempt);
8016
8172
  attempt++;
8017
8173
  try {
8018
- const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs);
8019
- if (result.success || result.pending || result.error?.includes("Dispatch error")) {
8174
+ const result = await this.sendWithTimeout(extrinsic, keyPair, currentTip, timeoutMs, nonce);
8175
+ if (result.success || result.error?.includes("Dispatch error")) {
8020
8176
  return result;
8021
8177
  }
8178
+ if (result.stalled) {
8179
+ stalled = result;
8180
+ nonce ??= this.signedNonce(extrinsic);
8181
+ if (nonce === void 0) return result;
8182
+ continue;
8183
+ }
8184
+ if (result.pending) return stalled ?? result;
8022
8185
  } catch (err) {
8023
- return {
8186
+ return stalled ?? {
8024
8187
  success: false,
8025
8188
  error: err instanceof Error ? err.message : "Unknown error"
8026
8189
  };
8027
8190
  }
8028
8191
  }
8029
- return {
8192
+ return stalled ?? {
8030
8193
  success: false,
8031
8194
  error: `Transaction failed after ${maxRetries} attempts`
8032
8195
  };
8033
8196
  }
8197
+ /**
8198
+ * The nonce an extrinsic was signed with, or undefined if it carries no readable one — which is
8199
+ * the case before it has ever been signed, and for a stub api in tests.
8200
+ */
8201
+ signedNonce(extrinsic) {
8202
+ try {
8203
+ const nonce = extrinsic.nonce?.toNumber?.();
8204
+ return typeof nonce === "number" && Number.isFinite(nonce) ? nonce : void 0;
8205
+ } catch {
8206
+ return void 0;
8207
+ }
8208
+ }
8034
8209
  /**
8035
8210
  * Classifies a submission rejection. Codes 1013 ("already imported") and 1014 ("priority is
8036
8211
  * too low") both mean a copy of this account+nonce is already in the pool — almost always our
@@ -8048,10 +8223,15 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8048
8223
  *
8049
8224
  * A timeout is only a failure when the extrinsic never made it into the transaction pool.
8050
8225
  * Once a pool-entry status (Future/Ready/Broadcast/Retracted) has been seen, the extrinsic is
8051
- * in flight and may well execute after the watch is abandoned — the result is then `pending`,
8052
- * telling the caller to confirm the outcome later instead of re-signing the same call.
8226
+ * in flight and may well execute after the watch is abandoned — the result is then `pending`
8227
+ * and `stalled`, telling the caller to replace it under the same nonce or confirm it later,
8228
+ * never to re-sign the same call under a fresh one.
8229
+ *
8230
+ * `nonce` pins the submission to a specific account nonce, which is what makes a retry a pool
8231
+ * replacement rather than a second extrinsic queued behind the first. Left undefined on the
8232
+ * first attempt, where the api's auto-nonce is correct.
8053
8233
  */
8054
- async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs) {
8234
+ async sendWithTimeout(extrinsic, keyPair, tip, timeoutMs, nonce) {
8055
8235
  return new Promise((resolve) => {
8056
8236
  let resolved = false;
8057
8237
  let unsubscribe = null;
@@ -8065,12 +8245,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8065
8245
  resolve({
8066
8246
  success: false,
8067
8247
  pending: enteredPool || void 0,
8248
+ stalled: enteredPool || void 0,
8068
8249
  extrinsicHash: enteredPool ? extrinsic.hash.toHex() : void 0,
8069
8250
  error: `Transaction timed out after ${timeoutMs}ms${enteredPool ? " while in the transaction pool" : ""}`
8070
8251
  });
8071
8252
  }
8072
8253
  }, timeoutMs);
8073
- extrinsic.signAndSend(keyPair, { tip }, (result) => {
8254
+ extrinsic.signAndSend(keyPair, nonce === void 0 ? { tip } : { tip, nonce }, (result) => {
8074
8255
  if (resolved) return;
8075
8256
  if (result.status.isFuture || result.status.isReady || result.status.isBroadcast || result.status.isRetracted) {
8076
8257
  enteredPool = true;
@@ -8078,16 +8259,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8078
8259
  if (result.dispatchError && (result.status.isInBlock || result.status.isFinalized)) {
8079
8260
  resolved = true;
8080
8261
  clearTimeout(timeoutId);
8081
- let errorMsg;
8082
- if (result.dispatchError.isModule) {
8083
- const decoded = this.api.registry.findMetaError(result.dispatchError.asModule);
8084
- errorMsg = `Dispatch error: ${decoded.section}::${decoded.name}`;
8085
- } else {
8086
- errorMsg = `Dispatch error: ${result.dispatchError.toString()}`;
8087
- }
8088
8262
  resolve({
8089
8263
  success: false,
8090
- error: errorMsg
8264
+ error: `Dispatch error: ${this.describeDispatchError(result.dispatchError)}`
8091
8265
  });
8092
8266
  } else if (result.status.isDropped || result.status.isInvalid || result.status.isUsurped || result.status.isFinalityTimeout) {
8093
8267
  resolved = true;
@@ -8105,21 +8279,19 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8105
8279
  if (interrupted) {
8106
8280
  const [indexCodec, dispatchError] = interrupted.event.data;
8107
8281
  if (Number(indexCodec.toString()) === 0) {
8108
- let errorMsg;
8109
- if (dispatchError?.isModule) {
8110
- const decoded = this.api.registry.findMetaError(dispatchError.asModule);
8111
- errorMsg = `Dispatch error: ${decoded.section}::${decoded.name}`;
8112
- } else {
8113
- errorMsg = `Dispatch error: batch interrupted (${dispatchError?.toString()})`;
8114
- }
8115
- resolve({ success: false, error: errorMsg });
8282
+ resolve({
8283
+ success: false,
8284
+ error: `Dispatch error: ${this.describeDispatchError(dispatchError)}`
8285
+ });
8116
8286
  return;
8117
8287
  }
8118
8288
  }
8119
8289
  resolve({
8120
8290
  success: true,
8121
8291
  blockHash: (result.status.isInBlock ? result.status.asInBlock : result.status.asFinalized).toHex(),
8122
- extrinsicHash: extrinsic.hash.toHex()
8292
+ extrinsicHash: extrinsic.hash.toHex(),
8293
+ // Carried so a batch caller can attribute each item's outcome.
8294
+ events: result.events
8123
8295
  });
8124
8296
  }
8125
8297
  }).then((unsub) => {
@@ -8146,8 +8318,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8146
8318
  */
8147
8319
  async submitBid(commitment, userOp) {
8148
8320
  try {
8149
- const extrinsic = this.api.tx.intentsCoprocessor.placeBid(commitment, userOp);
8150
- return await this.signAndSendExtrinsic(extrinsic);
8321
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.placeBid(commitment, userOp));
8151
8322
  } catch (error) {
8152
8323
  return {
8153
8324
  success: false,
@@ -8165,8 +8336,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8165
8336
  */
8166
8337
  async retractBid(commitment) {
8167
8338
  try {
8168
- const extrinsic = this.api.tx.intentsCoprocessor.retractBid(commitment);
8169
- return await this.signAndSendExtrinsic(extrinsic);
8339
+ return await this.signAndSendExtrinsic((api) => api.tx.intentsCoprocessor.retractBid(commitment));
8170
8340
  } catch (error) {
8171
8341
  return {
8172
8342
  success: false,
@@ -8196,11 +8366,12 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8196
8366
  */
8197
8367
  async submitBidWithRetraction(retractCommitment, bidCommitment, userOp) {
8198
8368
  try {
8199
- const batch = this.api.tx.utility.batch([
8200
- this.api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
8201
- this.api.tx.intentsCoprocessor.retractBid(retractCommitment)
8202
- ]);
8203
- return await this.signAndSendExtrinsic(batch);
8369
+ return await this.signAndSendExtrinsic(
8370
+ (api) => api.tx.utility.batch([
8371
+ api.tx.intentsCoprocessor.placeBid(bidCommitment, userOp),
8372
+ api.tx.intentsCoprocessor.retractBid(retractCommitment)
8373
+ ])
8374
+ );
8204
8375
  } catch (error) {
8205
8376
  return {
8206
8377
  success: false,
@@ -8208,6 +8379,98 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8208
8379
  };
8209
8380
  }
8210
8381
  }
8382
+ /**
8383
+ * Places every phantom bid of one interval in a single extrinsic, retracting each chain's
8384
+ * previous bid alongside it.
8385
+ *
8386
+ * The pallet registers one phantom order per configured chain in the same block, so this is the
8387
+ * whole interval's set. Submitting them one at a time costs a block per chain: submissions are
8388
+ * serialised on the account nonce and each waits for inclusion, so the last chain's bid is many
8389
+ * blocks behind the first, against a bid window measured in tens of blocks. Batched, every bid
8390
+ * lands in the same block.
8391
+ *
8392
+ * Uses `utility.force_batch`, not `utility.batch`. `batch` stops at the first failing call, so
8393
+ * one rejected bid — a closed window, a duplicate, an insufficient deposit — would silently
8394
+ * drop every bid after it. `force_batch` runs them all and reports each outcome. It needs no
8395
+ * special origin: any signed account may call it, exactly like `batch`.
8396
+ *
8397
+ * @param bids - The bids to place; an empty list is a no-op
8398
+ * @returns Per-bid outcomes, in the order given
8399
+ */
8400
+ async submitPhantomBids(bids) {
8401
+ if (bids.length === 0) return { bids: [] };
8402
+ const placeIndexByBid = [];
8403
+ let callCount = 0;
8404
+ for (const bid of bids) {
8405
+ placeIndexByBid.push(callCount);
8406
+ callCount += bid.retractCommitment ? 2 : 1;
8407
+ }
8408
+ const outcome = await this.signAndSendExtrinsic(
8409
+ (api) => api.tx.utility.forceBatch(
8410
+ bids.flatMap((bid) => {
8411
+ const calls = [api.tx.intentsCoprocessor.placeBid(bid.commitment, bid.userOp)];
8412
+ if (bid.retractCommitment) {
8413
+ calls.push(api.tx.intentsCoprocessor.retractBid(bid.retractCommitment));
8414
+ }
8415
+ return calls;
8416
+ })
8417
+ )
8418
+ );
8419
+ if (!outcome.success) {
8420
+ return {
8421
+ bids: bids.map((bid) => ({ commitment: bid.commitment, success: false, error: outcome.error })),
8422
+ pending: outcome.pending,
8423
+ extrinsicHash: outcome.extrinsicHash,
8424
+ error: outcome.error
8425
+ };
8426
+ }
8427
+ const items = this.readForceBatchItems(outcome.events ?? [], callCount);
8428
+ return {
8429
+ bids: bids.map((bid, index) => {
8430
+ const error = items.errors[placeIndexByBid[index]];
8431
+ return { commitment: bid.commitment, success: !error, error };
8432
+ }),
8433
+ blockHash: outcome.blockHash,
8434
+ extrinsicHash: outcome.extrinsicHash,
8435
+ error: items.error
8436
+ };
8437
+ }
8438
+ /**
8439
+ * Reads one outcome per call out of a force_batch's events.
8440
+ *
8441
+ * `ItemFailed` carries no index — pallet-utility emits exactly one `ItemCompleted` or
8442
+ * `ItemFailed` per call, in call order, so the k-th item event belongs to call k.
8443
+ *
8444
+ * A count that does not match the calls submitted means the events are not the ones assumed
8445
+ * here, and every attribution after the discrepancy would be off by one. The bids are then
8446
+ * reported as placed: a bid wrongly recorded as landed is retracted next interval and the
8447
+ * retraction harmlessly fails with `BidNotFound`, whereas one wrongly recorded as failed is
8448
+ * never retracted at all and leaves its deposit reserved.
8449
+ */
8450
+ readForceBatchItems(events, callCount) {
8451
+ const errors = [];
8452
+ for (const { event } of events) {
8453
+ if (event.section !== "utility") continue;
8454
+ if (event.method === "ItemCompleted") errors.push(void 0);
8455
+ else if (event.method === "ItemFailed") errors.push(this.describeDispatchError(event.data[0]));
8456
+ }
8457
+ if (errors.length !== callCount) {
8458
+ return {
8459
+ errors: new Array(callCount).fill(void 0),
8460
+ error: `force_batch reported ${errors.length} item events for ${callCount} calls; outcomes not attributed`
8461
+ };
8462
+ }
8463
+ return { errors };
8464
+ }
8465
+ /** Renders a DispatchError as `pallet::Error`, falling back to its raw form. */
8466
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8467
+ describeDispatchError(dispatchError) {
8468
+ if (dispatchError?.isModule) {
8469
+ const decoded = this.api.registry.findMetaError(dispatchError.asModule);
8470
+ return `${decoded.section}::${decoded.name}`;
8471
+ }
8472
+ return dispatchError?.toString() ?? "unknown dispatch error";
8473
+ }
8211
8474
  /**
8212
8475
  * Fetches all bid storage entries for a given order commitment.
8213
8476
  * Returns the on-chain data only (filler addresses and deposits).
@@ -8216,7 +8479,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8216
8479
  * @returns Array of BidStorageEntry objects
8217
8480
  */
8218
8481
  async getBidStorageEntries(commitment) {
8219
- const entries = await this.api.query.intentsCoprocessor.bids.entries(commitment);
8482
+ const api = await this.http();
8483
+ const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
8220
8484
  return entries.map(([storageKey, depositValue]) => ({
8221
8485
  commitment,
8222
8486
  filler: storageKey.args[1].toString(),
@@ -8246,9 +8510,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8246
8510
  * Single round-trip but does not include deposit amounts.
8247
8511
  */
8248
8512
  async getBidsViaRpc(commitment) {
8249
- const result = await this.api._rpcCore.provider.send("intents_getBidsForOrder", [
8250
- commitment
8251
- ]);
8513
+ const api = await this.http();
8514
+ const result = await api._rpcCore.provider.send("intents_getBidsForOrder", [commitment]);
8252
8515
  return result.map((entry) => {
8253
8516
  const userOp = decodeUserOpScale(entry.user_op);
8254
8517
  const filler = new Keyring({ type: "sr25519" }).encodeAddress(hexToU8a(entry.filler));
@@ -8260,7 +8523,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8260
8523
  * Slower but works on all nodes and includes deposit amounts.
8261
8524
  */
8262
8525
  async getBidsViaStorage(commitment) {
8263
- const entries = await this.api.query.intentsCoprocessor.bids.entries(commitment);
8526
+ const api = await this.http();
8527
+ const entries = await api.query.intentsCoprocessor.bids.entries(commitment);
8264
8528
  if (entries.length === 0) return [];
8265
8529
  const bidPromises = entries.map(async ([storageKey, depositValue]) => {
8266
8530
  try {
@@ -8268,7 +8532,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8268
8532
  const deposit = BigInt(depositValue.toString());
8269
8533
  const offchainKey = this.buildOffchainBidKey(commitment, filler);
8270
8534
  const offchainKeyHex = u8aToHex(offchainKey);
8271
- const offchainResult = await this.api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
8535
+ const offchainResult = await api.rpc.offchain.localStorageGet("PERSISTENT", offchainKeyHex);
8272
8536
  if (!offchainResult || offchainResult.isNone) return null;
8273
8537
  const bidData = offchainResult.unwrap().toHex();
8274
8538
  const decoded = this.decodeBid(bidData);
@@ -8302,7 +8566,8 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8302
8566
  */
8303
8567
  async fetchPhantomOrder(commitment) {
8304
8568
  const key = u8aConcat(OFFCHAIN_PHANTOM_PREFIX, hexToU8a(commitment));
8305
- const result = await this.api.rpc.offchain.localStorageGet("PERSISTENT", u8aToHex(key));
8569
+ const api = await this.http();
8570
+ const result = await api.rpc.offchain.localStorageGet("PERSISTENT", u8aToHex(key));
8306
8571
  if (!result || result.isNone) return null;
8307
8572
  const rawHex = result.unwrap().toHex();
8308
8573
  if (rawHex === "0x" || rawHex === "0x00") return null;
@@ -8346,8 +8611,9 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8346
8611
  * Reads the PhantomOrderRegistered events emitted in a single block.
8347
8612
  */
8348
8613
  async getPhantomOrdersInBlock(blockNumber) {
8349
- const blockHash = await this.api.rpc.chain.getBlockHash(blockNumber);
8350
- const apiAt = await this.api.at(blockHash);
8614
+ const api = await this.http();
8615
+ const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
8616
+ const apiAt = await api.at(blockHash);
8351
8617
  const records = await apiAt.query.system.events();
8352
8618
  const orders = [];
8353
8619
  for (const { event } of records) {
@@ -8368,7 +8634,13 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8368
8634
  return orders;
8369
8635
  }
8370
8636
  /**
8371
- * Polls for newly registered phantom orders, invoking the callback once per order.
8637
+ * Polls for newly registered phantom orders, invoking the callback once per block that carries
8638
+ * any, with all of that block's orders.
8639
+ *
8640
+ * Per block rather than per order because that is how the pallet writes them: one order per
8641
+ * configured chain, all registered in the same `on_initialize`. Delivering them together lets a
8642
+ * caller bid on the whole interval in one extrinsic (see {@link submitPhantomBids}) instead of
8643
+ * one per chain.
8372
8644
  *
8373
8645
  * Each tick reads the current head and scans every block between the last one processed and that
8374
8646
  * head, so the block cursor — not the connection — determines what has been seen. This replaced a
@@ -8381,10 +8653,16 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8381
8653
  * cannot drop them, because the cursor only advances past a block whose events were actually
8382
8654
  * read. Recovery replays the backlog.
8383
8655
  *
8656
+ * Every read here goes over HTTP, never the websocket. Polling is a sequence of independent
8657
+ * one-shot requests with no state to lose between them, which is exactly what a stateless
8658
+ * transport does well: a request either answers or fails loudly on this tick, instead of a
8659
+ * socket that looks alive while delivering nothing. It also means a websocket outage does not
8660
+ * pause phantom bidding at all — the two transports fail independently.
8661
+ *
8384
8662
  * Returns a function that stops polling.
8385
8663
  */
8386
8664
  pollPhantomOrders(callback, options = {}) {
8387
- const { intervalMs = 6e3, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8665
+ const { intervalMs, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
8388
8666
  let cursor = null;
8389
8667
  let inFlight = false;
8390
8668
  let stopped = false;
@@ -8392,7 +8670,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8392
8670
  if (inFlight || stopped) return;
8393
8671
  inFlight = true;
8394
8672
  try {
8395
- const head = (await this.api.rpc.chain.getHeader()).number.toNumber();
8673
+ const head = (await (await this.http()).rpc.chain.getHeader()).number.toNumber();
8396
8674
  if (cursor === null) {
8397
8675
  cursor = Math.max(head - 1 - lookbackBlocks, -1);
8398
8676
  }
@@ -8401,7 +8679,7 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8401
8679
  for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
8402
8680
  if (stopped) return;
8403
8681
  const orders = await this.getPhantomOrdersInBlock(blockNumber);
8404
- for (const order of orders) callback(order);
8682
+ if (orders.length > 0) callback(orders);
8405
8683
  cursor = blockNumber;
8406
8684
  }
8407
8685
  } catch (err) {
@@ -8411,12 +8689,31 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8411
8689
  }
8412
8690
  };
8413
8691
  void tick();
8414
- const timer = setInterval(() => void tick(), intervalMs);
8692
+ let timer = null;
8693
+ const startTimer = (ms) => {
8694
+ if (stopped) return;
8695
+ timer = setInterval(() => void tick(), ms);
8696
+ };
8697
+ if (intervalMs !== void 0) startTimer(intervalMs);
8698
+ else void this.phantomPollIntervalMs().then(startTimer);
8415
8699
  return () => {
8416
8700
  stopped = true;
8417
- clearInterval(timer);
8701
+ if (timer) clearInterval(timer);
8418
8702
  };
8419
8703
  }
8704
+ /**
8705
+ * The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
8706
+ * everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
8707
+ * since an unreachable node is the poll's problem to report, not the cadence lookup's.
8708
+ */
8709
+ async phantomPollIntervalMs() {
8710
+ try {
8711
+ const specName = (await this.http()).runtimeVersion.specName.toString();
8712
+ return specName === "gargantua" ? GARGANTUA_PHANTOM_POLL_INTERVAL_MS : PHANTOM_POLL_INTERVAL_MS;
8713
+ } catch {
8714
+ return PHANTOM_POLL_INTERVAL_MS;
8715
+ }
8716
+ }
8420
8717
  };
8421
8718
  var TronChain = class _TronChain {
8422
8719
  constructor(params, evm) {
@@ -11791,52 +12088,85 @@ query LatestPhantomOrderPriceSnapshot($tokenA: String!, $tokenB: String!) {
11791
12088
  }
11792
12089
  }
11793
12090
  }`;
11794
- var LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT = `
11795
- query LatestPhantomOrderLiquiditySnapshot($tokenA: String!, $tokenB: String!) {
11796
- phantomOrderPriceSnapshots(
12091
+ var AVAILABLE_LIQUIDITY = `
12092
+ query AvailableLiquidity(
12093
+ $poolId: String!
12094
+ $sourceChain: String!
12095
+ $destinationChain: String!
12096
+ $direction: String!
12097
+ ) {
12098
+ poolChainLiquidities(
11797
12099
  filter: {
11798
12100
  and: [
11799
- { tokenA: { equalTo: $tokenA } }
11800
- { tokenB: { equalTo: $tokenB } }
12101
+ { poolId: { equalToInsensitive: $poolId } }
12102
+ { chain: { equalTo: $destinationChain } }
12103
+ { direction: { equalTo: $direction } }
11801
12104
  ]
11802
12105
  }
11803
- orderBy: SNAPSHOT_TIME_DESC
11804
12106
  first: 1
11805
12107
  ) {
11806
12108
  nodes {
11807
- commitment
11808
- tokenA
11809
- tokenB
11810
- snapshotTime
12109
+ depth
12110
+ bidCount
12111
+ unrestrictedDepth
12112
+ unrestrictedBidCount
12113
+ lastUpdatedAt
12114
+ }
12115
+ }
12116
+ poolRoutes(
12117
+ filter: {
12118
+ and: [
12119
+ { poolId: { equalToInsensitive: $poolId } }
12120
+ { sourceChain: { equalTo: $sourceChain } }
12121
+ { chain: { equalTo: $destinationChain } }
12122
+ { direction: { equalTo: $direction } }
12123
+ ]
12124
+ }
12125
+ first: 1
12126
+ ) {
12127
+ nodes {
12128
+ depth
12129
+ bidCount
12130
+ lastUpdatedAt
11811
12131
  }
11812
12132
  }
11813
12133
  }`;
11814
- var LIQUIDITY_PROVIDER_BALANCES = `
11815
- query LiquidityProviderBalanceAggregates($commitment: String!, $tokenAddress: String!) {
11816
- liquidityProviderBalances(
12134
+ var BUY_AND_SELL_RATES = `
12135
+ query BuyAndSellRates(
12136
+ $poolId: String!
12137
+ $directChain: String!
12138
+ $directDirection: String!
12139
+ $reverseChain: String!
12140
+ $reverseDirection: String!
12141
+ ) {
12142
+ direct: poolChainLiquidities(
11817
12143
  filter: {
11818
12144
  and: [
11819
- { commitment: { equalTo: $commitment } }
11820
- { tokenAddress: { equalTo: $tokenAddress } }
12145
+ { poolId: { equalToInsensitive: $poolId } }
12146
+ { chain: { equalTo: $directChain } }
12147
+ { direction: { equalTo: $directDirection } }
11821
12148
  ]
11822
12149
  }
12150
+ first: 1
11823
12151
  ) {
11824
- aggregates {
11825
- sum {
11826
- balance
11827
- }
11828
- distinctCount {
11829
- providerId
11830
- }
12152
+ nodes {
12153
+ rate
12154
+ lastUpdatedAt
11831
12155
  }
11832
- groupedAggregates(groupBy: [CHAIN, TOKEN_ADDRESS]) {
11833
- keys
11834
- sum {
11835
- balance
11836
- }
11837
- distinctCount {
11838
- providerId
11839
- }
12156
+ }
12157
+ reverse: poolChainLiquidities(
12158
+ filter: {
12159
+ and: [
12160
+ { poolId: { equalToInsensitive: $poolId } }
12161
+ { chain: { equalTo: $reverseChain } }
12162
+ { direction: { equalTo: $reverseDirection } }
12163
+ ]
12164
+ }
12165
+ first: 1
12166
+ ) {
12167
+ nodes {
12168
+ rate
12169
+ lastUpdatedAt
11840
12170
  }
11841
12171
  }
11842
12172
  }`;
@@ -15228,6 +15558,12 @@ var CryptoUtils = class _CryptoUtils {
15228
15558
  * signing infrastructure (hardware wallets, MPC/TEE policy engines) instead
15229
15559
  * of an opaque 32-byte digest.
15230
15560
  *
15561
+ * The payload must be a standard self-describing `eth_signTypedData_v4`
15562
+ * payload — `EIP712Domain` listed in `types`, `chainId` as a JSON number —
15563
+ * because some signing backends (e.g. MPC Vault) hash it server-side from
15564
+ * the JSON rather than locally via viem. viem ignores both details when
15565
+ * hashing, so the digest is unchanged for local signers.
15566
+ *
15231
15567
  * @param userOp - The packed UserOperation to sign (signature field ignored).
15232
15568
  * @param entryPoint - Address of the EntryPoint v0.8 contract.
15233
15569
  * @param chainId - Chain ID of the network on which the operation will execute.
@@ -15238,10 +15574,22 @@ var CryptoUtils = class _CryptoUtils {
15238
15574
  domain: {
15239
15575
  name: "ERC4337",
15240
15576
  version: "1",
15241
- chainId,
15577
+ // Runtime number so JSON.stringify emits a canonical v4 numeric chainId for
15578
+ // server-side hashers; viem's uint256 type mapping wants bigint but its
15579
+ // runtime accepts numbers, hence the cast.
15580
+ chainId: Number(chainId),
15242
15581
  verifyingContract: entryPoint
15243
15582
  },
15583
+ // `as const`: viem derives the domain's TYPE from `types.EIP712Domain`, so the
15584
+ // entries must stay string literals — widened `string` fields make viem's
15585
+ // typed-data generics reject the payload at every call site.
15244
15586
  types: {
15587
+ EIP712Domain: [
15588
+ { name: "name", type: "string" },
15589
+ { name: "version", type: "string" },
15590
+ { name: "chainId", type: "uint256" },
15591
+ { name: "verifyingContract", type: "address" }
15592
+ ],
15245
15593
  PackedUserOperation: [
15246
15594
  { name: "sender", type: "address" },
15247
15595
  { name: "nonce", type: "uint256" },
@@ -17972,178 +18320,191 @@ var OrderStatusChecker = class {
17972
18320
  return true;
17973
18321
  }
17974
18322
  };
17975
- var COMMITMENT_PATTERN = /^0x[0-9a-f]{64}$/i;
18323
+
18324
+ // src/protocols/intents/liquidity-pool.ts
18325
+ function sortPoolSymbols(symbolA, symbolB) {
18326
+ return symbolA.toLowerCase() <= symbolB.toLowerCase() ? [symbolA, symbolB] : [symbolB, symbolA];
18327
+ }
18328
+ function poolSlug(symbolA, symbolB) {
18329
+ return sortPoolSymbols(symbolA, symbolB).join("-");
18330
+ }
18331
+ function resolveLiquidityPool(symbolA, symbolB) {
18332
+ const [token0Symbol, token1Symbol] = sortPoolSymbols(symbolA, symbolB);
18333
+ return {
18334
+ poolId: `${token0Symbol}-${token1Symbol}`,
18335
+ token0Symbol,
18336
+ token1Symbol
18337
+ };
18338
+ }
18339
+
18340
+ // src/protocols/intents/LiquidityEngine.ts
18341
+ var INDEXER_FIXED_POINT_DECIMALS = 18;
18342
+ var POOL_RATE_SCALE = 10n ** 18n;
18343
+ var SELL = "SELL";
18344
+ var BUY = "BUY";
18345
+ var USD_STABLE_SYMBOLS = /* @__PURE__ */ new Set(["USDC", "USDT"]);
17976
18346
  var LiquidityEngine = class {
17977
- /**
17978
- * @param queryClient - Nexus GraphQL client attached to the gateway.
17979
- * @param chainConfigService - Resolves token decimals for formatted results.
17980
- */
17981
- constructor(queryClient, chainConfigService) {
18347
+ constructor(queryClient) {
17982
18348
  this.queryClient = queryClient;
17983
- this.chainConfigService = chainConfigService;
17984
18349
  }
17985
18350
  queryClient;
17986
- chainConfigService;
17987
18351
  /**
17988
- * Retrieves the newest directional Phantom snapshot for a pair and its
17989
- * indexed output-token liquidity.
18352
+ * Returns liquidity reachable from one source chain on one destination.
17990
18353
  *
17991
- * Nexus filters balances to the canonical output token, then aggregates them
17992
- * overall and by chain. The returned amounts are decimal strings: the total
17993
- * uses the canonical Base output-token decimals, while each chain group uses
17994
- * that chain's token decimals. They describe `snapshotTime`, not live
17995
- * reservations or fill guarantees.
18354
+ * The caller resolves chain-specific token addresses through chain
18355
+ * configuration; this layer only maps those configured symbols onto the
18356
+ * indexer's canonical pool and route fields.
17996
18357
  *
17997
- * @param params - Canonical Phantom-market input and output token addresses.
17998
- * @returns The latest snapshot, or `undefined` if Nexus has no snapshot for
17999
- * the directional pair.
18000
- * @throws {InvalidAvailableLiquiditySnapshotError} If Nexus returns malformed
18001
- * or internally inconsistent snapshot data.
18002
- */
18003
- async getAvailableLiquiditySnapshot(params) {
18004
- const tokenIn = normalizeEvmAddress(params.tokenIn, "tokenIn");
18005
- const tokenOut = normalizeEvmAddress(params.tokenOut, "tokenOut");
18006
- const response = await this.queryClient.request(
18007
- LATEST_PHANTOM_ORDER_LIQUIDITY_SNAPSHOT,
18008
- { tokenA: tokenIn, tokenB: tokenOut }
18009
- );
18010
- const node = response?.phantomOrderPriceSnapshots?.nodes?.[0];
18011
- if (!node) return;
18012
- const commitment = node.commitment.toLowerCase();
18013
- if (!COMMITMENT_PATTERN.test(commitment)) {
18014
- throw new InvalidAvailableLiquiditySnapshotError(commitment || "<missing>", "commitment is not bytes32 hex");
18015
- }
18016
- if (node.tokenA.toLowerCase() !== tokenIn || node.tokenB.toLowerCase() !== tokenOut) {
18017
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshot token pair does not match the query");
18018
- }
18019
- const snapshotTime = new Date(dateStringtoTimestamp(node.snapshotTime));
18020
- if (Number.isNaN(snapshotTime.getTime())) {
18021
- throw new InvalidAvailableLiquiditySnapshotError(commitment, "snapshotTime is invalid");
18358
+ * Destination, unrestricted, and explicit-route capacity are returned as
18359
+ * separate values so callers can apply their own source-chain policy.
18360
+ *
18361
+ * @returns `undefined` only when the indexer has not published a destination
18362
+ * pool sample yet.
18363
+ */
18364
+ async getAvailableLiquidity(params) {
18365
+ const pool = resolveLiquidityPool(params.source.symbol, params.destination.symbol);
18366
+ const direction = params.source.symbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18367
+ const variables = {
18368
+ poolId: pool.poolId,
18369
+ sourceChain: params.source.chain,
18370
+ destinationChain: params.destination.chain,
18371
+ direction
18372
+ };
18373
+ const response = await this.queryClient.request(AVAILABLE_LIQUIDITY, variables);
18374
+ if (!response?.poolChainLiquidities?.nodes || !response?.poolRoutes?.nodes) {
18375
+ throw new InvalidLiquidityIndexerResponseError("liquidity connections are missing");
18022
18376
  }
18023
- const { totalLiquidity, providerCount, liquidityByChain } = await this.querySnapshotLiquidityAggregates({
18024
- commitment,
18025
- tokenAddress: tokenOut
18026
- });
18377
+ const chainLiquidity = response.poolChainLiquidities.nodes[0];
18378
+ if (!chainLiquidity) return void 0;
18379
+ const route = response.poolRoutes.nodes[0];
18027
18380
  return {
18028
- totalLiquidity: this.formatLiquidity(totalLiquidity, "EVM-8453" /* BASE_MAINNET */, tokenOut, commitment),
18029
- providerCount,
18030
- tokenAddress: tokenOut,
18031
- snapshotTime,
18032
- liquidityByChain: liquidityByChain.map((group) => ({
18033
- ...group,
18034
- totalLiquidity: this.formatLiquidity(group.totalLiquidity, group.chain, group.tokenAddress, commitment)
18035
- }))
18381
+ sourceChain: params.source.chain,
18382
+ destinationChain: params.destination.chain,
18383
+ tokenAddress: normalizeEvmAddress(params.destination.address, "destination token"),
18384
+ updatedAt: readIndexerDate(chainLiquidity.lastUpdatedAt, "destination lastUpdatedAt"),
18385
+ destination: readLiquiditySlice(chainLiquidity.depth, chainLiquidity.bidCount, "destination"),
18386
+ unrestricted: readLiquiditySlice(
18387
+ chainLiquidity.unrestrictedDepth,
18388
+ chainLiquidity.unrestrictedBidCount,
18389
+ "unrestricted"
18390
+ ),
18391
+ explicitRoute: route ? {
18392
+ ...readLiquiditySlice(route.depth, route.bidCount, "explicit route"),
18393
+ updatedAt: readIndexerDate(route.lastUpdatedAt, "route lastUpdatedAt")
18394
+ } : null
18036
18395
  };
18037
18396
  }
18038
18397
  /**
18039
- * Requests server-side sums and distinct provider counts for one immutable
18040
- * snapshot/output-token pair, including chain-level aggregate groups.
18398
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
18399
+ * per one base token.
18041
18400
  *
18042
- * `commitment` uniquely identifies the selected snapshot
18043
- */
18044
- async querySnapshotLiquidityAggregates(params) {
18045
- const response = await this.queryClient.request(
18046
- LIQUIDITY_PROVIDER_BALANCES,
18047
- { commitment: params.commitment, tokenAddress: params.tokenAddress }
18048
- );
18049
- const connection = response?.liquidityProviderBalances;
18050
- const aggregates = connection?.aggregates;
18051
- if (!connection || !aggregates) {
18052
- throw new InvalidAvailableLiquiditySnapshotError(
18053
- params.commitment,
18054
- "liquidityProviderBalances aggregates are missing"
18055
- );
18056
- }
18057
- const totalLiquidity = parseSnapshotBigInt(aggregates.sum.balance ?? "0", params.commitment, "total balance");
18058
- const providerCount = parseProviderCount(aggregates.distinctCount.providerId, params.commitment, "total");
18059
- const liquidityByChain = connection.groupedAggregates.map((group, index) => {
18060
- const [chain, tokenAddress] = group.keys;
18061
- if (!chain?.trim() || !tokenAddress) {
18062
- throw new InvalidAvailableLiquiditySnapshotError(
18063
- params.commitment,
18064
- `liquidity group ${index} has invalid keys`
18065
- );
18066
- }
18067
- const normalizedTokenAddress = normalizeIndexedLiquidityAddress(
18068
- tokenAddress,
18069
- params.commitment,
18070
- `liquidity group ${index} tokenAddress`
18071
- );
18072
- if (normalizedTokenAddress !== params.tokenAddress) {
18073
- throw new InvalidAvailableLiquiditySnapshotError(
18074
- params.commitment,
18075
- `liquidity group ${index} tokenAddress does not match the snapshot output token`
18076
- );
18077
- }
18078
- return {
18079
- chain: chain.trim(),
18080
- tokenAddress: normalizedTokenAddress,
18081
- totalLiquidity: parseSnapshotBigInt(
18082
- group.sum.balance ?? "0",
18083
- params.commitment,
18084
- `liquidity group ${index} balance`
18085
- ),
18086
- providerCount: parseProviderCount(
18087
- group.distinctCount.providerId,
18088
- params.commitment,
18089
- `liquidity group ${index}`
18090
- )
18091
- };
18401
+ * The requested direction is read on the destination chain; its reverse is
18402
+ * read on the source chain. This mirrors where each direction's output token
18403
+ * must be delivered for a cross-chain trade.
18404
+ */
18405
+ async getBuyAndSellRates(params) {
18406
+ const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
18407
+ const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18408
+ const reverseDirection = directDirection === SELL ? BUY : SELL;
18409
+ const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
18410
+ poolId: pool.poolId,
18411
+ directChain: params.destinationChain,
18412
+ directDirection,
18413
+ reverseChain: params.sourceChain,
18414
+ reverseDirection
18092
18415
  });
18093
- const groupedTotal = liquidityByChain.reduce((sum, group) => sum + group.totalLiquidity, 0n);
18094
- if (groupedTotal !== totalLiquidity) {
18095
- throw new InvalidAvailableLiquiditySnapshotError(
18096
- params.commitment,
18097
- "grouped liquidity does not match the total liquidity"
18098
- );
18099
- }
18100
- return { totalLiquidity, providerCount, liquidityByChain };
18416
+ if (!response?.direct?.nodes || !response?.reverse?.nodes) {
18417
+ throw new InvalidLiquidityIndexerResponseError("rate connections are missing");
18418
+ }
18419
+ const direct = readIndexedRate(response.direct.nodes[0], "direct rate");
18420
+ const reverse = readIndexedRate(response.reverse.nodes[0], "reverse rate");
18421
+ if (!direct && !reverse) return void 0;
18422
+ const quoteTokenSymbol = resolveQuoteTokenSymbol(
18423
+ params.tokenInSymbol,
18424
+ params.tokenOutSymbol,
18425
+ direct?.scaledRate,
18426
+ reverse?.scaledRate
18427
+ );
18428
+ const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
18429
+ const buy = quoteIsTokenOut ? direct : reverse;
18430
+ const sell = quoteIsTokenOut ? reverse : direct;
18431
+ return {
18432
+ baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
18433
+ quoteTokenSymbol,
18434
+ sourceChain: params.sourceChain,
18435
+ destinationChain: params.destinationChain,
18436
+ buyRate: buy ? formatUnits(buy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
18437
+ sellRate: sell ? formatUnits(reciprocalRate(sell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
18438
+ buyRateUpdatedAt: buy?.updatedAt ?? null,
18439
+ sellRateUpdatedAt: sell?.updatedAt ?? null
18440
+ };
18101
18441
  }
18102
- /** Formats a raw amount using the configured decimals for its chain/token. */
18103
- formatLiquidity(amount, chain, tokenAddress, commitment) {
18104
- const decimals = this.chainConfigService.getAssetMetadataByAddress(chain, tokenAddress)?.decimals;
18105
- if (decimals === void 0) {
18106
- throw new InvalidAvailableLiquiditySnapshotError(
18107
- commitment,
18108
- `token decimals are not configured for ${tokenAddress} on ${chain}`
18109
- );
18110
- }
18111
- return formatUnits(amount, decimals);
18442
+ };
18443
+ var InvalidLiquidityIndexerResponseError = class extends Error {
18444
+ constructor(reason) {
18445
+ super(`Invalid liquidity indexer response: ${reason}`);
18446
+ this.name = "InvalidLiquidityIndexerResponseError";
18112
18447
  }
18113
18448
  };
18114
- var InvalidAvailableLiquiditySnapshotError = class extends Error {
18115
- /** Creates an error that identifies the invalid snapshot and field/reason. */
18116
- constructor(commitment, reason) {
18117
- super(`Invalid available-liquidity snapshot ${commitment}: ${reason}`);
18118
- this.name = "InvalidAvailableLiquiditySnapshotError";
18449
+ var UnsupportedLiquidityAssetError = class extends Error {
18450
+ constructor(chain, asset) {
18451
+ super(`No configured liquidity asset found for ${asset} on ${chain}`);
18452
+ this.name = "UnsupportedLiquidityAssetError";
18119
18453
  }
18120
18454
  };
18121
- function parseSnapshotBigInt(value, commitment, field) {
18455
+ var UnsupportedLiquidityChainError = class extends Error {
18456
+ constructor(chainId) {
18457
+ super(`No configured liquidity chain found for chain ID ${chainId}`);
18458
+ this.name = "UnsupportedLiquidityChainError";
18459
+ }
18460
+ };
18461
+ function readLiquiditySlice(depth, providerCount, label) {
18462
+ if (!Number.isSafeInteger(providerCount) || providerCount < 0) {
18463
+ throw new InvalidLiquidityIndexerResponseError(`${label} provider count is invalid`);
18464
+ }
18465
+ return { totalLiquidity: formatIndexerAmount(depth, `${label} depth`), providerCount };
18466
+ }
18467
+ function formatIndexerAmount(value, label) {
18122
18468
  try {
18123
18469
  const amount = BigInt(value);
18124
- if (amount < 0n) {
18125
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} cannot be negative`);
18126
- }
18127
- return amount;
18128
- } catch (error) {
18129
- if (error instanceof InvalidAvailableLiquiditySnapshotError) throw error;
18130
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not an integer`);
18470
+ if (amount < 0n) throw new Error();
18471
+ return formatUnits(amount, INDEXER_FIXED_POINT_DECIMALS);
18472
+ } catch {
18473
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a non-negative integer`);
18131
18474
  }
18132
18475
  }
18133
- function parseProviderCount(value, commitment, field) {
18134
- const count = Number(value);
18135
- if (!Number.isSafeInteger(count) || count < 0) {
18136
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} provider count is invalid`);
18137
- }
18138
- return count;
18476
+ function readIndexerDate(value, label) {
18477
+ const date = new Date(dateStringtoTimestamp(value));
18478
+ if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
18479
+ return date;
18139
18480
  }
18140
- function normalizeIndexedLiquidityAddress(address, commitment, field) {
18481
+ function readIndexedRate(node, label) {
18482
+ if (!node) return void 0;
18141
18483
  try {
18142
- return normalizeEvmAddress(address, field);
18143
- } catch {
18144
- throw new InvalidAvailableLiquiditySnapshotError(commitment, `${field} is not a valid EVM address`);
18484
+ const scaledRate = BigInt(node.rate);
18485
+ if (scaledRate <= 0n) throw new Error();
18486
+ return {
18487
+ scaledRate,
18488
+ updatedAt: readIndexerDate(node.lastUpdatedAt, `${label} lastUpdatedAt`)
18489
+ };
18490
+ } catch (error) {
18491
+ if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
18492
+ throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
18145
18493
  }
18146
18494
  }
18495
+ function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
18496
+ const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
18497
+ const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
18498
+ if (inputIsUsdStable !== outputIsUsdStable) return inputIsUsdStable ? tokenOutSymbol : tokenInSymbol;
18499
+ if (directRate !== void 0) return directRate >= POOL_RATE_SCALE ? tokenOutSymbol : tokenInSymbol;
18500
+ if (reverseRate !== void 0) return reverseRate >= POOL_RATE_SCALE ? tokenInSymbol : tokenOutSymbol;
18501
+ throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
18502
+ }
18503
+ function reciprocalRate(rate, label) {
18504
+ const reciprocal = POOL_RATE_SCALE * POOL_RATE_SCALE / rate;
18505
+ if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
18506
+ return reciprocal;
18507
+ }
18147
18508
 
18148
18509
  // src/protocols/intents/quote/types.ts
18149
18510
  var UnsupportedIntentQuoteStrategyError = class extends Error {
@@ -18568,8 +18929,6 @@ var IntentGateway = class _IntentGateway {
18568
18929
  gasEstimator;
18569
18930
  /** Quote strategies for pricing orders before placement, keyed by strategy name. */
18570
18931
  quoteStrategies;
18571
- /** Resolves order tokens to canonical Phantom snapshot market pairs. */
18572
- phantomSnapshotPairResolver;
18573
18932
  /**
18574
18933
  * Private constructor — use {@link IntentGateway.create} instead.
18575
18934
  *
@@ -18613,7 +18972,6 @@ var IntentGateway = class _IntentGateway {
18613
18972
  this.bidManager = bidManager;
18614
18973
  this.gasEstimator = gasEstimator;
18615
18974
  this._crypto = crypto;
18616
- this.phantomSnapshotPairResolver = new PhantomSnapshotPairResolver(dest.configService);
18617
18975
  this.quoteStrategies = {
18618
18976
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18619
18977
  dest.configService,
@@ -18693,33 +19051,62 @@ var IntentGateway = class _IntentGateway {
18693
19051
  return handler.quote({ ...params, strategy }, source, destination);
18694
19052
  }
18695
19053
  /**
18696
- * Returns the output-token liquidity measured in the latest directional
18697
- * Phantom snapshot for this gateway's source and destination.
19054
+ * Returns indexed destination liquidity and its source-routing slices.
18698
19055
  *
18699
- * Pair resolution uses the same canonical Base market as {@link quoteIntent}.
18700
- * The snapshot itself determines the output token and chain to aggregate. The
18701
- * amount is in the token's smallest unit and reflects the indexer's
18702
- * `snapshotTime`; it is not a live reservation or fill guarantee.
19056
+ * Destination, unrestricted, and explicit-route capacity come exclusively
19057
+ * from the indexer's pair-centric liquidity entities. The SDK does not decide
19058
+ * whether unrestricted bidders cover the source chain. Amounts reflect the
19059
+ * latest rolling sample; they are not reservations or fill guarantees.
18703
19060
  *
18704
19061
  * Requires a prior call to {@link withQueryClient}.
18705
19062
  */
18706
19063
  async queryAvailableLiquidity(params) {
18707
19064
  const { queryClient } = this.requireIndexer();
18708
- const sourceStateMachineId = this.source.config.stateMachineId;
18709
- const destinationStateMachineId = this.dest.config.stateMachineId;
18710
- const pair = this.phantomSnapshotPairResolver.resolve(params, sourceStateMachineId, destinationStateMachineId);
18711
- if (!pair) {
18712
- throw new UnsupportedIntentQuotePairError({
18713
- source: sourceStateMachineId,
18714
- destination: destinationStateMachineId,
18715
- tokenIn: params.tokenIn,
18716
- tokenOut: params.tokenOut,
18717
- quoteSource: "Phantom snapshot pair"
18718
- });
18719
- }
18720
- return new LiquidityEngine(queryClient, this.dest.configService).getAvailableLiquiditySnapshot({
18721
- tokenIn: pair.tokenA,
18722
- tokenOut: pair.tokenB
19065
+ const sourceStateMachineId = getConfigByStateMachineId(this.source.config.stateMachineId)?.stateMachineId;
19066
+ const destinationStateMachineId = getConfigByStateMachineId(this.dest.config.stateMachineId)?.stateMachineId;
19067
+ if (!sourceStateMachineId) throw new UnsupportedLiquidityChainError(this.source.config.stateMachineId);
19068
+ if (!destinationStateMachineId) throw new UnsupportedLiquidityChainError(this.dest.config.stateMachineId);
19069
+ const sourceToken = this.source.configService.getAssetMetadataByAddress(sourceStateMachineId, params.tokenIn);
19070
+ const destinationToken = this.dest.configService.getAssetMetadataByAddress(
19071
+ destinationStateMachineId,
19072
+ params.tokenOut
19073
+ );
19074
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceStateMachineId, params.tokenIn);
19075
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationStateMachineId, params.tokenOut);
19076
+ return new LiquidityEngine(queryClient).getAvailableLiquidity({
19077
+ source: {
19078
+ chain: sourceStateMachineId,
19079
+ ...sourceToken
19080
+ },
19081
+ destination: {
19082
+ chain: destinationStateMachineId,
19083
+ ...destinationToken
19084
+ }
19085
+ });
19086
+ }
19087
+ /**
19088
+ * Returns chain-specific buy and sell rates in less-valued quote-token units
19089
+ * without requiring token addresses. Symbols are matched case-insensitively;
19090
+ * chain IDs are numeric IDs for chains configured in the SDK.
19091
+ */
19092
+ async queryBuyAndSellRates(params) {
19093
+ const { queryClient } = this.requireIndexer();
19094
+ const sourceChain = chainConfigs[params.sourceChainId]?.stateMachineId;
19095
+ const destinationChain = chainConfigs[params.destinationChainId]?.stateMachineId;
19096
+ if (!sourceChain) throw new UnsupportedLiquidityChainError(params.sourceChainId);
19097
+ if (!destinationChain) throw new UnsupportedLiquidityChainError(params.destinationChainId);
19098
+ const sourceToken = this.source.configService.getAssetMetadataBySymbol(sourceChain, params.tokenInSymbol);
19099
+ const destinationToken = this.dest.configService.getAssetMetadataBySymbol(
19100
+ destinationChain,
19101
+ params.tokenOutSymbol
19102
+ );
19103
+ if (!sourceToken) throw new UnsupportedLiquidityAssetError(sourceChain, params.tokenInSymbol);
19104
+ if (!destinationToken) throw new UnsupportedLiquidityAssetError(destinationChain, params.tokenOutSymbol);
19105
+ return new LiquidityEngine(queryClient).getBuyAndSellRates({
19106
+ sourceChain,
19107
+ destinationChain,
19108
+ tokenInSymbol: sourceToken.symbol,
19109
+ tokenOutSymbol: destinationToken.symbol
18723
19110
  });
18724
19111
  }
18725
19112
  /**
@@ -19259,14 +19646,34 @@ var IntentGateway = class _IntentGateway {
19259
19646
  }
19260
19647
  }
19261
19648
  };
19649
+
19650
+ // src/protocols/intents/phantom-aggregation.ts
19262
19651
  var FILL_ORDER_ABI = IntentGatewayV2_default.ABI;
19263
- var DECLARATION_VERSION = 1;
19264
- var MAX_DECLARED_CHAINS = 255;
19265
- function encodeAcceptedSourceChains(chains2) {
19266
- if (chains2.length > MAX_DECLARED_CHAINS) {
19267
- throw new Error(`Cannot declare more than ${MAX_DECLARED_CHAINS} source chains`);
19652
+ var DECLARATION_V1 = 1;
19653
+ var DECLARATION_V2 = 2;
19654
+ var MAX_DECLARED_ENTRIES = 255;
19655
+ var MAX_TOKEN_ID_BYTES = 32;
19656
+ function tokenIdToBytes(tokenId) {
19657
+ if (tokenId < 0n) throw new Error(`Uniswap V4 tokenId cannot be negative: ${tokenId}`);
19658
+ const bytes = [];
19659
+ let rest = tokenId;
19660
+ while (rest > 0n) {
19661
+ bytes.unshift(Number(rest & 0xffn));
19662
+ rest >>= 8n;
19663
+ }
19664
+ return bytes.length > 0 ? bytes : [0];
19665
+ }
19666
+ function encodePhantomBidDeclaration(declaration) {
19667
+ const chains2 = declaration.acceptedSourceChains ?? [];
19668
+ const positions = declaration.uniswapV4Positions ?? [];
19669
+ if (chains2.length > MAX_DECLARED_ENTRIES) {
19670
+ throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} source chains`);
19268
19671
  }
19269
- const bytes = [DECLARATION_VERSION, chains2.length];
19672
+ if (positions.length > MAX_DECLARED_ENTRIES) {
19673
+ throw new Error(`Cannot declare more than ${MAX_DECLARED_ENTRIES} Uniswap V4 positions`);
19674
+ }
19675
+ const version = positions.length > 0 ? DECLARATION_V2 : DECLARATION_V1;
19676
+ const bytes = [version, chains2.length];
19270
19677
  for (const chain of chains2) {
19271
19678
  const encoded = stringToU8a(chain);
19272
19679
  if (encoded.length === 0 || encoded.length > 255) {
@@ -19274,25 +19681,59 @@ function encodeAcceptedSourceChains(chains2) {
19274
19681
  }
19275
19682
  bytes.push(encoded.length, ...encoded);
19276
19683
  }
19684
+ if (version === DECLARATION_V2) {
19685
+ bytes.push(positions.length);
19686
+ for (const tokenId of positions) {
19687
+ const encoded = tokenIdToBytes(tokenId);
19688
+ if (encoded.length > MAX_TOKEN_ID_BYTES) {
19689
+ throw new Error(`Uniswap V4 tokenId exceeds uint256: ${tokenId}`);
19690
+ }
19691
+ bytes.push(encoded.length, ...encoded);
19692
+ }
19693
+ }
19277
19694
  return u8aToHex(new Uint8Array(bytes));
19278
19695
  }
19279
- function decodeAcceptedSourceChains(paymasterAndData) {
19280
- if (!paymasterAndData || !isHex$1(paymasterAndData)) return null;
19696
+ function decodePhantomBidDeclaration(paymasterAndData) {
19697
+ const absent = { acceptedSources: null, uniswapV4Positions: [] };
19698
+ if (!paymasterAndData || !isHex$1(paymasterAndData)) return absent;
19281
19699
  const bytes = hexToU8a(paymasterAndData);
19282
- if (bytes.length < 2 || bytes[0] !== DECLARATION_VERSION) return null;
19283
- const count = bytes[1];
19700
+ if (bytes.length < 2) return absent;
19701
+ const version = bytes[0];
19702
+ if (version !== DECLARATION_V1 && version !== DECLARATION_V2) return absent;
19284
19703
  const chains2 = [];
19285
19704
  let offset = 2;
19286
- for (let entry = 0; entry < count; entry++) {
19287
- if (offset >= bytes.length) return null;
19705
+ for (let entry = 0; entry < bytes[1]; entry++) {
19706
+ if (offset >= bytes.length) return absent;
19288
19707
  const length = bytes[offset];
19289
19708
  offset += 1;
19290
- if (length === 0 || offset + length > bytes.length) return null;
19709
+ if (length === 0 || offset + length > bytes.length) return absent;
19291
19710
  chains2.push(u8aToString(bytes.subarray(offset, offset + length)));
19292
19711
  offset += length;
19293
19712
  }
19294
- if (offset !== bytes.length) return null;
19295
- return chains2;
19713
+ const positions = [];
19714
+ if (version === DECLARATION_V2) {
19715
+ if (offset >= bytes.length) return absent;
19716
+ const count = bytes[offset];
19717
+ offset += 1;
19718
+ for (let entry = 0; entry < count; entry++) {
19719
+ if (offset >= bytes.length) return absent;
19720
+ const length = bytes[offset];
19721
+ offset += 1;
19722
+ if (length === 0 || length > MAX_TOKEN_ID_BYTES || offset + length > bytes.length) return absent;
19723
+ let tokenId = 0n;
19724
+ for (const byte of bytes.subarray(offset, offset + length)) tokenId = tokenId << 8n | BigInt(byte);
19725
+ positions.push(tokenId);
19726
+ offset += length;
19727
+ }
19728
+ }
19729
+ if (offset !== bytes.length) return absent;
19730
+ return { acceptedSources: chains2, uniswapV4Positions: positions };
19731
+ }
19732
+ function encodeAcceptedSourceChains(chains2) {
19733
+ return encodePhantomBidDeclaration({ acceptedSourceChains: chains2 });
19734
+ }
19735
+ function decodeAcceptedSourceChains(paymasterAndData) {
19736
+ return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
19296
19737
  }
19297
19738
  FILL_ORDER_ABI.find(
19298
19739
  (item) => item?.type === "function" && item?.name === "fillOrder"
@@ -23767,6 +24208,6 @@ async function teleportDot(param_) {
23767
24208
  return stream;
23768
24209
  }
23769
24210
 
23770
- 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, decodeUserOpScale, encodeAcceptedSourceChains, 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 };
24211
+ 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 };
23771
24212
  //# sourceMappingURL=index.js.map
23772
24213
  //# sourceMappingURL=index.js.map