@hyperbridge/sdk 2.8.7 → 2.8.10

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.
@@ -2808,7 +2808,7 @@ var chainConfigs = {
2808
2808
  // "Usdt0Oft": Not available on BSC
2809
2809
  },
2810
2810
  rpcEnvKey: "BSC_MAINNET",
2811
- defaultRpcUrl: "https://binance.llamarpc.com",
2811
+ defaultRpcUrl: "https://bsc-rpc.publicnode.com",
2812
2812
  consensusStateId: "BSC0",
2813
2813
  coingeckoId: "binance-smart-chain",
2814
2814
  erc4626Vaults: [
@@ -3858,6 +3858,11 @@ var ABI3 = [
3858
3858
  type: "uint256",
3859
3859
  internalType: "uint256"
3860
3860
  },
3861
+ {
3862
+ name: "validUntil",
3863
+ type: "uint256",
3864
+ internalType: "uint256"
3865
+ },
3861
3866
  {
3862
3867
  name: "outputs",
3863
3868
  type: "tuple[]",
@@ -7825,6 +7830,255 @@ function encodeISMPMessage(message) {
7825
7830
  throw new Error("Failed to encode ISMP message", { cause: error });
7826
7831
  }
7827
7832
  }
7833
+
7834
+ // src/utils/rateLimiter.ts
7835
+ var TokenBucket = class {
7836
+ /**
7837
+ * @param ratePerSecond - sustained requests per second.
7838
+ * @param burst - how many requests may go out back-to-back before pacing starts. Defaults to
7839
+ * one second's worth, which is the shape most limiters police.
7840
+ */
7841
+ constructor(ratePerSecond, burst = ratePerSecond) {
7842
+ this.ratePerSecond = ratePerSecond;
7843
+ this.burst = burst;
7844
+ if (ratePerSecond <= 0) throw new Error(`TokenBucket rate must be positive, got ${ratePerSecond}`);
7845
+ this.tokens = burst;
7846
+ this.lastRefill = Date.now();
7847
+ }
7848
+ ratePerSecond;
7849
+ burst;
7850
+ /** Fractional on purpose: a partial token is real capacity, just not yet a whole request. */
7851
+ tokens;
7852
+ lastRefill;
7853
+ waiting = [];
7854
+ drainTimer = null;
7855
+ /** Resolves once this caller may send. */
7856
+ async acquire() {
7857
+ if (this.waiting.length === 0 && this.take()) return;
7858
+ return new Promise((resolve) => {
7859
+ this.waiting.push(resolve);
7860
+ this.scheduleDrain();
7861
+ });
7862
+ }
7863
+ /** Requests currently waiting on a token. Exposed for tests and diagnostics. */
7864
+ get queued() {
7865
+ return this.waiting.length;
7866
+ }
7867
+ refill() {
7868
+ const now = Date.now();
7869
+ const elapsed = now - this.lastRefill;
7870
+ if (elapsed <= 0) {
7871
+ if (elapsed < 0) this.lastRefill = now;
7872
+ return;
7873
+ }
7874
+ this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerSecond / 1e3);
7875
+ this.lastRefill = now;
7876
+ }
7877
+ take() {
7878
+ this.refill();
7879
+ if (this.tokens < 1) return false;
7880
+ this.tokens -= 1;
7881
+ return true;
7882
+ }
7883
+ scheduleDrain() {
7884
+ if (this.drainTimer) return;
7885
+ this.refill();
7886
+ const deficit = 1 - this.tokens;
7887
+ const waitMs = deficit <= 0 ? 0 : Math.ceil(deficit * 1e3 / this.ratePerSecond);
7888
+ const timer = setTimeout(() => {
7889
+ this.drainTimer = null;
7890
+ this.drain();
7891
+ }, waitMs);
7892
+ timer.unref?.();
7893
+ this.drainTimer = timer;
7894
+ }
7895
+ drain() {
7896
+ while (this.waiting.length > 0 && this.take()) {
7897
+ this.waiting.shift()?.();
7898
+ }
7899
+ if (this.waiting.length > 0) this.scheduleDrain();
7900
+ }
7901
+ };
7902
+ var BATCHES_NOT_SUPPORTED_CODE = -32005;
7903
+ var TOO_BIG_BATCH_REQUEST_CODE = -32010;
7904
+ var DEFAULT_MAX_BATCH_SIZE = 32;
7905
+ function rpcError({ code, message, data }) {
7906
+ const suffix = data === void 0 ? "" : `: ${typeof data === "string" ? data : JSON.stringify(data)}`;
7907
+ const error = new Error(`${code}: ${message}${suffix}`);
7908
+ error.code = code;
7909
+ error.data = data;
7910
+ return error;
7911
+ }
7912
+ var BatchingHttpProvider = class _BatchingHttpProvider extends api.HttpProvider {
7913
+ #endpoint;
7914
+ #headers;
7915
+ #limiter;
7916
+ #maxBatchSize;
7917
+ #batchingSupported = true;
7918
+ #pending = [];
7919
+ #flushTimer = null;
7920
+ #flushing = false;
7921
+ #nextId = 1;
7922
+ constructor(endpoint, headers, limiter, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
7923
+ super(endpoint, headers, 0);
7924
+ this.#endpoint = endpoint;
7925
+ this.#headers = headers;
7926
+ this.#limiter = limiter;
7927
+ this.#maxBatchSize = maxBatchSize;
7928
+ }
7929
+ /**
7930
+ * `isCacheable` is accepted for interface compatibility and ignored: this provider has no
7931
+ * response cache, which is deliberate — see the note in `IntentsCoprocessor.http`.
7932
+ */
7933
+ async send(method, params, _isCacheable) {
7934
+ return new Promise((resolve, reject) => {
7935
+ this.#pending.push({
7936
+ id: this.#nextId++,
7937
+ method,
7938
+ params,
7939
+ resolve,
7940
+ reject
7941
+ });
7942
+ if (this.#pending.length >= this.#maxBatchSize) this.#flushNow();
7943
+ else this.#scheduleFlush();
7944
+ });
7945
+ }
7946
+ clone() {
7947
+ return new _BatchingHttpProvider(this.#endpoint, this.#headers, this.#limiter, this.#maxBatchSize);
7948
+ }
7949
+ /** Calls waiting for a flush. Exposed for tests and diagnostics. */
7950
+ get queued() {
7951
+ return this.#pending.length;
7952
+ }
7953
+ /**
7954
+ * Collect for the rest of this macrotask, then send.
7955
+ *
7956
+ * A macrotask rather than a microtask because the bursts worth catching are not synchronous:
7957
+ * `Promise.all` over a set of reads starts them in one synchronous run, but each then advances
7958
+ * through several microtask turns of its own before reaching the provider. A microtask flush
7959
+ * would fire between those turns and split one burst across several requests.
7960
+ */
7961
+ #scheduleFlush() {
7962
+ if (this.#flushTimer || this.#flushing) return;
7963
+ const timer = setTimeout(() => {
7964
+ this.#flushTimer = null;
7965
+ void this.#flush();
7966
+ }, 0);
7967
+ timer.unref?.();
7968
+ this.#flushTimer = timer;
7969
+ }
7970
+ #flushNow() {
7971
+ if (this.#flushing) return;
7972
+ if (this.#flushTimer) {
7973
+ clearTimeout(this.#flushTimer);
7974
+ this.#flushTimer = null;
7975
+ }
7976
+ void this.#flush();
7977
+ }
7978
+ async #flush() {
7979
+ if (this.#flushing) return;
7980
+ const calls = this.#pending.splice(0, this.#maxBatchSize);
7981
+ if (calls.length === 0) return;
7982
+ this.#flushing = true;
7983
+ try {
7984
+ await this.#limiter.acquire();
7985
+ await this.#post(calls);
7986
+ } finally {
7987
+ this.#flushing = false;
7988
+ if (this.#pending.length > 0) this.#scheduleFlush();
7989
+ }
7990
+ }
7991
+ async #post(calls) {
7992
+ const single = calls.length === 1;
7993
+ const payload = calls.map(({ id, method, params }) => ({ id, jsonrpc: "2.0", method, params }));
7994
+ const body = JSON.stringify(single ? payload[0] : payload);
7995
+ let parsed;
7996
+ try {
7997
+ const response = await fetch(this.#endpoint, {
7998
+ body,
7999
+ headers: {
8000
+ Accept: "application/json",
8001
+ "Content-Type": "application/json",
8002
+ ...this.#headers
8003
+ },
8004
+ method: "POST"
8005
+ });
8006
+ if (!response.ok) throw new Error(`[${response.status}]: ${response.statusText}`);
8007
+ parsed = JSON.parse(await response.text());
8008
+ } catch (err) {
8009
+ const error = err instanceof Error ? err : new Error(String(err));
8010
+ error.message = `${error.message}
8011
+ Failed HTTP Request: ${JSON.stringify(
8012
+ calls.map(({ method, params }) => ({ method, params }))
8013
+ )}`;
8014
+ for (const call of calls) call.reject(error);
8015
+ return;
8016
+ }
8017
+ if (Array.isArray(parsed)) {
8018
+ this.#settleBatch(calls, parsed);
8019
+ return;
8020
+ }
8021
+ if (!single) {
8022
+ this.#handleBatchRefusal(calls, parsed);
8023
+ return;
8024
+ }
8025
+ this.#settle(calls[0], parsed);
8026
+ }
8027
+ #settleBatch(calls, responses) {
8028
+ const byId = /* @__PURE__ */ new Map();
8029
+ for (const response of responses) {
8030
+ if (typeof response?.id === "number") byId.set(response.id, response);
8031
+ }
8032
+ for (const call of calls) {
8033
+ const response = byId.get(call.id);
8034
+ if (response) this.#settle(call, response);
8035
+ else call.reject(new Error(`No response for ${call.method} in batch reply`));
8036
+ }
8037
+ }
8038
+ #settle(call, response) {
8039
+ if (response?.error) {
8040
+ call.reject(rpcError(response.error));
8041
+ return;
8042
+ }
8043
+ if (!response || response.result === void 0) {
8044
+ call.reject(new Error("No result found in jsonrpc response"));
8045
+ return;
8046
+ }
8047
+ call.resolve(response.result);
8048
+ }
8049
+ /**
8050
+ * The server rejected the batch itself rather than any call in it. Both forms are recoverable
8051
+ * without losing a call, and neither should ever surface to a caller as a failure.
8052
+ */
8053
+ #handleBatchRefusal(calls, response) {
8054
+ const code = response?.error?.code;
8055
+ if (code === BATCHES_NOT_SUPPORTED_CODE) {
8056
+ this.#batchingSupported = false;
8057
+ this.#maxBatchSize = 1;
8058
+ this.#requeue(calls);
8059
+ return;
8060
+ }
8061
+ if (code === TOO_BIG_BATCH_REQUEST_CODE) {
8062
+ this.#maxBatchSize = Math.max(1, Math.floor(this.#maxBatchSize / 2));
8063
+ this.#requeue(calls);
8064
+ return;
8065
+ }
8066
+ const error = response?.error ? rpcError(response.error) : new Error("Malformed batch reply: neither an array nor an error");
8067
+ for (const call of calls) call.reject(error);
8068
+ }
8069
+ /** Puts calls back at the head of the queue, so a refused batch keeps its place in line. */
8070
+ #requeue(calls) {
8071
+ this.#pending.unshift(...calls);
8072
+ this.#scheduleFlush();
8073
+ }
8074
+ /** Whether batches are still being attempted. Exposed for tests and diagnostics. */
8075
+ get batchingSupported() {
8076
+ return this.#batchingSupported;
8077
+ }
8078
+ };
8079
+
8080
+ // src/chains/intentsCoprocessor.ts
8081
+ var SYSTEM_EVENTS_KEY = util.u8aToHex(util.u8aConcat(utilCrypto.xxhashAsU8a("System", 128), utilCrypto.xxhashAsU8a("Events", 128)));
7828
8082
  var OFFCHAIN_BID_PREFIX = new TextEncoder().encode("intents::bid::");
7829
8083
  var OFFCHAIN_PHANTOM_PREFIX = new TextEncoder().encode("intents::phantom::order::");
7830
8084
  var HYPERBRIDGE_TYPES_BUNDLE = {
@@ -7838,6 +8092,63 @@ var HTTP_CONNECT_TIMEOUT_MS = 2e4;
7838
8092
  var INCLUSION_TIMEOUT_MS = 2e4;
7839
8093
  var PHANTOM_POLL_INTERVAL_MS = 15e3;
7840
8094
  var GARGANTUA_PHANTOM_POLL_INTERVAL_MS = 6e3;
8095
+ var DEFAULT_RPC_MAX_RPS = 8;
8096
+ var DEFAULT_MAX_BLOCKS_PER_POLL = 10;
8097
+ var MAX_RATE_LIMIT_BACKOFF_TICKS = 8;
8098
+ var rpcLimiters = /* @__PURE__ */ new Map();
8099
+ function limiterFor(httpUrl) {
8100
+ const key = new URL(httpUrl).origin;
8101
+ let limiter = rpcLimiters.get(key);
8102
+ if (!limiter) {
8103
+ limiter = new TokenBucket(configuredRpcMaxRps());
8104
+ rpcLimiters.set(key, limiter);
8105
+ }
8106
+ return limiter;
8107
+ }
8108
+ function configuredRpcMaxRps() {
8109
+ const raw = typeof process !== "undefined" ? process.env?.HYPERBRIDGE_RPC_MAX_RPS : void 0;
8110
+ const parsed = raw === void 0 ? Number.NaN : Number(raw);
8111
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_RPC_MAX_RPS;
8112
+ }
8113
+ function isRateLimited(err) {
8114
+ const message = err instanceof Error ? err.message : String(err);
8115
+ return message.includes("[429]") || /too many requests/i.test(message);
8116
+ }
8117
+ function isMethodUnavailable(err) {
8118
+ const message = err instanceof Error ? err.message : String(err);
8119
+ const code = err?.code;
8120
+ return code === -32601 || /method not found|unsafe to be called externally/i.test(message);
8121
+ }
8122
+ var EventDecodeError = class extends Error {
8123
+ };
8124
+ function phantomOrdersFrom(records) {
8125
+ if (records == null || typeof records[Symbol.iterator] !== "function") {
8126
+ throw new EventDecodeError(`Expected a decoded event vector, got ${typeof records}`);
8127
+ }
8128
+ const orders = [];
8129
+ for (const record of records) {
8130
+ if (typeof record !== "object" || record === null || !("event" in record)) {
8131
+ throw new EventDecodeError(
8132
+ "system.events did not decode to event records \u2014 the storage entry's metadata was probably not carried through, leaving the value as raw bytes"
8133
+ );
8134
+ }
8135
+ const { event } = record;
8136
+ if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8137
+ const [commitment, chain, createdAt, legs] = event.data;
8138
+ orders.push({
8139
+ commitment: commitment.toHex(),
8140
+ chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8141
+ createdAt: createdAt.toNumber(),
8142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8143
+ legs: legs.map((leg) => ({
8144
+ tokenA: leg.tokenA.toHex(),
8145
+ tokenB: leg.tokenB.toHex(),
8146
+ standardAmount: BigInt(leg.standardAmount.toString())
8147
+ }))
8148
+ });
8149
+ }
8150
+ return orders;
8151
+ }
7841
8152
  function rejectAfter(ms, message) {
7842
8153
  return new Promise((_resolve, reject) => {
7843
8154
  const timer = setTimeout(() => reject(new Error(message)), ms);
@@ -7909,8 +8220,14 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
7909
8220
  ownsConnection;
7910
8221
  /** Cached result of whether the node exposes intents_* RPC methods */
7911
8222
  hasIntentsRpc = null;
8223
+ /** The pallet's phantom timings, read once. Cleared on failure so the read retries. */
8224
+ phantomTimingsRead = null;
7912
8225
  /** The HTTP-backed api, connected on first use. Cleared after a failed attempt so it retries. */
7913
8226
  httpApi = null;
8227
+ /** Last runtime version read from the node, for {@link confirmedRuntimeVersion} to compare against. */
8228
+ lastRuntimeVersion;
8229
+ /** Set once the node refuses `state_queryStorage`, so the poll stops asking for it. */
8230
+ rangeQueryUnavailable = false;
7914
8231
  // Serialises every extrinsic submission on this instance's substrate account. All submit/retract
7915
8232
  // methods funnel through signAndSendExtrinsic, each using the API's auto-nonce; fired in parallel
7916
8233
  // (bids for orders on different chains, or several phantom orders in one interval) they would grab
@@ -8010,7 +8327,11 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8010
8327
  // replayed from memory on every tick, faster than the TTL could lapse, and the node never
8011
8328
  // saw a second request. The cache bought nothing here anyway: the poll reads each block
8012
8329
  // once, and `api.at(hash)` reuses registries at the api layer regardless.
8013
- provider: new api.HttpProvider(httpUrl, {}, 0),
8330
+ // Concurrent calls are coalesced into one JSON-RPC batch request, and every request
8331
+ // to this endpoint is paced by a bucket shared with any other coprocessor in this
8332
+ // process pointed at the same host — the limit is the server's, and it counts
8333
+ // requests per address rather than per connection.
8334
+ provider: new BatchingHttpProvider(httpUrl, {}, limiterFor(httpUrl)),
8014
8335
  typesBundle: HYPERBRIDGE_TYPES_BUNDLE,
8015
8336
  // A second connection to the node the ws api already reported on; its init warnings
8016
8337
  // would just be duplicates.
@@ -8578,29 +8899,77 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8578
8899
  }
8579
8900
  /**
8580
8901
  * Reads the PhantomOrderRegistered events emitted in a single block.
8902
+ *
8903
+ * Costs two RPCs per block when `knownVersion` is supplied and four without it, which is why the
8904
+ * poll goes to the trouble of establishing one. `api.at(hash)` has to work out which metadata to
8905
+ * decode the block against, and with nothing to go on it fetches the header and then the runtime
8906
+ * version at its parent — every block, forever. Its cheaper paths are a registry already pinned
8907
+ * to this exact hash (only ever the previous block's) or one matching a version the caller
8908
+ * names, so naming the version is the only way out. See `getBlockRegistry` in
8909
+ * `@polkadot/api/base/Init`; the `getUpgradeVersion` shortcut that would otherwise skip the
8910
+ * lookup only covers chains hardcoded in `@polkadot/types-known`, which Hyperbridge is not.
8911
+ *
8912
+ * @param knownVersion - the runtime version this block is known to run, if the caller has
8913
+ * established one. Passing a version the block does not actually run decodes it against the
8914
+ * wrong metadata, so this is for callers that have checked, not a place to pass a guess.
8581
8915
  */
8582
- async getPhantomOrdersInBlock(blockNumber) {
8916
+ async getPhantomOrdersInBlock(blockNumber, knownVersion) {
8583
8917
  const api = await this.http();
8584
8918
  const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
8585
- const apiAt = await api.at(blockHash);
8586
- const records = await apiAt.query.system.events();
8587
- const orders = [];
8588
- for (const { event } of records) {
8589
- if (event.section !== "intentsCoprocessor" || event.method !== "PhantomOrderRegistered") continue;
8590
- const [commitment, chain, createdAt, legs] = event.data;
8591
- orders.push({
8592
- commitment: commitment.toHex(),
8593
- chain: new TextDecoder().decode(util.hexToU8a(chain.toHex())),
8594
- createdAt: createdAt.toNumber(),
8595
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
8596
- legs: legs.map((leg) => ({
8597
- tokenA: leg.tokenA.toHex(),
8598
- tokenB: leg.tokenB.toHex(),
8599
- standardAmount: BigInt(leg.standardAmount.toString())
8600
- }))
8601
- });
8602
- }
8603
- return orders;
8919
+ return await this.getPhantomOrdersAtHash(blockHash.toHex(), knownVersion);
8920
+ }
8921
+ /**
8922
+ * The same read, for a caller that already holds the block's hash.
8923
+ *
8924
+ * Split out so the poll can fetch a whole range's hashes in one concurrent wave — which the
8925
+ * provider coalesces into a single batched request — and then read each block's events knowing
8926
+ * its hash. `chain_getBlockHash` is the half of the pair that parallelises safely: it takes no
8927
+ * historic block hash, so it never triggers polkadot-js's per-hash registry resolution, and
8928
+ * concurrent calls cannot race each other's registry state.
8929
+ */
8930
+ async getPhantomOrdersAtHash(blockHash, knownVersion) {
8931
+ const api = await this.http();
8932
+ const apiAt = await api.at(blockHash, knownVersion);
8933
+ return phantomOrdersFrom(await apiAt.query.system.events());
8934
+ }
8935
+ /**
8936
+ * Every block's phantom orders across a whole range, in one `state_queryStorage` call.
8937
+ *
8938
+ * This is the cheap path: the request cost of a scan stops depending on how many blocks it
8939
+ * covers. The events key is the only key queried, and both bounds are block hashes the caller
8940
+ * already holds.
8941
+ *
8942
+ * Two properties of the RPC shape the result.
8943
+ *
8944
+ * It returns *diffs*: `query_storage_unfiltered` in `sc-rpc` pushes a change set for a block only
8945
+ * when the value differs from the previous block in the range (`has_changed`, and the set is
8946
+ * dropped when empty), so a block whose events encode byte-for-byte identically to its
8947
+ * predecessor's is simply absent. That happens on a quiet chain, where consecutive blocks carry
8948
+ * nothing but the timestamp inherent's `ExtrinsicSuccess`. It is safe here because an absent
8949
+ * block provably carries no phantom orders: a `PhantomOrderRegistered` commitment is derived from
8950
+ * the block number (`phantom_order_commitment`), so a block that registered orders can never
8951
+ * encode identically to any other block. Absent therefore means "same as the previous block",
8952
+ * and the previous block having orders would contradict that.
8953
+ *
8954
+ * And it is gated by `--rpc-methods` (`check_if_safe` in `sc-rpc`), which answers a denied call
8955
+ * with `Method not found`. The node this reads from must already run unsafe RPC to serve
8956
+ * `offchain_localStorageGet` for the orders themselves, so this is normally available; the poll
8957
+ * falls back to reading block by block when it is not.
8958
+ *
8959
+ * @returns one entry per block the node reported a change for, in ascending block order.
8960
+ */
8961
+ async getPhantomOrdersInRange(fromBlockHash, toBlockHash) {
8962
+ const api = await this.http();
8963
+ const changeSets = await api.rpc.state.queryStorage.raw(
8964
+ [SYSTEM_EVENTS_KEY],
8965
+ fromBlockHash,
8966
+ toBlockHash
8967
+ );
8968
+ return changeSets.map((changeSet) => {
8969
+ const value = changeSet?.changes?.find(([key]) => key === SYSTEM_EVENTS_KEY)?.[1];
8970
+ if (!value) return [];
8971
+ return phantomOrdersFrom(api.registry.createType("Vec<EventRecord>", value));
8972
+ });
8604
8973
  }
8605
8974
  /**
8606
8975
  * Polls for newly registered phantom orders, invoking the callback once per block that carries
@@ -8628,30 +8997,85 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8628
8997
  * socket that looks alive while delivering nothing. It also means a websocket outage does not
8629
8998
  * pause phantom bidding at all — the two transports fail independently.
8630
8999
  *
9000
+ * What the cadence does *not* describe is the request rate, which is what rate limiters police.
9001
+ * A tick costs four requests whatever the range covers — the head, the runtime version, the two
9002
+ * bounding block hashes as one batched request, and one `state_queryStorage` for every block's
9003
+ * events — and they go out back-to-back, so an interval well under any per-second limit could
9004
+ * still arrive as a burst over it. Three things keep that in bounds: the provider coalesces
9005
+ * concurrent calls into one request and paces requests through the endpoint's token bucket (see
9006
+ * `http`), and `maxBlocksPerPoll` bounds the range. A 429 that gets through anyway backs the
9007
+ * poll off for a doubling number of ticks, so a limiter that is already shedding load is not
9008
+ * handed the next window's budget in rejections too.
9009
+ *
9010
+ * Where the node will not serve `state_queryStorage` the poll reads block by block instead, at
9011
+ * three requests plus one per block; see {@link scanRangeAtOnce}.
9012
+ *
8631
9013
  * Returns a function that stops polling.
8632
9014
  */
8633
9015
  pollPhantomOrders(callback, options = {}) {
8634
- const { intervalMs, maxBlocksPerPoll = 500, lookbackBlocks = 0, onError } = options;
9016
+ const {
9017
+ intervalMs,
9018
+ maxBlocksPerPoll = DEFAULT_MAX_BLOCKS_PER_POLL,
9019
+ onError,
9020
+ onSkip
9021
+ } = options;
8635
9022
  let cursor = null;
8636
9023
  let inFlight = false;
8637
9024
  let stopped = false;
9025
+ let backoffTicks = 0;
9026
+ let backoffLength = 0;
8638
9027
  const tick = async () => {
8639
9028
  if (inFlight || stopped) return;
9029
+ if (backoffTicks > 0) {
9030
+ backoffTicks -= 1;
9031
+ return;
9032
+ }
8640
9033
  inFlight = true;
8641
9034
  try {
8642
- const head = (await (await this.http()).rpc.chain.getHeader()).number.toNumber();
9035
+ const api = await this.http();
9036
+ const head = (await api.rpc.chain.getHeader()).number.toNumber();
8643
9037
  if (cursor === null) {
8644
- cursor = Math.max(head - 1 - lookbackBlocks, -1);
9038
+ cursor = Math.max(head - 1, -1);
9039
+ } else {
9040
+ const { bidWindowBlocks, intervalBlocks } = await this.phantomTimings();
9041
+ if (head - cursor > bidWindowBlocks + Math.max(intervalBlocks, bidWindowBlocks)) {
9042
+ const from = cursor + 1;
9043
+ cursor = Math.max(head - 1 - bidWindowBlocks, -1);
9044
+ onSkip?.({ from, to: cursor, head });
9045
+ }
8645
9046
  }
8646
9047
  if (head <= cursor) return;
9048
+ const knownVersion = await this.confirmedRuntimeVersion();
8647
9049
  const to = Math.min(head, cursor + maxBlocksPerPoll);
8648
- for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) {
9050
+ const ranged = await this.scanRangeAtOnce(api, cursor + 1, to, knownVersion, onError);
9051
+ if (ranged) {
9052
+ for (const orders of ranged) {
9053
+ if (stopped) return;
9054
+ if (orders.length > 0) callback(orders);
9055
+ }
9056
+ cursor = to;
9057
+ backoffLength = 0;
9058
+ return;
9059
+ }
9060
+ const numbers = [];
9061
+ for (let blockNumber = cursor + 1; blockNumber <= to; blockNumber++) numbers.push(blockNumber);
9062
+ const hashes = await Promise.allSettled(
9063
+ numbers.map((blockNumber) => api.rpc.chain.getBlockHash(blockNumber))
9064
+ );
9065
+ for (let index = 0; index < numbers.length; index++) {
8649
9066
  if (stopped) return;
8650
- const orders = await this.getPhantomOrdersInBlock(blockNumber);
9067
+ const hash = hashes[index];
9068
+ if (hash.status === "rejected") throw hash.reason;
9069
+ const orders = await this.getPhantomOrdersAtHash(hash.value.toHex(), knownVersion);
8651
9070
  if (orders.length > 0) callback(orders);
8652
- cursor = blockNumber;
9071
+ cursor = numbers[index];
8653
9072
  }
9073
+ backoffLength = 0;
8654
9074
  } catch (err) {
9075
+ if (isRateLimited(err)) {
9076
+ backoffLength = Math.min(backoffLength === 0 ? 1 : backoffLength * 2, MAX_RATE_LIMIT_BACKOFF_TICKS);
9077
+ backoffTicks = backoffLength;
9078
+ }
8655
9079
  onError?.(err);
8656
9080
  } finally {
8657
9081
  inFlight = false;
@@ -8670,6 +9094,125 @@ var IntentsCoprocessor = class _IntentsCoprocessor {
8670
9094
  if (timer) clearInterval(timer);
8671
9095
  };
8672
9096
  }
9097
+ /**
9098
+ * The Hyperbridge head, over HTTP like every other read here.
9099
+ *
9100
+ * Exposed for callers that have to know how old something is: a phantom order carries the block
9101
+ * it was registered at, and only against the head does that become "still biddable" or "long
9102
+ * expired".
9103
+ */
9104
+ async latestBlockNumber() {
9105
+ const api = await this.http();
9106
+ return (await api.rpc.chain.getHeader()).number.toNumber();
9107
+ }
9108
+ /**
9109
+ * The pallet's phantom timings, read from chain state.
9110
+ *
9111
+ * Both are governance-settable and neither is derivable: on Nexus today the window is 15 while
9112
+ * the runtime constant behind it is 25, so anything hard-coded is wrong in one direction or the
9113
+ * other — too tight and live orders are dropped, too loose and bids are sent into a closed
9114
+ * window for the pallet to reject.
9115
+ *
9116
+ * Read once per instance and cached, because a governance change to either is rare and a read
9117
+ * per poll tick would be a request per tick forever. The cost is that a change is picked up on
9118
+ * the next restart rather than immediately. A failed read is not cached, so it retries.
9119
+ */
9120
+ async phantomTimings() {
9121
+ if (!this.phantomTimingsRead) {
9122
+ this.phantomTimingsRead = this.readPhantomTimings().catch((err) => {
9123
+ this.phantomTimingsRead = null;
9124
+ throw err;
9125
+ });
9126
+ }
9127
+ return this.phantomTimingsRead;
9128
+ }
9129
+ async readPhantomTimings() {
9130
+ const api = await this.http();
9131
+ const [window, interval] = await Promise.all([
9132
+ api.query.intentsCoprocessor.phantomBidWindow(),
9133
+ api.query.intentsCoprocessor.phantomOrderInterval()
9134
+ ]);
9135
+ const stored = Number(window.toString());
9136
+ return {
9137
+ bidWindowBlocks: stored === 0 ? Number(api.consts.intentsCoprocessor.phantomOrderBidWindowBlocks.toString()) : stored,
9138
+ intervalBlocks: Number(interval.toString())
9139
+ };
9140
+ }
9141
+ /**
9142
+ * A whole range of blocks in one `state_queryStorage` call, or `null` when that is not available
9143
+ * and the caller should read block by block.
9144
+ *
9145
+ * Two conditions have to hold, and both are about decoding rather than the range itself.
9146
+ *
9147
+ * The version must be confirmed for this tick — an upgrade inside the range means blocks decode
9148
+ * against different metadata, and one call cannot do that.
9149
+ *
9150
+ * And that confirmed version must still be the one the api's own registry was built for.
9151
+ * `state_queryStorage` declares no historic block hash, so rpc-core skips its registry swap and
9152
+ * decodes the reply against the default registry — fixed at connect, with no
9153
+ * `subscribeRuntimeVersion` on an HTTP api to refresh it. After an upgrade the two diverge, and
9154
+ * the per-block path takes over for good: `api.at(hash, version)` resolves, and builds, the right
9155
+ * registry. That costs a restart to get the cheap path back, which is the correct direction to
9156
+ * fail in.
9157
+ */
9158
+ async scanRangeAtOnce(api, from, to, knownVersion, onError) {
9159
+ if (this.rangeQueryUnavailable || !knownVersion) return null;
9160
+ const registryVersion = api.runtimeVersion?.specVersion;
9161
+ if (!registryVersion || !knownVersion.specVersion.eq(registryVersion)) return null;
9162
+ const [fromHash, toHash] = from === to ? await Promise.all([api.rpc.chain.getBlockHash(from)]).then(([only]) => [only, only]) : await Promise.all([api.rpc.chain.getBlockHash(from), api.rpc.chain.getBlockHash(to)]);
9163
+ try {
9164
+ return await this.getPhantomOrdersInRange(fromHash.toHex(), toHash.toHex());
9165
+ } catch (err) {
9166
+ if (isMethodUnavailable(err)) {
9167
+ this.rangeQueryUnavailable = true;
9168
+ return null;
9169
+ }
9170
+ if (err instanceof EventDecodeError) {
9171
+ this.rangeQueryUnavailable = true;
9172
+ onError?.(err);
9173
+ return null;
9174
+ }
9175
+ throw err;
9176
+ }
9177
+ }
9178
+ /**
9179
+ * The runtime version this tick's blocks may be decoded against, or `undefined` when that cannot
9180
+ * be established and each block must resolve its own.
9181
+ *
9182
+ * Naming a version to `api.at` is what removes two of the four RPCs a block scan costs, and it
9183
+ * is only sound while the version is actually the block's. Getting that wrong is not a loud
9184
+ * failure: events decoded against the wrong metadata come back as a shape the scan does not
9185
+ * recognise, so the block reads as carrying no phantom orders and the cursor advances past it —
9186
+ * exactly the silent miss the block cursor exists to rule out.
9187
+ *
9188
+ * So the version is read fresh each tick and only used when it matches the previous reading.
9189
+ * `specVersion` only ever increases, and this read happens *after* the head read, so two equal
9190
+ * readings mean no upgrade landed anywhere in between — and therefore none in the range about to
9191
+ * be scanned. A reading that differs means an upgrade landed inside the range: that tick falls
9192
+ * back to per-block resolution, which is exact, and the version is used from the next tick on
9193
+ * once it has been seen twice.
9194
+ *
9195
+ * The gap this leaves is a backlog reaching back past an upgrade, whose oldest blocks predate
9196
+ * even the previous reading. Recovering from an outage that long means those bid windows closed
9197
+ * many upgrades ago, so nothing is lost that was still winnable.
9198
+ *
9199
+ * A version that cannot be read at all yields `undefined` rather than an error: the scan is
9200
+ * about to make the same request against the same endpoint and is the better place to report it.
9201
+ */
9202
+ async confirmedRuntimeVersion() {
9203
+ let api;
9204
+ let current;
9205
+ try {
9206
+ api = await this.http();
9207
+ current = await api.rpc.state.getRuntimeVersion();
9208
+ } catch {
9209
+ return void 0;
9210
+ }
9211
+ const previous = this.lastRuntimeVersion ?? api.runtimeVersion;
9212
+ this.lastRuntimeVersion = current;
9213
+ if (!previous?.specVersion || !previous?.specName) return void 0;
9214
+ return current.specVersion.eq(previous.specVersion) && current.specName.eq(previous.specName) ? current : void 0;
9215
+ }
8673
9216
  /**
8674
9217
  * The poll cadence for the runtime this instance is connected to: Gargantua polls every block,
8675
9218
  * everything else every 15s. Falls back to the slower cadence if the runtime cannot be read,
@@ -12101,40 +12644,17 @@ query AvailableLiquidity(
12101
12644
  }
12102
12645
  }`;
12103
12646
  var BUY_AND_SELL_RATES = `
12104
- query BuyAndSellRates(
12105
- $poolId: String!
12106
- $directChain: String!
12107
- $directDirection: String!
12108
- $reverseChain: String!
12109
- $reverseDirection: String!
12110
- ) {
12111
- direct: poolChainLiquidities(
12112
- filter: {
12113
- and: [
12114
- { poolId: { equalToInsensitive: $poolId } }
12115
- { chain: { equalTo: $directChain } }
12116
- { direction: { equalTo: $directDirection } }
12117
- ]
12118
- }
12647
+ query GetLiquidityPoolRate($poolId: String!) {
12648
+ liquidityPools(
12119
12649
  first: 1
12650
+ filter: { id: { equalToInsensitive: $poolId } }
12120
12651
  ) {
12121
12652
  nodes {
12122
- rate
12123
- lastUpdatedAt
12124
- }
12125
- }
12126
- reverse: poolChainLiquidities(
12127
- filter: {
12128
- and: [
12129
- { poolId: { equalToInsensitive: $poolId } }
12130
- { chain: { equalTo: $reverseChain } }
12131
- { direction: { equalTo: $reverseDirection } }
12132
- ]
12133
- }
12134
- first: 1
12135
- ) {
12136
- nodes {
12137
- rate
12653
+ id
12654
+ token0Symbol
12655
+ token1Symbol
12656
+ sellRate
12657
+ buyRate
12138
12658
  lastUpdatedAt
12139
12659
  }
12140
12660
  }
@@ -17070,6 +17590,115 @@ var OrderCanceller = class _OrderCanceller {
17070
17590
  return feeInDestFeeToken * 1005n / 1000n;
17071
17591
  }
17072
17592
  };
17593
+ var FILL_ORDER_V1_ABI = [
17594
+ {
17595
+ type: "function",
17596
+ name: "fillOrder",
17597
+ stateMutability: "payable",
17598
+ outputs: [],
17599
+ inputs: [
17600
+ ABI3.find((e) => e.type === "function" && e.name === "fillOrder").inputs[0],
17601
+ {
17602
+ name: "options",
17603
+ type: "tuple",
17604
+ internalType: "struct FillOptions",
17605
+ components: [
17606
+ { name: "relayerFee", type: "uint256", internalType: "uint256" },
17607
+ { name: "nativeDispatchFee", type: "uint256", internalType: "uint256" },
17608
+ {
17609
+ name: "outputs",
17610
+ type: "tuple[]",
17611
+ internalType: "struct TokenInfo[]",
17612
+ components: [
17613
+ { name: "token", type: "bytes32", internalType: "bytes32" },
17614
+ { name: "amount", type: "uint256", internalType: "uint256" }
17615
+ ]
17616
+ }
17617
+ ]
17618
+ }
17619
+ ]
17620
+ }
17621
+ ];
17622
+ var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
17623
+ var LEGACY_FILL_OPTIONS_IMPLEMENTATIONS = /* @__PURE__ */ new Set([
17624
+ // The pre-validUntil IntentGatewayV2 implementation. One entry covers every chain: the
17625
+ // protocol contracts are CREATE2-deployed, so this is the implementation address on all
17626
+ // of them (confirmed with the maintainers).
17627
+ "0x976b268b06f545c4a2bf44866aa2465bd8b3c67d"
17628
+ ]);
17629
+ var CHAINS_WITHOUT_VALID_UNTIL = /* @__PURE__ */ new Set([
17630
+ 97,
17631
+ // BNB testnet
17632
+ 10200,
17633
+ // Gnosis Chiado
17634
+ 80002,
17635
+ // Polygon Amoy
17636
+ 84532,
17637
+ // Base Sepolia
17638
+ 421614,
17639
+ // Arbitrum Sepolia
17640
+ 688689,
17641
+ // Pharos testnet
17642
+ 11155111,
17643
+ // Sepolia
17644
+ 11155420,
17645
+ // Optimism Sepolia
17646
+ 420420417
17647
+ // Polkadot Hub Paseo
17648
+ ]);
17649
+ var knownV2Gateways = /* @__PURE__ */ new Set();
17650
+ function resetFillOptionsVersionCache() {
17651
+ knownV2Gateways.clear();
17652
+ }
17653
+ async function resolveImplementation(client, gateway) {
17654
+ const slot = await client.getStorageAt({ address: gateway, slot: ERC1967_IMPLEMENTATION_SLOT });
17655
+ if (!slot || slot.length < 66) return gateway;
17656
+ const addr = `0x${slot.slice(-40)}`;
17657
+ return /^0x0{40}$/.test(addr) ? gateway : addr;
17658
+ }
17659
+ async function getFillOptionsVersion(client, gateway) {
17660
+ const chainId = client.chain?.id;
17661
+ if (chainId !== void 0 && CHAINS_WITHOUT_VALID_UNTIL.has(chainId)) return 1;
17662
+ const key = gateway.toLowerCase();
17663
+ if (knownV2Gateways.has(key)) return 2;
17664
+ const implementation = await resolveImplementation(client, gateway);
17665
+ if (LEGACY_FILL_OPTIONS_IMPLEMENTATIONS.has(implementation.toLowerCase())) return 1;
17666
+ knownV2Gateways.add(key);
17667
+ return 2;
17668
+ }
17669
+ function encodeFillOrder(order, options, version) {
17670
+ if (version === 2) {
17671
+ return viem.encodeFunctionData({
17672
+ abi: ABI3,
17673
+ functionName: "fillOrder",
17674
+ args: [order, options]
17675
+ });
17676
+ }
17677
+ const { relayerFee, nativeDispatchFee, outputs } = options;
17678
+ return viem.encodeFunctionData({
17679
+ abi: FILL_ORDER_V1_ABI,
17680
+ functionName: "fillOrder",
17681
+ args: [order, { relayerFee, nativeDispatchFee, outputs }]
17682
+ });
17683
+ }
17684
+ function decodeFillOrder(data) {
17685
+ try {
17686
+ const decoded = viem.decodeFunctionData({ abi: ABI3, data });
17687
+ if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
17688
+ return { order: decoded.args[0], options: decoded.args[1] };
17689
+ }
17690
+ } catch {
17691
+ }
17692
+ try {
17693
+ const decoded = viem.decodeFunctionData({ abi: FILL_ORDER_V1_ABI, data });
17694
+ if (decoded.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
17695
+ const legacy = decoded.args[1];
17696
+ return { order: decoded.args[0], options: { ...legacy, validUntil: 0n } };
17697
+ }
17698
+ } catch {
17699
+ }
17700
+ return null;
17701
+ }
17073
17702
  var BidImpl = class {
17074
17703
  solverAddress;
17075
17704
  outputs;
@@ -17500,19 +18129,9 @@ var BidManager = class {
17500
18129
  const innerCalls = this.crypto.decodeERC7821Execute(bid.userOp.callData);
17501
18130
  if (!innerCalls || innerCalls.length === 0) return null;
17502
18131
  for (const call of innerCalls) {
17503
- try {
17504
- const decoded = viem.decodeFunctionData({
17505
- abi: ABI3,
17506
- data: call.data
17507
- });
17508
- if (decoded?.functionName === "fillOrder" && decoded.args && decoded.args.length >= 2) {
17509
- const fillOptions = decoded.args[1];
17510
- if (fillOptions?.outputs?.length > 0) {
17511
- return fillOptions;
17512
- }
17513
- }
17514
- } catch {
17515
- continue;
18132
+ const decoded = decodeFillOrder(call.data);
18133
+ if (decoded && decoded.options?.outputs?.length > 0) {
18134
+ return decoded.options;
17516
18135
  }
17517
18136
  }
17518
18137
  } catch {
@@ -17890,6 +18509,10 @@ var GasEstimator = class {
17890
18509
  relayerFee: crossChainFees.postRequestFee,
17891
18510
  // Always dispatch with the fee token (see the method docs).
17892
18511
  nativeDispatchFee: 0n,
18512
+ // Unbounded for estimation: this call is simulated, never submitted, and a real
18513
+ // bound here would only risk the estimate reverting on a slow bundler round trip.
18514
+ // The caller sets the real one on the options it actually signs.
18515
+ validUntil: 0n,
17893
18516
  outputs: order.output.assets.map((asset) => ({
17894
18517
  ...asset,
17895
18518
  token: normalizeAddressForEvmBytes32(asset.token)
@@ -17902,11 +18525,12 @@ var GasEstimator = class {
17902
18525
  let maxFeePerGas = gasPrice + gasPrice * BigInt(maxFeeBumpPercent) / 100n;
17903
18526
  const orderForEstimation = { ...order, session: solverAccountAddress };
17904
18527
  const commitment = orderCommitment(orderForEstimation);
17905
- const fillOrderCalldata = viem.encodeFunctionData({
17906
- abi: ABI3,
17907
- functionName: "fillOrder",
17908
- args: [transformOrderForContract(orderForEstimation), fillOptions]
17909
- });
18528
+ const fillOptionsVersion = await getFillOptionsVersion(this.ctx.dest.client, intentGatewayV2Address);
18529
+ const fillOrderCalldata = encodeFillOrder(
18530
+ transformOrderForContract(orderForEstimation),
18531
+ fillOptions,
18532
+ fillOptionsVersion
18533
+ );
17910
18534
  let callGasLimit = 500000n;
17911
18535
  let verificationGasLimit = 100000n;
17912
18536
  let preVerificationGas = 100000n;
@@ -18346,29 +18970,29 @@ var LiquidityEngine = class {
18346
18970
  };
18347
18971
  }
18348
18972
  /**
18349
- * Returns chain-specific buy and sell rates in less-valued quote-token units
18350
- * per one base token.
18973
+ * Returns the indexed pool's aggregate buy and sell rates in less-valued
18974
+ * quote-token units per one base token.
18351
18975
  *
18352
- * The requested direction is read on the destination chain; its reverse is
18353
- * read on the source chain. This mirrors where each direction's output token
18354
- * must be delivered for a cross-chain trade.
18976
+ * The indexer depth-weights fresh per-chain samples into the pool rates. The
18977
+ * source and destination chains remain part of the result because they define
18978
+ * the cross-chain route whose configured token symbols were resolved.
18355
18979
  */
18356
18980
  async getBuyAndSellRates(params) {
18357
18981
  const pool = resolveLiquidityPool(params.tokenInSymbol, params.tokenOutSymbol);
18358
- const directDirection = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase() ? SELL : BUY;
18359
- const reverseDirection = directDirection === SELL ? BUY : SELL;
18360
18982
  const response = await this.queryClient.request(BUY_AND_SELL_RATES, {
18361
- poolId: pool.poolId,
18362
- directChain: params.destinationChain,
18363
- directDirection,
18364
- reverseChain: params.sourceChain,
18365
- reverseDirection
18983
+ poolId: pool.poolId
18366
18984
  });
18367
- if (!response?.direct?.nodes || !response?.reverse?.nodes) {
18368
- throw new InvalidLiquidityIndexerResponseError("rate connections are missing");
18369
- }
18370
- const direct = readIndexedRate(response.direct.nodes[0], "direct rate");
18371
- const reverse = readIndexedRate(response.reverse.nodes[0], "reverse rate");
18985
+ if (!response?.liquidityPools?.nodes) {
18986
+ throw new InvalidLiquidityIndexerResponseError("liquidity pool connection is missing");
18987
+ }
18988
+ const indexedPool = response.liquidityPools.nodes[0];
18989
+ if (!indexedPool) return void 0;
18990
+ validateIndexedPool(indexedPool, pool);
18991
+ const sell = readIndexedRate(indexedPool.sellRate, indexedPool.lastUpdatedAt, "pool sell rate");
18992
+ const buy = readIndexedRate(indexedPool.buyRate, indexedPool.lastUpdatedAt, "pool buy rate");
18993
+ const inputIsToken0 = params.tokenInSymbol.toLowerCase() === pool.token0Symbol.toLowerCase();
18994
+ const direct = inputIsToken0 ? sell : buy;
18995
+ const reverse = inputIsToken0 ? buy : sell;
18372
18996
  if (!direct && !reverse) return void 0;
18373
18997
  const quoteTokenSymbol = resolveQuoteTokenSymbol(
18374
18998
  params.tokenInSymbol,
@@ -18377,17 +19001,17 @@ var LiquidityEngine = class {
18377
19001
  reverse?.scaledRate
18378
19002
  );
18379
19003
  const quoteIsTokenOut = quoteTokenSymbol === params.tokenOutSymbol;
18380
- const buy = quoteIsTokenOut ? direct : reverse;
18381
- const sell = quoteIsTokenOut ? reverse : direct;
19004
+ const orientedBuy = quoteIsTokenOut ? direct : reverse;
19005
+ const orientedSell = quoteIsTokenOut ? reverse : direct;
18382
19006
  return {
18383
19007
  baseTokenSymbol: quoteIsTokenOut ? params.tokenInSymbol : params.tokenOutSymbol,
18384
19008
  quoteTokenSymbol,
18385
19009
  sourceChain: params.sourceChain,
18386
19010
  destinationChain: params.destinationChain,
18387
- buyRate: buy ? viem.formatUnits(buy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
18388
- sellRate: sell ? viem.formatUnits(reciprocalRate(sell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
18389
- buyRateUpdatedAt: buy?.updatedAt ?? null,
18390
- sellRateUpdatedAt: sell?.updatedAt ?? null
19011
+ buyRate: orientedBuy ? viem.formatUnits(orientedBuy.scaledRate, INDEXER_FIXED_POINT_DECIMALS) : null,
19012
+ sellRate: orientedSell ? viem.formatUnits(reciprocalRate(orientedSell.scaledRate, "sell rate"), INDEXER_FIXED_POINT_DECIMALS) : null,
19013
+ buyRateUpdatedAt: orientedBuy?.updatedAt ?? null,
19014
+ sellRateUpdatedAt: orientedSell?.updatedAt ?? null
18391
19015
  };
18392
19016
  }
18393
19017
  };
@@ -18429,20 +19053,25 @@ function readIndexerDate(value, label) {
18429
19053
  if (Number.isNaN(date.getTime())) throw new InvalidLiquidityIndexerResponseError(`${label} is invalid`);
18430
19054
  return date;
18431
19055
  }
18432
- function readIndexedRate(node, label) {
18433
- if (!node) return void 0;
19056
+ function readIndexedRate(value, lastUpdatedAt, label) {
19057
+ if (value === null) return void 0;
18434
19058
  try {
18435
- const scaledRate = BigInt(node.rate);
19059
+ const scaledRate = BigInt(value);
18436
19060
  if (scaledRate <= 0n) throw new Error();
18437
19061
  return {
18438
19062
  scaledRate,
18439
- updatedAt: readIndexerDate(node.lastUpdatedAt, `${label} lastUpdatedAt`)
19063
+ updatedAt: readIndexerDate(lastUpdatedAt, `${label} lastUpdatedAt`)
18440
19064
  };
18441
19065
  } catch (error) {
18442
19066
  if (error instanceof InvalidLiquidityIndexerResponseError) throw error;
18443
19067
  throw new InvalidLiquidityIndexerResponseError(`${label} is not a positive integer`);
18444
19068
  }
18445
19069
  }
19070
+ function validateIndexedPool(indexedPool, expected) {
19071
+ if (indexedPool.id.toLowerCase() !== expected.poolId.toLowerCase() || indexedPool.token0Symbol.toLowerCase() !== expected.token0Symbol.toLowerCase() || indexedPool.token1Symbol.toLowerCase() !== expected.token1Symbol.toLowerCase()) {
19072
+ throw new InvalidLiquidityIndexerResponseError(`pool identity does not match ${expected.poolId}`);
19073
+ }
19074
+ }
18446
19075
  function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reverseRate) {
18447
19076
  const inputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenInSymbol);
18448
19077
  const outputIsUsdStable = USD_STABLE_SYMBOLS.has(tokenOutSymbol);
@@ -18452,7 +19081,8 @@ function resolveQuoteTokenSymbol(tokenInSymbol, tokenOutSymbol, directRate, reve
18452
19081
  throw new InvalidLiquidityIndexerResponseError("cannot orient an empty rate pair");
18453
19082
  }
18454
19083
  function reciprocalRate(rate, label) {
18455
- const reciprocal = POOL_RATE_SCALE * POOL_RATE_SCALE / rate;
19084
+ const numerator = POOL_RATE_SCALE * POOL_RATE_SCALE;
19085
+ const reciprocal = (numerator + rate - 1n) / rate;
18456
19086
  if (reciprocal <= 0n) throw new InvalidLiquidityIndexerResponseError(`${label} reciprocal underflowed`);
18457
19087
  return reciprocal;
18458
19088
  }
@@ -18484,6 +19114,20 @@ var InvalidPhantomSnapshotError = class extends Error {
18484
19114
  this.name = "InvalidPhantomSnapshotError";
18485
19115
  }
18486
19116
  };
19117
+ var IndexedRateUnavailableError = class extends Error {
19118
+ constructor(params) {
19119
+ const route = params.source && params.destination && params.tokenIn && params.tokenOut ? ` for ${params.tokenIn} -> ${params.tokenOut} on ${params.source} -> ${params.destination}` : "";
19120
+ const side = params.side ? ` ${params.side}` : "";
19121
+ super(`No indexed${side} rate available${route}`);
19122
+ this.name = "IndexedRateUnavailableError";
19123
+ }
19124
+ };
19125
+ var InvalidIndexedRateError = class extends Error {
19126
+ constructor(reason) {
19127
+ super(`Invalid indexed intent rate: ${reason}`);
19128
+ this.name = "InvalidIndexedRateError";
19129
+ }
19130
+ };
18487
19131
  var BPS_DENOMINATOR = 10000n;
18488
19132
  function validateQuoteParams(params) {
18489
19133
  const hasAmountIn = params.amountIn !== void 0;
@@ -18850,9 +19494,142 @@ function isSupportedSnapshotPair(tokenA, tokenB) {
18850
19494
  function isConfiguredAddress(address) {
18851
19495
  return Boolean(address && address !== "0x" && !/^0x0{40}$/i.test(address));
18852
19496
  }
19497
+ var INDEXED_RATE_DECIMALS = 18;
19498
+ var INDEXED_RATE_SCALE = 10n ** BigInt(INDEXED_RATE_DECIMALS);
19499
+ var IndexedRateIntentQuoteStrategy = class {
19500
+ constructor(chainConfigService, getQueryClient) {
19501
+ this.chainConfigService = chainConfigService;
19502
+ this.getQueryClient = getQueryClient;
19503
+ }
19504
+ chainConfigService;
19505
+ getQueryClient;
19506
+ async quote(params, source, destination) {
19507
+ validateQuoteParams(params);
19508
+ const sourceConfig = getConfigByStateMachineId(source.stateMachineId);
19509
+ const destinationConfig = getConfigByStateMachineId(destination.stateMachineId);
19510
+ if (!sourceConfig) throw new UnsupportedLiquidityChainError(source.stateMachineId);
19511
+ if (!destinationConfig) throw new UnsupportedLiquidityChainError(destination.stateMachineId);
19512
+ const tokenIn = this.resolveAsset(sourceConfig.stateMachineId, params.tokenIn);
19513
+ const tokenOut = this.resolveAsset(destinationConfig.stateMachineId, params.tokenOut);
19514
+ const [protocolFeeBps, rates] = await Promise.all([
19515
+ readProtocolFeeBps(this.chainConfigService, source),
19516
+ new LiquidityEngine(this.getQueryClient()).getBuyAndSellRates({
19517
+ sourceChain: sourceConfig.stateMachineId,
19518
+ destinationChain: destinationConfig.stateMachineId,
19519
+ tokenInSymbol: tokenIn.symbol,
19520
+ tokenOutSymbol: tokenOut.symbol
19521
+ })
19522
+ ]);
19523
+ if (!rates) {
19524
+ throw new IndexedRateUnavailableError({
19525
+ source: sourceConfig.stateMachineId,
19526
+ destination: destinationConfig.stateMachineId,
19527
+ tokenIn: tokenIn.symbol,
19528
+ tokenOut: tokenOut.symbol
19529
+ });
19530
+ }
19531
+ const selectedRate = selectIndexedRate(rates, tokenIn.symbol, tokenOut.symbol);
19532
+ return quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps);
19533
+ }
19534
+ resolveAsset(chain, address) {
19535
+ const asset = this.chainConfigService.getAssetMetadataByAddress(chain, address);
19536
+ if (!asset) throw new UnsupportedLiquidityAssetError(chain, address);
19537
+ const { decimals } = asset;
19538
+ if (decimals === void 0 || !Number.isSafeInteger(decimals) || decimals < 0) {
19539
+ throw new InvalidIndexedRateError(`decimals are not configured for ${asset.symbol} on ${chain}`);
19540
+ }
19541
+ return { ...asset, decimals };
19542
+ }
19543
+ };
19544
+ function selectIndexedRate(rates, tokenInSymbol, tokenOutSymbol) {
19545
+ if (tokenInSymbol === rates.baseTokenSymbol && tokenOutSymbol === rates.quoteTokenSymbol) {
19546
+ return readIndexedRate2(
19547
+ "buy",
19548
+ rates.buyRate,
19549
+ rates.buyRateUpdatedAt,
19550
+ rates,
19551
+ tokenInSymbol,
19552
+ tokenOutSymbol
19553
+ );
19554
+ }
19555
+ if (tokenInSymbol === rates.quoteTokenSymbol && tokenOutSymbol === rates.baseTokenSymbol) {
19556
+ return readIndexedRate2(
19557
+ "sell",
19558
+ rates.sellRate,
19559
+ rates.sellRateUpdatedAt,
19560
+ rates,
19561
+ tokenInSymbol,
19562
+ tokenOutSymbol
19563
+ );
19564
+ }
19565
+ throw new InvalidIndexedRateError(
19566
+ `indexed pair ${rates.baseTokenSymbol}/${rates.quoteTokenSymbol} does not match ${tokenInSymbol}/${tokenOutSymbol}`
19567
+ );
19568
+ }
19569
+ function readIndexedRate2(side, rate, updatedAt, rates, tokenInSymbol, tokenOutSymbol) {
19570
+ if (!rate || !updatedAt) {
19571
+ throw new IndexedRateUnavailableError({
19572
+ source: rates.sourceChain,
19573
+ destination: rates.destinationChain,
19574
+ tokenIn: tokenInSymbol,
19575
+ tokenOut: tokenOutSymbol,
19576
+ side
19577
+ });
19578
+ }
19579
+ try {
19580
+ const scaledRate = viem.parseUnits(rate, INDEXED_RATE_DECIMALS);
19581
+ if (scaledRate <= 0n || Number.isNaN(updatedAt.getTime())) throw new Error();
19582
+ return { side, rate, scaledRate, updatedAt };
19583
+ } catch {
19584
+ throw new InvalidIndexedRateError(`${side} rate or timestamp is invalid`);
19585
+ }
19586
+ }
19587
+ function quoteWithIndexedRate(params, tokenIn, tokenOut, selectedRate, rates, protocolFeeBps) {
19588
+ const inputUnit = 10n ** BigInt(tokenIn.decimals);
19589
+ const outputUnit = 10n ** BigInt(tokenOut.decimals);
19590
+ if (params.amountIn !== void 0) {
19591
+ const netAmountIn2 = deductProtocolFee(params.amountIn, protocolFeeBps);
19592
+ const amountOut = selectedRate.side === "buy" ? netAmountIn2 * selectedRate.scaledRate * outputUnit / (inputUnit * INDEXED_RATE_SCALE) : netAmountIn2 * outputUnit * INDEXED_RATE_SCALE / (inputUnit * selectedRate.scaledRate);
19593
+ if (amountOut <= 0n) throw new InvalidIndexedRateError("quote rounds down to zero output");
19594
+ return buildResult("EXACT_INPUT", params.amountIn, amountOut, selectedRate, rates, protocolFeeBps);
19595
+ }
19596
+ if (params.amountOut === void 0) throw new Error("Quote amount is missing after validation");
19597
+ const netAmountIn = selectedRate.side === "buy" ? divCeil(params.amountOut * inputUnit * INDEXED_RATE_SCALE, selectedRate.scaledRate * outputUnit) : divCeil(params.amountOut * inputUnit * selectedRate.scaledRate, outputUnit * INDEXED_RATE_SCALE);
19598
+ const amountIn = grossUpForProtocolFee(netAmountIn, protocolFeeBps);
19599
+ return buildResult("EXACT_OUTPUT", amountIn, params.amountOut, selectedRate, rates, protocolFeeBps);
19600
+ }
19601
+ function buildResult(tradeType, amountIn, amountOut, selectedRate, rates, protocolFeeBps) {
19602
+ return {
19603
+ strategy: "indexed_rates",
19604
+ tradeType,
19605
+ amountIn,
19606
+ amountOut,
19607
+ quoteMetadata: {
19608
+ sourceChain: rates.sourceChain,
19609
+ destinationChain: rates.destinationChain,
19610
+ baseTokenSymbol: rates.baseTokenSymbol,
19611
+ quoteTokenSymbol: rates.quoteTokenSymbol,
19612
+ rateSide: selectedRate.side,
19613
+ rate: selectedRate.rate,
19614
+ rateUpdatedAt: selectedRate.updatedAt,
19615
+ protocolFeeBps
19616
+ }
19617
+ };
19618
+ }
18853
19619
 
18854
19620
  // src/protocols/intents/IntentGateway.ts
18855
- var CROSS_CHAIN_ORDER_FEE_GAS_PRICE_BUMP_PERCENT = 10n;
19621
+ var ORDER_FEE_GAS_PRICE_BUMP_POLICY = {
19622
+ defaultPercent: 10n,
19623
+ bySourceStateMachineId: {
19624
+ ["EVM-1" /* MAINNET */]: 50n
19625
+ }
19626
+ };
19627
+ function resolveOrderFeeGasPriceBump(sourceStateMachineId, isSameChain) {
19628
+ if (isSameChain) {
19629
+ return 0n;
19630
+ }
19631
+ return ORDER_FEE_GAS_PRICE_BUMP_POLICY.bySourceStateMachineId[sourceStateMachineId] ?? ORDER_FEE_GAS_PRICE_BUMP_POLICY.defaultPercent;
19632
+ }
18856
19633
  var IntentGateway = class _IntentGateway {
18857
19634
  /** EVM chain on which orders are placed and escrowed. */
18858
19635
  source;
@@ -18924,6 +19701,10 @@ var IntentGateway = class _IntentGateway {
18924
19701
  this.gasEstimator = gasEstimator;
18925
19702
  this._crypto = crypto;
18926
19703
  this.quoteStrategies = {
19704
+ indexed_rates: new IndexedRateIntentQuoteStrategy(
19705
+ dest.configService,
19706
+ () => this.requireIndexer().queryClient
19707
+ ),
18927
19708
  phantom_snapshot: new PhantomSnapshotIntentQuoteStrategy(
18928
19709
  dest.configService,
18929
19710
  () => this.requireIndexer().queryClient
@@ -18977,26 +19758,26 @@ var IntentGateway = class _IntentGateway {
18977
19758
  /**
18978
19759
  * Quotes an intent between this gateway's source and destination chains.
18979
19760
  *
18980
- * Uses the latest directional Phantom order price snapshot from the attached
18981
- * indexer by default. Pass `strategy: "uniswap_v4"` only when explicitly
18982
- * requesting a Uniswap quote. Provide exactly one of `amountIn` or `amountOut`.
19761
+ * Uses the indexer's latest aggregate directional pool rate by default. Pass
19762
+ * `strategy: "phantom_snapshot"` or `strategy: "uniswap_v4"` only when
19763
+ * explicitly requesting a legacy quote source. Provide exactly one of
19764
+ * `amountIn` or `amountOut`.
18983
19765
  *
18984
- * Both built-in strategies resolve their canonical market on Base,
18985
- * regardless of this gateway's destination chain. Returned
19766
+ * The gateway's source and destination chains resolve the configured order
19767
+ * tokens; the indexer supplies the depth-weighted pool rate. Returned
18986
19768
  * `amountIn`/`amountOut` already account for the gateway's protocol fee
18987
- * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order
18988
- * inputs; use the returned amounts directly when placing the order.
19769
+ * (`quoteMetadata.protocolFeeBps`), which the gateway deducts from order inputs.
18989
19770
  *
18990
19771
  * @param params - Token pair, amount, and optional strategy/pool overrides.
18991
19772
  * @returns The quoted amounts plus strategy-specific metadata.
18992
19773
  * @throws {UnsupportedIntentQuoteStrategyError} For unknown strategies.
18993
19774
  * @throws {UnsupportedIntentQuotePairError} When the selected strategy does not support the pair.
18994
- * @throws {PhantomSnapshotUnavailableError} When an eligible cNGN pair has no snapshot.
19775
+ * @throws {IndexedRateUnavailableError} When the requested direction has no indexed rate.
18995
19776
  */
18996
19777
  async quoteIntent(params) {
18997
19778
  const source = { stateMachineId: this.source.config.stateMachineId, client: this.source.client };
18998
19779
  const destination = { stateMachineId: this.dest.config.stateMachineId, client: this.dest.client };
18999
- const strategy = params.strategy ?? "phantom_snapshot";
19780
+ const strategy = params.strategy ?? "indexed_rates";
19000
19781
  const handler = this.quoteStrategies[strategy];
19001
19782
  if (!handler) throw new UnsupportedIntentQuoteStrategyError(strategy);
19002
19783
  return handler.quote({ ...params, strategy }, source, destination);
@@ -19036,9 +19817,9 @@ var IntentGateway = class _IntentGateway {
19036
19817
  });
19037
19818
  }
19038
19819
  /**
19039
- * Returns chain-specific buy and sell rates in less-valued quote-token units
19040
- * without requiring token addresses. Symbols are matched case-insensitively;
19041
- * chain IDs are numeric IDs for chains configured in the SDK.
19820
+ * Returns aggregate indexed pool buy and sell rates in less-valued quote-token
19821
+ * units without requiring token addresses. Symbols are matched
19822
+ * case-insensitively; chain IDs resolve configured token deployments.
19042
19823
  */
19043
19824
  async queryBuyAndSellRates(params) {
19044
19825
  const { queryClient } = this.requireIndexer();
@@ -19067,8 +19848,9 @@ var IntentGateway = class _IntentGateway {
19067
19848
  * **Yield/receive protocol:**
19068
19849
  * 1. If `order.fees` is unset or zero, prices the fee on an internal copy
19069
19850
  * via {@link quoteOrderFees}: same-chain fees are twice the fill-gas
19070
- * estimate without a gas-price bump; cross-chain gas is priced 10% above
19071
- * the live price before attaching (fill gas + the settlement relayer fee)
19851
+ * estimate without a gas-price bump; cross-chain order fees originating on
19852
+ * Ethereum price gas 50% above the live price, while other source chains use
19853
+ * 10%, before attaching (fill gas + the settlement relayer fee)
19072
19854
  * with a further 5% buffer over the whole sum — strictly above the solver's
19073
19855
  * unpadded requirement. Direct solver estimates remain unbumped. The wei
19074
19856
  * cost used for the `value` field receives a 2% buffer.
@@ -19438,7 +20220,8 @@ var IntentGateway = class _IntentGateway {
19438
20220
  * transaction (check the native balance).
19439
20221
  *
19440
20222
  * @param order - The order to quote. `order.fees` is ignored and not mutated.
19441
- * Gas prices used to derive cross-chain `fees` receive 10% SDK-only headroom.
20223
+ * Gas prices used to derive cross-chain `fees` receive 50% SDK-only headroom
20224
+ * when the source chain is Ethereum mainnet and 10% for other source chains.
19442
20225
  * Same-chain quotes and direct calls to {@link estimateFillOrder}, including
19443
20226
  * Simplex solver estimates, remain unbumped.
19444
20227
  *
@@ -19449,15 +20232,14 @@ var IntentGateway = class _IntentGateway {
19449
20232
  */
19450
20233
  async quoteOrderFees(order, options) {
19451
20234
  const isSameChain = this.source.config.stateMachineId === this.dest.config.stateMachineId;
20235
+ const orderFeeGasPriceBumpPercent = resolveOrderFeeGasPriceBump(this.source.config.stateMachineId, isSameChain);
19452
20236
  const estimate = await this.gasEstimator.estimateFillOrder(
19453
20237
  {
19454
20238
  order,
19455
20239
  maxPriorityFeePerGasBumpPercent: options?.maxPriorityFeePerGasBumpPercent,
19456
20240
  maxFeePerGasBumpPercent: options?.maxFeePerGasBumpPercent
19457
20241
  },
19458
- {
19459
- orderFeeGasPriceBumpPercent: isSameChain ? 0n : CROSS_CHAIN_ORDER_FEE_GAS_PRICE_BUMP_PERCENT
19460
- }
20242
+ { orderFeeGasPriceBumpPercent }
19461
20243
  );
19462
20244
  if (estimate.totalGasCostWei === 0n || estimate.totalGasInFeeToken === 0n) {
19463
20245
  throw new Error("Gas estimation failed");
@@ -19686,6 +20468,17 @@ function encodeAcceptedSourceChains(chains2) {
19686
20468
  function decodeAcceptedSourceChains(paymasterAndData) {
19687
20469
  return decodePhantomBidDeclaration(paymasterAndData).acceptedSources;
19688
20470
  }
20471
+ var UNISWAP_QUOTE_HAIRCUT_BPS = 10n;
20472
+ var PHANTOM_QUOTE_HAIRCUT_BPS = 5n;
20473
+ function haircut(amount, bps) {
20474
+ return amount * (10000n - bps) / 10000n;
20475
+ }
20476
+ function applyUniswapQuoteHaircut(amount) {
20477
+ return haircut(amount, UNISWAP_QUOTE_HAIRCUT_BPS);
20478
+ }
20479
+ function applyPhantomQuoteHaircut(amount) {
20480
+ return haircut(amount, PHANTOM_QUOTE_HAIRCUT_BPS);
20481
+ }
19689
20482
  FILL_ORDER_ABI.find(
19690
20483
  (item) => item?.type === "function" && item?.name === "fillOrder"
19691
20484
  )?.inputs?.[0];
@@ -24161,6 +24954,7 @@ async function teleportDot(param_) {
24161
24954
 
24162
24955
  exports.ADDRESS_ZERO = ADDRESS_ZERO2;
24163
24956
  exports.BundlerMethod = BundlerMethod;
24957
+ exports.CHAINS_WITHOUT_VALID_UNTIL = CHAINS_WITHOUT_VALID_UNTIL;
24164
24958
  exports.ChainConfigService = ChainConfigService;
24165
24959
  exports.Chains = Chains;
24166
24960
  exports.CryptoUtils = CryptoUtils;
@@ -24173,22 +24967,27 @@ exports.ERC7821_BATCH_MODE = ERC7821_BATCH_MODE;
24173
24967
  exports.EvmChain = EvmChain;
24174
24968
  exports.EvmHostABI = ABI;
24175
24969
  exports.EvmLanguage = EvmLanguage;
24970
+ exports.FILL_ORDER_V1_ABI = FILL_ORDER_V1_ABI;
24176
24971
  exports.HyperClientStatus = HyperClientStatus;
24177
24972
  exports.HyperFungibleToken = HyperFungibleToken;
24178
24973
  exports.HyperFungibleTokenABI = HyperFungibleTokenABI;
24179
24974
  exports.INCLUSION_TIMEOUT_MS = INCLUSION_TIMEOUT_MS;
24975
+ exports.IndexedRateUnavailableError = IndexedRateUnavailableError;
24180
24976
  exports.IntentGateway = IntentGateway;
24181
24977
  exports.IntentGatewayABI = ABI3;
24182
24978
  exports.IntentOrderStatus = IntentOrderStatus;
24183
24979
  exports.IntentsCoprocessor = IntentsCoprocessor;
24980
+ exports.InvalidIndexedRateError = InvalidIndexedRateError;
24184
24981
  exports.InvalidLiquidityIndexerResponseError = InvalidLiquidityIndexerResponseError;
24185
24982
  exports.InvalidPhantomSnapshotError = InvalidPhantomSnapshotError;
24186
24983
  exports.IsmpClient = IsmpClient;
24984
+ exports.LEGACY_FILL_OPTIONS_IMPLEMENTATIONS = LEGACY_FILL_OPTIONS_IMPLEMENTATIONS;
24187
24985
  exports.MOCK_ADDRESS = MOCK_ADDRESS;
24188
24986
  exports.ORDER_V2_PARAM_TYPE = ORDER_V2_PARAM_TYPE;
24189
24987
  exports.OrderStatus = OrderStatus;
24190
24988
  exports.OrderStatusChecker = OrderStatusChecker;
24191
24989
  exports.PACKED_USEROP_TYPEHASH = PACKED_USEROP_TYPEHASH;
24990
+ exports.PHANTOM_QUOTE_HAIRCUT_BPS = PHANTOM_QUOTE_HAIRCUT_BPS;
24192
24991
  exports.PLACE_ORDER_SELECTOR = PLACE_ORDER_SELECTOR;
24193
24992
  exports.PhantomSnapshotUnavailableError = PhantomSnapshotUnavailableError;
24194
24993
  exports.PharosChain = PharosChain;
@@ -24208,6 +25007,7 @@ exports.TeleportStatus = TeleportStatus;
24208
25007
  exports.TimeoutStatus = TimeoutStatus;
24209
25008
  exports.TokenGateway = TokenGateway;
24210
25009
  exports.TronChain = TronChain;
25010
+ exports.UNISWAP_QUOTE_HAIRCUT_BPS = UNISWAP_QUOTE_HAIRCUT_BPS;
24211
25011
  exports.USE_ETHERSCAN_CHAINS = USE_ETHERSCAN_CHAINS;
24212
25012
  exports.UnsupportedIntentQuotePairError = UnsupportedIntentQuotePairError;
24213
25013
  exports.UnsupportedIntentQuoteStrategyError = UnsupportedIntentQuoteStrategyError;
@@ -24216,6 +25016,8 @@ exports.UnsupportedLiquidityChainError = UnsupportedLiquidityChainError;
24216
25016
  exports.WrappedHyperFungibleTokenABI = WrappedHyperFungibleTokenABI;
24217
25017
  exports.__test = __test;
24218
25018
  exports.adjustDecimals = adjustDecimals;
25019
+ exports.applyPhantomQuoteHaircut = applyPhantomQuoteHaircut;
25020
+ exports.applyUniswapQuoteHaircut = applyUniswapQuoteHaircut;
24219
25021
  exports.bytes20ToBytes32 = bytes20ToBytes32;
24220
25022
  exports.bytes32ToBytes20 = bytes32ToBytes20;
24221
25023
  exports.calculateAllowanceMappingLocation = calculateAllowanceMappingLocation;
@@ -24234,11 +25036,13 @@ exports.createEvmChain = createEvmChain;
24234
25036
  exports.createQueryClient = createQueryClient;
24235
25037
  exports.decodeAcceptedSourceChains = decodeAcceptedSourceChains;
24236
25038
  exports.decodeERC7821ExecuteBatch = decodeERC7821ExecuteBatch;
25039
+ exports.decodeFillOrder = decodeFillOrder;
24237
25040
  exports.decodePhantomBidDeclaration = decodePhantomBidDeclaration;
24238
25041
  exports.decodeUserOpScale = decodeUserOpScale;
24239
25042
  exports.deriveHttpUrl = deriveHttpUrl;
24240
25043
  exports.encodeAcceptedSourceChains = encodeAcceptedSourceChains;
24241
25044
  exports.encodeERC7821ExecuteBatch = encodeERC7821ExecuteBatch;
25045
+ exports.encodeFillOrder = encodeFillOrder;
24242
25046
  exports.encodeISMPMessage = encodeISMPMessage;
24243
25047
  exports.encodePhantomBidDeclaration = encodePhantomBidDeclaration;
24244
25048
  exports.encodeStateMachineId = encodeStateMachineId;
@@ -24252,6 +25056,7 @@ exports.getChainId = getChainId;
24252
25056
  exports.getConfigByStateMachineId = getConfigByStateMachineId;
24253
25057
  exports.getContractCallInput = getContractCallInput;
24254
25058
  exports.getContractCallInputs = getContractCallInputs;
25059
+ exports.getFillOptionsVersion = getFillOptionsVersion;
24255
25060
  exports.getGasPriceFromEtherscan = getGasPriceFromEtherscan;
24256
25061
  exports.getOrFetchStorageSlot = getOrFetchStorageSlot;
24257
25062
  exports.getOrderPlacedFromTx = getOrderPlacedFromTx;
@@ -24283,6 +25088,7 @@ exports.queryGetRequest = queryGetRequest;
24283
25088
  exports.queryPostRequest = queryPostRequest;
24284
25089
  exports.quoteUniswap = quoteUniswap;
24285
25090
  exports.requestCommitmentKey = requestCommitmentKey;
25091
+ exports.resetFillOptionsVersionCache = resetFillOptionsVersionCache;
24286
25092
  exports.responseCommitmentKey = responseCommitmentKey;
24287
25093
  exports.retryPromise = retryPromise;
24288
25094
  exports.sortPoolSymbols = sortPoolSymbols;