@hyperbridge/simplex 0.16.6 → 0.16.7

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.
@@ -70714,13 +70714,13 @@ var require_ponyfill_es2018 = __commonJS({
70714
70714
  }
70715
70715
  function convertReadableWritablePair(pair, context) {
70716
70716
  assertDictionary(pair, context);
70717
- const readable = pair === null || pair === void 0 ? void 0 : pair.readable;
70718
- assertRequiredField(readable, "readable", "ReadableWritablePair");
70719
- assertReadableStream(readable, `${context} has member 'readable' that`);
70717
+ const readable2 = pair === null || pair === void 0 ? void 0 : pair.readable;
70718
+ assertRequiredField(readable2, "readable", "ReadableWritablePair");
70719
+ assertReadableStream(readable2, `${context} has member 'readable' that`);
70720
70720
  const writable = pair === null || pair === void 0 ? void 0 : pair.writable;
70721
70721
  assertRequiredField(writable, "writable", "ReadableWritablePair");
70722
70722
  assertWritableStream(writable, `${context} has member 'writable' that`);
70723
- return { readable, writable };
70723
+ return { readable: readable2, writable };
70724
70724
  }
70725
70725
  class ReadableStream2 {
70726
70726
  constructor(rawUnderlyingSource = {}, rawStrategy = {}) {
@@ -71399,7 +71399,7 @@ var require_ponyfill_es2018 = __commonJS({
71399
71399
  if (controller._finishPromise !== void 0) {
71400
71400
  return controller._finishPromise;
71401
71401
  }
71402
- const readable = stream4._readable;
71402
+ const readable2 = stream4._readable;
71403
71403
  controller._finishPromise = newPromise((resolve11, reject) => {
71404
71404
  controller._finishPromise_resolve = resolve11;
71405
71405
  controller._finishPromise_reject = reject;
@@ -71407,15 +71407,15 @@ var require_ponyfill_es2018 = __commonJS({
71407
71407
  const cancelPromise = controller._cancelAlgorithm(reason);
71408
71408
  TransformStreamDefaultControllerClearAlgorithms(controller);
71409
71409
  uponPromise(cancelPromise, () => {
71410
- if (readable._state === "errored") {
71411
- defaultControllerFinishPromiseReject(controller, readable._storedError);
71410
+ if (readable2._state === "errored") {
71411
+ defaultControllerFinishPromiseReject(controller, readable2._storedError);
71412
71412
  } else {
71413
- ReadableStreamDefaultControllerError(readable._readableStreamController, reason);
71413
+ ReadableStreamDefaultControllerError(readable2._readableStreamController, reason);
71414
71414
  defaultControllerFinishPromiseResolve(controller);
71415
71415
  }
71416
71416
  return null;
71417
71417
  }, (r7) => {
71418
- ReadableStreamDefaultControllerError(readable._readableStreamController, r7);
71418
+ ReadableStreamDefaultControllerError(readable2._readableStreamController, r7);
71419
71419
  defaultControllerFinishPromiseReject(controller, r7);
71420
71420
  return null;
71421
71421
  });
@@ -71426,7 +71426,7 @@ var require_ponyfill_es2018 = __commonJS({
71426
71426
  if (controller._finishPromise !== void 0) {
71427
71427
  return controller._finishPromise;
71428
71428
  }
71429
- const readable = stream4._readable;
71429
+ const readable2 = stream4._readable;
71430
71430
  controller._finishPromise = newPromise((resolve11, reject) => {
71431
71431
  controller._finishPromise_resolve = resolve11;
71432
71432
  controller._finishPromise_reject = reject;
@@ -71434,15 +71434,15 @@ var require_ponyfill_es2018 = __commonJS({
71434
71434
  const flushPromise = controller._flushAlgorithm();
71435
71435
  TransformStreamDefaultControllerClearAlgorithms(controller);
71436
71436
  uponPromise(flushPromise, () => {
71437
- if (readable._state === "errored") {
71438
- defaultControllerFinishPromiseReject(controller, readable._storedError);
71437
+ if (readable2._state === "errored") {
71438
+ defaultControllerFinishPromiseReject(controller, readable2._storedError);
71439
71439
  } else {
71440
- ReadableStreamDefaultControllerClose(readable._readableStreamController);
71440
+ ReadableStreamDefaultControllerClose(readable2._readableStreamController);
71441
71441
  defaultControllerFinishPromiseResolve(controller);
71442
71442
  }
71443
71443
  return null;
71444
71444
  }, (r7) => {
71445
- ReadableStreamDefaultControllerError(readable._readableStreamController, r7);
71445
+ ReadableStreamDefaultControllerError(readable2._readableStreamController, r7);
71446
71446
  defaultControllerFinishPromiseReject(controller, r7);
71447
71447
  return null;
71448
71448
  });
@@ -214231,13 +214231,19 @@ function describeError(error) {
214231
214231
  if (graphqlErrors?.length) return graphqlErrors.map((e4) => e4.message).join("; ");
214232
214232
  return error instanceof Error ? error.message : String(error);
214233
214233
  }
214234
+ function sameSymbol(a6, b5) {
214235
+ return a6.toLowerCase() === b5.toLowerCase();
214236
+ }
214234
214237
  function findBook(books, tokenA, tokenB) {
214235
214238
  const book = books.find(
214236
- (b5) => b5.base === tokenA && b5.quote === tokenB || b5.base === tokenB && b5.quote === tokenA
214239
+ (b5) => sameSymbol(b5.base, tokenA) && sameSymbol(b5.quote, tokenB) || sameSymbol(b5.base, tokenB) && sameSymbol(b5.quote, tokenA)
214237
214240
  );
214238
214241
  if (!book) throw new OrderbookRequestError(`no book trades ${tokenA} for ${tokenB}`);
214239
214242
  return book;
214240
214243
  }
214244
+ function optionalBigInt(value) {
214245
+ return value === null ? null : BigInt(value);
214246
+ }
214241
214247
  function parseRate(raw) {
214242
214248
  return {
214243
214249
  side: raw.side,
@@ -214282,24 +214288,32 @@ function toRoute(tokenIn, tokenOut) {
214282
214288
  function requiredInput(side, amountOut, rate) {
214283
214289
  return side === "BID" ? divCeil(amountOut * ORDERBOOK_SCALE, rate) : divCeil(amountOut * rate, ORDERBOOK_SCALE);
214284
214290
  }
214285
- function buildQuote(tradeType, amountIn, amountOut, quote, tokenIn, tokenOut) {
214286
- if (quote.rate === null) throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
214287
- const [base32, quoteToken] = quote.side === "BID" ? [tokenIn, tokenOut] : [tokenOut, tokenIn];
214291
+ function swapQuoteTotals(quote) {
214292
+ const worstFill = quote.fills.at(-1);
214293
+ if (quote.fillable && !worstFill)
214294
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no fills");
214288
214295
  return {
214289
- tradeType,
214290
- amountIn,
214291
- amountOut,
214292
- quoteMetadata: {
214293
- sourceChain: tokenIn.chain,
214294
- destinationChain: tokenOut.chain,
214295
- route: quote.route,
214296
- side: quote.side,
214297
- baseTokenSymbol: base32.symbol,
214298
- quoteTokenSymbol: quoteToken.symbol,
214299
- rate: format(quote.rate),
214300
- maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
214301
- orderCount: quote.fills.length
214302
- }
214296
+ side: quote.side,
214297
+ amountIn: quote.amountIn,
214298
+ amountOut: quote.fills.reduce((total, fill) => total + fill.amountOut, 0n),
214299
+ fillable: quote.fillable,
214300
+ maxFillableIn: quote.maxFillableIn,
214301
+ worstIn: worstFill?.amountIn ?? 0n,
214302
+ worstOut: worstFill?.amountOut ?? 0n
214303
+ };
214304
+ }
214305
+ function levelQuoteTotals(quote) {
214306
+ if (quote.fillable && quote.rate === null) {
214307
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
214308
+ }
214309
+ return {
214310
+ side: quote.side,
214311
+ amountIn: quote.amountIn,
214312
+ amountOut: quote.amountOut,
214313
+ fillable: quote.fillable,
214314
+ maxFillableIn: quote.maxFillableIn,
214315
+ worstIn: quote.amountIn,
214316
+ worstOut: quote.amountOut
214303
214317
  };
214304
214318
  }
214305
214319
  function toOrderbookAmount(amount, decimals) {
@@ -214385,7 +214399,7 @@ function encodePhantomBidDeclaration(declaration) {
214385
214399
  }
214386
214400
  return u8aToHex(new Uint8Array(bytes4));
214387
214401
  }
214388
- var import_merge_race, __defProp3, __getOwnPropNames2, __esm2, __export2, node_exports, wasm, full_path, __dirname$1, cachedTextDecoder, cachedUint8ArrayMemory0, WASM_VECTOR_LEN, cachedTextEncoder, encodeString5, cachedDataViewMemory0, KeccakMergeFinalization, KeccakMerge, wasmPath, wasmBytes2, bindings, wasmModule, wasmInstance, __wasm, init_node11, RequestStatus, TimeoutStatus, IntentOrderStatus, ABI, evmHost_default, ABI2, handlerV2_default, polkadotAssetHubPaseo, polkadotHubMainnet, pharosMainnet, pharosAtlantic, tronNile, chainConfigs, configsByStateMachineId, getConfigByStateMachineId, getChainId3, getViemChain, hyperbridgeAddress, ABI3, IntentGatewayV2_default, ChainConfigService, ABI4, uniswapRouterV2_default, chains, DEFAULT_ADDRESS, EvmChain, REQUEST_COMMITMENTS_SLOT, RESPONSE_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, STATE_COMMITMENTS_SLOT, MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, MissingConsensusUpdateTimeError, TokenBucket, BATCHES_NOT_SUPPORTED_CODE, TOO_BIG_BATCH_REQUEST_CODE, DEFAULT_MAX_BATCH_SIZE, _endpoint2, _headers3, _limiter, _maxBatchSize, _batchingSupported, _pending2, _flushTimer, _flushing, _nextId, __BatchingHttpProvider_instances, scheduleFlush_fn, flushNow_fn, flush_fn, post_fn, settleBatch_fn, settle_fn, handleBatchRefusal_fn, requeue_fn, _a5, BatchingHttpProvider, OFFCHAIN_BID_PREFIX, HYPERBRIDGE_TYPES_BUNDLE, BASE_TIP, HTTP_CONNECT_TIMEOUT_MS, INCLUSION_TIMEOUT_MS, DEFAULT_RPC_MAX_RPS, rpcLimiters, BidCodec, PackedUserOperationCodec, IntentsCoprocessor, ReviveContractInfo, ReviveAccountType, ReviveAccountInfo, H256, EvmStateProof, SubstrateHashing, SubstrateStateMachineProof, BasicProof, LeafIndexAndPos, MmrProof, ConsensusStateId, ConsensusMessage, FraudProofMessage, StateMachine, StateMachineId, StateMachineHeight, Proof, PostRequest, PostResponse, GetRequest, StorageValue, GetResponse, Request3, Response2, RequestMessage, RequestResponse, ResponseMessage, TimeoutMessage, GetRequestsWithProof, Message, DEFAULT_CHILD_STORAGE_PREFIX, PharosProofNodeCodec, SiblingLeftmostLeafProofCodec, NonExistenceProofCodec, AccountProofDataCodec, PharosStateProof, DEFAULT_POLL_INTERVAL, ADDRESS_ZERO2, DEFAULT_GRAFFITI, DEFAULT_LOGGER, REQUEST_STATUS_WEIGHTS, TIMEOUT_STATUS_WEIGHTS, COMBINED_STATUS_WEIGHTS, CHAIN_ID_TO_COINGECKO_NATIVE, TESTNET_CHAINS, ORDER_STATUS, loadDriver, detectEnvironment, STORAGE_KEYS, LEGACY_STORAGE_KEYS, ABI5, uniswapV3Quoter_default, UNISWAP_V4_QUOTER_ABI, ABI6, universalRouter_default, PERMIT2_ABI, UniswapQuoteEngine, COMMON_FEE_TIERS, Swap, ABI7, erc7281_default, DEFAULT_GRAFFITI2, ERC7821_BATCH_MODE, BundlerMethod, SELECT_SOLVER_TYPEHASH, PACKED_USEROP_TYPEHASH, DOMAIN_TYPEHASH, CryptoUtils, FEE_TOKEN_CACHE_TTL_MS, OrderPlacer, TOKEN_KEYED_GATEWAY_ABI, keyingByClient, PARTIAL_FILLS_SLOT, USED_USEROPS_STORAGE_KEY, OrderExecutor, _a6, OrderCanceller, FILL_ORDER_SELECTOR, SUPPORTED_INTENTS_VERSION, CONTRACT_VERSION_ABI, CANONICAL_EVM_TOKEN, ENTRY_POINT_EVENT_ABI, BidImpl, BidManager, RELAYER_MESSAGE_GAS, NO_BUNDLER_FILL_GAS_BASE, NO_BUNDLER_FILL_GAS_PER_OUTPUT, NO_BUNDLER_PAYMASTER_VERIFICATION_GAS, NO_BUNDLER_PAYMASTER_POST_OP_GAS, GasEstimator, OrderStatusChecker, ORDERBOOK_URLS, ORDERBOOK_DECIMALS, ORDERBOOK_SCALE, OrderbookRequestError, RATE_FIELDS, TOP_OF_BOOK_QUERY, ROUTE_LIQUIDITY_QUERY, QUOTE_QUERY, HyperFxOrderbook, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, OrderbookQuoteNotConvergedError, InsufficientOrderbookLiquidityError, MAX_EXACT_OUTPUT_ROUNDS, OrderbookMarket, ORDER_FEE_GAS_PRICE_BUMP_POLICY, IntentGateway, DECLARATION_V1, DECLARATION_V2, PAYMASTER_ADDRESS_BYTES, PAYMASTER_GAS_LIMIT_BYTES, PAYMASTER_DATA_OFFSET, PERMIT2_DATA_BYTES, PERMIT2_SPONSORSHIP_BYTES, MAX_DECLARED_ENTRIES, MAX_TOKEN_ID_BYTES, TeleportParams, MultiAccount;
214402
+ var import_merge_race, __defProp3, __getOwnPropNames2, __esm2, __export2, node_exports, wasm, full_path, __dirname$1, cachedTextDecoder, cachedUint8ArrayMemory0, WASM_VECTOR_LEN, cachedTextEncoder, encodeString5, cachedDataViewMemory0, KeccakMergeFinalization, KeccakMerge, wasmPath, wasmBytes2, bindings, wasmModule, wasmInstance, __wasm, init_node11, RequestStatus, TimeoutStatus, IntentOrderStatus, ABI, evmHost_default, ABI2, handlerV2_default, polkadotAssetHubPaseo, polkadotHubMainnet, pharosMainnet, pharosAtlantic, tronNile, chainConfigs, configsByStateMachineId, getConfigByStateMachineId, getChainId3, getViemChain, hyperbridgeAddress, ABI3, IntentGatewayV2_default, ChainConfigService, ABI4, uniswapRouterV2_default, chains, DEFAULT_ADDRESS, EvmChain, REQUEST_COMMITMENTS_SLOT, RESPONSE_COMMITMENTS_SLOT, REQUEST_RECEIPTS_SLOT, STATE_COMMITMENTS_SLOT, MISSING_CONSENSUS_UPDATE_TIME_MESSAGE, MissingConsensusUpdateTimeError, TokenBucket, BATCHES_NOT_SUPPORTED_CODE, TOO_BIG_BATCH_REQUEST_CODE, DEFAULT_MAX_BATCH_SIZE, _endpoint2, _headers3, _limiter, _maxBatchSize, _batchingSupported, _pending2, _flushTimer, _flushing, _nextId, __BatchingHttpProvider_instances, scheduleFlush_fn, flushNow_fn, flush_fn, post_fn, settleBatch_fn, settle_fn, handleBatchRefusal_fn, requeue_fn, _a5, BatchingHttpProvider, OFFCHAIN_BID_PREFIX, HYPERBRIDGE_TYPES_BUNDLE, BASE_TIP, HTTP_CONNECT_TIMEOUT_MS, INCLUSION_TIMEOUT_MS, DEFAULT_RPC_MAX_RPS, rpcLimiters, BidCodec, PackedUserOperationCodec, IntentsCoprocessor, ReviveContractInfo, ReviveAccountType, ReviveAccountInfo, H256, EvmStateProof, SubstrateHashing, SubstrateStateMachineProof, BasicProof, LeafIndexAndPos, MmrProof, ConsensusStateId, ConsensusMessage, FraudProofMessage, StateMachine, StateMachineId, StateMachineHeight, Proof, PostRequest, PostResponse, GetRequest, StorageValue, GetResponse, Request3, Response2, RequestMessage, RequestResponse, ResponseMessage, TimeoutMessage, GetRequestsWithProof, Message, DEFAULT_CHILD_STORAGE_PREFIX, PharosProofNodeCodec, SiblingLeftmostLeafProofCodec, NonExistenceProofCodec, AccountProofDataCodec, PharosStateProof, DEFAULT_POLL_INTERVAL, ADDRESS_ZERO2, DEFAULT_GRAFFITI, DEFAULT_LOGGER, REQUEST_STATUS_WEIGHTS, TIMEOUT_STATUS_WEIGHTS, COMBINED_STATUS_WEIGHTS, CHAIN_ID_TO_COINGECKO_NATIVE, TESTNET_CHAINS, ORDER_STATUS, loadDriver, detectEnvironment, STORAGE_KEYS, LEGACY_STORAGE_KEYS, ABI5, uniswapV3Quoter_default, UNISWAP_V4_QUOTER_ABI, ABI6, universalRouter_default, PERMIT2_ABI, UniswapQuoteEngine, COMMON_FEE_TIERS, Swap, ABI7, erc7281_default, DEFAULT_GRAFFITI2, ERC7821_BATCH_MODE, BundlerMethod, SELECT_SOLVER_TYPEHASH, PACKED_USEROP_TYPEHASH, DOMAIN_TYPEHASH, CryptoUtils, FEE_TOKEN_CACHE_TTL_MS, OrderPlacer, TOKEN_KEYED_GATEWAY_ABI, keyingByClient, PARTIAL_FILLS_SLOT, USED_USEROPS_STORAGE_KEY, OrderExecutor, _a6, OrderCanceller, FILL_ORDER_SELECTOR, SUPPORTED_INTENTS_VERSION, CONTRACT_VERSION_ABI, CANONICAL_EVM_TOKEN, ENTRY_POINT_EVENT_ABI, BidImpl, BidManager, RELAYER_MESSAGE_GAS, NO_BUNDLER_FILL_GAS_BASE, NO_BUNDLER_FILL_GAS_PER_OUTPUT, NO_BUNDLER_PAYMASTER_VERIFICATION_GAS, NO_BUNDLER_PAYMASTER_POST_OP_GAS, GasEstimator, OrderStatusChecker, ORDERBOOK_URLS, ORDERBOOK_DECIMALS, ORDERBOOK_SCALE, OrderbookRequestError, RATE_FIELDS, TOP_OF_BOOK_QUERY, ROUTE_LIQUIDITY_QUERY, QUOTE_QUERY, QUOTE_PESSIMISTIC_QUERY, HyperFxOrderbook, UnsupportedLiquidityAssetError, UnsupportedLiquidityChainError, OrderbookQuoteNotConvergedError, MAX_EXACT_OUTPUT_ROUNDS, OrderbookMarket, ORDER_FEE_GAS_PRICE_BUMP_POLICY, IntentGateway, DECLARATION_V1, DECLARATION_V2, PAYMASTER_ADDRESS_BYTES, PAYMASTER_GAS_LIMIT_BYTES, PAYMASTER_DATA_OFFSET, PERMIT2_DATA_BYTES, PERMIT2_SPONSORSHIP_BYTES, MAX_DECLARED_ENTRIES, MAX_TOKEN_ID_BYTES, TeleportParams, MultiAccount;
214389
214403
  var init_node10 = __esm({
214390
214404
  "../sdk/dist/node/index.js"() {
214391
214405
  "use strict";
@@ -228328,14 +228342,20 @@ query TopOfBook($tokenA: String!, $tokenB: String!, $fillChain: String!, $source
228328
228342
  query RouteLiquidity($route: RouteInput!) {
228329
228343
  books { id base quote }
228330
228344
  routeLiquidity(route: $route) {
228331
- route bestRate depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
228345
+ route bestRate slippageBps depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
228332
228346
  }
228333
228347
  }`;
228334
228348
  QUOTE_QUERY = `
228335
228349
  query Quote($route: RouteInput!, $amountIn: BigInt!) {
228336
228350
  quote(route: $route, amountIn: $amountIn) {
228337
- route side amountIn amountOut rate fillable depth maxFillableIn
228338
- fills { orderRate amountOut advertisedSize }
228351
+ route side amountIn slippageBps fillable maxFillableIn
228352
+ fills { advertisedSize orderRate amountIn amountOut }
228353
+ }
228354
+ }`;
228355
+ QUOTE_PESSIMISTIC_QUERY = `
228356
+ query QuotePessimistic($route: RouteInput!, $amountIn: BigInt!) {
228357
+ quotePessimistic(route: $route, amountIn: $amountIn) {
228358
+ route side amountIn amountOut rate priceBucket slippageBps fillable maxFillableIn
228339
228359
  }
228340
228360
  }`;
228341
228361
  HyperFxOrderbook = class {
@@ -228365,8 +228385,9 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228365
228385
  return {
228366
228386
  route: raw.route,
228367
228387
  book,
228368
- side: book.base === route.tokenIn ? "BID" : "ASK",
228369
- bestRate: raw.bestRate === null ? null : BigInt(raw.bestRate),
228388
+ side: sameSymbol(book.base, route.tokenIn) ? "BID" : "ASK",
228389
+ bestRate: optionalBigInt(raw.bestRate),
228390
+ slippageBps: raw.slippageBps,
228370
228391
  depthIn: BigInt(raw.depthIn),
228371
228392
  depthOut: BigInt(raw.depthOut),
228372
228393
  availableLiquidity: BigInt(raw.availableLiquidity),
@@ -228375,7 +228396,10 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228375
228396
  solverCount: raw.solverCount
228376
228397
  };
228377
228398
  }
228378
- /** The clearing price for `amountIn` (1e18 tokenIn) on a route, which may combine several solvers' orders. */
228399
+ /**
228400
+ * The optimistic quote for `amountIn` (1e18 tokenIn) on a route: the route's orders, best price
228401
+ * first, each taking what it can at its own price. The fills' `amountOut`s sum to the output.
228402
+ */
228379
228403
  async quote(route, amountIn) {
228380
228404
  const { quote: raw } = await this.request(QUOTE_QUERY, {
228381
228405
  route,
@@ -228385,18 +228409,39 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228385
228409
  route: raw.route,
228386
228410
  side: raw.side,
228387
228411
  amountIn: BigInt(raw.amountIn),
228388
- amountOut: BigInt(raw.amountOut),
228389
- rate: raw.rate === null ? null : BigInt(raw.rate),
228412
+ slippageBps: raw.slippageBps,
228390
228413
  fillable: raw.fillable,
228391
- depth: BigInt(raw.depth),
228392
228414
  maxFillableIn: BigInt(raw.maxFillableIn),
228393
228415
  fills: raw.fills.map((fill) => ({
228416
+ advertisedSize: BigInt(fill.advertisedSize),
228394
228417
  orderRate: BigInt(fill.orderRate),
228395
- amountOut: BigInt(fill.amountOut),
228396
- advertisedSize: BigInt(fill.advertisedSize)
228418
+ amountIn: BigInt(fill.amountIn),
228419
+ amountOut: BigInt(fill.amountOut)
228397
228420
  }))
228398
228421
  };
228399
228422
  }
228423
+ /**
228424
+ * The pessimistic quote for `amountIn` (1e18 tokenIn) on a route: one price, from the first level
228425
+ * deep enough to fill the whole trade by itself, at which every order in it fills; the route's
228426
+ * worst price when no one level can but its levels together can.
228427
+ */
228428
+ async quotePessimistic(route, amountIn) {
228429
+ const { quotePessimistic: raw } = await this.request(
228430
+ QUOTE_PESSIMISTIC_QUERY,
228431
+ { route, amountIn: amountIn.toString() }
228432
+ );
228433
+ return {
228434
+ route: raw.route,
228435
+ side: raw.side,
228436
+ amountIn: BigInt(raw.amountIn),
228437
+ amountOut: BigInt(raw.amountOut),
228438
+ rate: optionalBigInt(raw.rate),
228439
+ priceBucket: optionalBigInt(raw.priceBucket),
228440
+ slippageBps: raw.slippageBps,
228441
+ fillable: raw.fillable,
228442
+ maxFillableIn: BigInt(raw.maxFillableIn)
228443
+ };
228444
+ }
228400
228445
  async request(query3, variables) {
228401
228446
  try {
228402
228447
  return await this.client.request(query3, variables);
@@ -228431,18 +228476,6 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228431
228476
  this.name = "OrderbookQuoteNotConvergedError";
228432
228477
  }
228433
228478
  };
228434
- InsufficientOrderbookLiquidityError = class extends Error {
228435
- constructor(route, maxFillableIn) {
228436
- super(
228437
- `The HyperFX orderbook cannot fill ${route.tokenIn} -> ${route.tokenOut} on ${route.sourceChain} -> ${route.destinationChain}; max fillable input is ${maxFillableIn} raw units`
228438
- );
228439
- __publicField(this, "route");
228440
- __publicField(this, "maxFillableIn");
228441
- this.route = route;
228442
- this.maxFillableIn = maxFillableIn;
228443
- this.name = "InsufficientOrderbookLiquidityError";
228444
- }
228445
- };
228446
228479
  MAX_EXACT_OUTPUT_ROUNDS = 16;
228447
228480
  OrderbookMarket = class {
228448
228481
  constructor(configService, orderbook) {
@@ -228452,26 +228485,67 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228452
228485
  this.orderbook = orderbook;
228453
228486
  }
228454
228487
  async quoteIntent(params, sourceChain, destinationChain) {
228488
+ const orderbook = this.orderbook();
228489
+ if (!params.optimistic) {
228490
+ const { quote: quote2, tokenIn: tokenIn2, tokenOut: tokenOut2 } = await this.priceIntent(
228491
+ params,
228492
+ sourceChain,
228493
+ destinationChain,
228494
+ (route, amountIn) => orderbook.quotePessimistic(route, amountIn),
228495
+ levelQuoteTotals
228496
+ );
228497
+ return {
228498
+ route: quote2.route,
228499
+ side: quote2.side,
228500
+ amountIn: fromOrderbookAmount(quote2.amountIn, tokenIn2.decimals),
228501
+ amountOut: fromOrderbookAmount(quote2.amountOut, tokenOut2.decimals),
228502
+ rate: quote2.rate,
228503
+ priceBucket: quote2.priceBucket,
228504
+ slippageBps: quote2.slippageBps,
228505
+ fillable: quote2.fillable,
228506
+ maxFillableIn: fromOrderbookAmount(quote2.maxFillableIn, tokenIn2.decimals)
228507
+ };
228508
+ }
228509
+ const { quote, tokenIn, tokenOut } = await this.priceIntent(
228510
+ params,
228511
+ sourceChain,
228512
+ destinationChain,
228513
+ (route, amountIn) => orderbook.quote(route, amountIn),
228514
+ swapQuoteTotals
228515
+ );
228516
+ return {
228517
+ route: quote.route,
228518
+ side: quote.side,
228519
+ amountIn: fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
228520
+ slippageBps: quote.slippageBps,
228521
+ fillable: quote.fillable,
228522
+ maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
228523
+ legs: quote.fills.map((fill) => ({
228524
+ advertisedSize: fromOrderbookAmount(fill.advertisedSize, tokenOut.decimals),
228525
+ orderRate: fill.orderRate,
228526
+ amountIn: fromOrderbookAmount(fill.amountIn, tokenIn.decimals),
228527
+ amountOut: fromOrderbookAmount(fill.amountOut, tokenOut.decimals)
228528
+ }))
228529
+ };
228530
+ }
228531
+ /**
228532
+ * The orderbook's quote for an exact input, or for the input found to deliver
228533
+ * an exact output, with the assets it trades. A route that cannot fill the
228534
+ * trade answers with an unfillable quote rather than an error.
228535
+ */
228536
+ async priceIntent(params, sourceChain, destinationChain, price, totals) {
228455
228537
  validateQuoteParams(params);
228456
228538
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
228457
228539
  const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
228458
228540
  const route = toRoute(tokenIn, tokenOut);
228459
228541
  if (params.amountIn !== void 0) {
228460
- const quote2 = await this.orderbook().quote(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
228461
- const amountOut2 = quote2.fillable ? fromOrderbookAmount(quote2.amountOut, tokenOut.decimals) : 0n;
228462
- if (amountOut2 === 0n) throw this.insufficient(route, quote2.maxFillableIn, tokenIn);
228463
- return buildQuote("EXACT_INPUT", params.amountIn, amountOut2, quote2, tokenIn, tokenOut);
228542
+ const quote2 = await price(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
228543
+ totals(quote2);
228544
+ return { quote: quote2, tokenIn, tokenOut };
228464
228545
  }
228465
- const amountOut = params.amountOut;
228466
- const quote = await this.quoteExactOutput(route, toOrderbookAmount(amountOut, tokenOut.decimals), tokenIn);
228467
- return buildQuote(
228468
- "EXACT_OUTPUT",
228469
- fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
228470
- amountOut,
228471
- quote,
228472
- tokenIn,
228473
- tokenOut
228474
- );
228546
+ const targetOut = toOrderbookAmount(params.amountOut, tokenOut.decimals);
228547
+ const quote = await this.quoteExactOutput(route, targetOut, tokenIn, price, totals);
228548
+ return { quote, tokenIn, tokenOut };
228475
228549
  }
228476
228550
  async availableLiquidity(params, sourceChain, destinationChain) {
228477
228551
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
@@ -228524,22 +228598,29 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228524
228598
  }
228525
228599
  /**
228526
228600
  * The orderbook only quotes an input amount, so an output is priced by
228527
- * guessing the input from the best rate and raising it at each clearing
228528
- * price until the quote delivers `targetOut`.
228601
+ * guessing the input from the best rate, then keeping the part of each quote
228602
+ * filled better than its worst rate and re-pricing the rest of `targetOut` at
228603
+ * the input-to-output ratio that part filled at, until a quote delivers it.
228604
+ * That ratio is the orderbook's own, with the protocol fee already taken off.
228605
+ *
228606
+ * An output the route cannot deliver is answered by quoting an input past its
228607
+ * `maxFillableIn`, which the orderbook serves as unfillable: the smallest such
228608
+ * input guessed from the best rate, or one raw unit when no order serves it.
228529
228609
  */
228530
- async quoteExactOutput(route, targetOut, tokenIn) {
228531
- const orderbook = this.orderbook();
228532
- const liquidity = await orderbook.routeLiquidity(route);
228533
- if (liquidity.bestRate === null) throw this.insufficient(route, 0n, tokenIn);
228610
+ async quoteExactOutput(route, targetOut, tokenIn, price, totals) {
228611
+ const liquidity = await this.orderbook().routeLiquidity(route);
228534
228612
  const inputUnit = toOrderbookAmount(1n, tokenIn.decimals);
228535
- let amountIn = roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
228613
+ let amountIn = liquidity.bestRate === null ? inputUnit : roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
228536
228614
  let lastAmountIn = amountIn;
228537
228615
  for (let round2 = 0; round2 < MAX_EXACT_OUTPUT_ROUNDS; round2++) {
228538
- if (amountIn > liquidity.maxFillableIn) throw this.insufficient(route, liquidity.maxFillableIn, tokenIn);
228539
- const quote = await orderbook.quote(route, amountIn);
228540
- if (quote.fillable && quote.amountOut >= targetOut) return quote;
228616
+ const served = await price(route, amountIn);
228617
+ const quote = totals(served);
228618
+ if (quote.fillable && quote.amountOut >= targetOut) return served;
228619
+ if (!quote.fillable && amountIn > liquidity.maxFillableIn) return served;
228541
228620
  lastAmountIn = amountIn;
228542
- const next = quote.rate === null ? amountIn : roundUpTo(requiredInput(quote.side, targetOut, quote.rate), inputUnit);
228621
+ const betterIn = quote.amountIn - quote.worstIn;
228622
+ const betterOut = quote.amountOut - quote.worstOut;
228623
+ const next = quote.fillable && quote.worstOut > 0n ? betterIn + roundUpTo(divCeil((targetOut - betterOut) * quote.worstIn, quote.worstOut), inputUnit) : amountIn;
228543
228624
  amountIn = next > amountIn ? next : amountIn + inputUnit;
228544
228625
  }
228545
228626
  throw new OrderbookQuoteNotConvergedError(
@@ -228548,9 +228629,6 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228548
228629
  fromOrderbookAmount(lastAmountIn, tokenIn.decimals)
228549
228630
  );
228550
228631
  }
228551
- insufficient(route, maxFillableIn, tokenIn) {
228552
- return new InsufficientOrderbookLiquidityError(route, fromOrderbookAmount(maxFillableIn, tokenIn.decimals));
228553
- }
228554
228632
  assetByAddress(chain2, address) {
228555
228633
  const resolvedChain = resolveChain(chain2);
228556
228634
  return withDecimals(
@@ -228709,22 +228787,6 @@ query Quote($route: RouteInput!, $amountIn: BigInt!) {
228709
228787
  this.orderbook = typeof orderbook === "string" ? new HyperFxOrderbook(orderbook) : orderbook;
228710
228788
  return this;
228711
228789
  }
228712
- /**
228713
- * Quotes an intent between this gateway's source and destination chains from
228714
- * the HyperFX orderbook. Provide exactly one of `amountIn` or `amountOut`, in
228715
- * raw token units.
228716
- *
228717
- * An exact-input quote is the orderbook's clearing price for `amountIn`,
228718
- * which may combine several solvers' orders. An exact-output quote finds the
228719
- * smallest input whose clearing price delivers `amountOut`. A cross-chain
228720
- * route only counts orders whose solvers accept the source chain.
228721
- *
228722
- * The orderbook's rates already carry the gateway protocol fee, so the
228723
- * returned amounts can be placed as the order's inputs and outputs directly.
228724
- *
228725
- * @throws {InsufficientOrderbookLiquidityError} When the route cannot fill the amount.
228726
- * @throws {OrderbookRequestError} When the orderbook is unreachable or trades no book for the pair.
228727
- */
228728
228790
  async quoteIntent(params) {
228729
228791
  return this.market.quoteIntent(params, this.source.config.stateMachineId, this.dest.config.stateMachineId);
228730
228792
  }
@@ -229954,6 +230016,7 @@ var init_external_links = __esm({
229954
230016
  "src/config/external-links.json"() {
229955
230017
  external_links_default = {
229956
230018
  hyperfxApp: "https://app.hyperfx.finance",
230019
+ alchemyDashboard: "https://dashboard.alchemy.com/",
229957
230020
  hyperbridgeExplorers: {
229958
230021
  mainnet: "https://nexus.statescan.io",
229959
230022
  testnet: "https://gargantua.statescan.io"
@@ -229987,12 +230050,58 @@ function chainByChainId(chainId) {
229987
230050
  function chainByAlchemySubdomain(subdomain) {
229988
230051
  return INIT_CHAINS.find((chain2) => chain2.alchemySubdomain === subdomain);
229989
230052
  }
229990
- var CHAIN_NATIVE_SYMBOLS, INIT_CHAINS, HYPERBRIDGE_WS_DEFAULTS;
230053
+ var FREE_RPC_URLS, CHAIN_NATIVE_SYMBOLS, INIT_CHAINS, HYPERBRIDGE_WS_DEFAULTS;
229991
230054
  var init_chains2 = __esm({
229992
230055
  "src/cli/init/chains.ts"() {
229993
230056
  "use strict";
229994
230057
  init_esm_shims();
229995
230058
  init_external_links();
230059
+ FREE_RPC_URLS = {
230060
+ 1: [
230061
+ "https://mainnet.gateway.tenderly.co",
230062
+ "https://rpc.mevblocker.io",
230063
+ "https://ethereum-rpc.publicnode.com",
230064
+ "https://ethereum.public.blockpi.network/v1/rpc/public",
230065
+ "https://eth.blockrazor.xyz",
230066
+ "https://eth.api.pocket.network",
230067
+ "https://ethereum-public.nodies.app",
230068
+ "https://eth-mainnet.public.blastapi.io",
230069
+ "https://public-eth.nownodes.io"
230070
+ ],
230071
+ 56: [
230072
+ "https://bsc-rpc.publicnode.com",
230073
+ "https://public-bsc-mainnet.fastnode.io",
230074
+ "https://public-bsc.nownodes.io",
230075
+ "https://bsc.blockrazor.xyz",
230076
+ "https://rpc-bsc.48.club",
230077
+ "https://bsc.api.pocket.network"
230078
+ ],
230079
+ 137: [
230080
+ "https://polygon-bor-rpc.publicnode.com",
230081
+ "https://polygon.drpc.org",
230082
+ "https://polygon.gateway.tenderly.co",
230083
+ "https://rpc.private.mev-x.com/polygon",
230084
+ "https://poly.api.pocket.network"
230085
+ ],
230086
+ 8453: [
230087
+ "https://base-rpc.publicnode.com",
230088
+ "https://base.gateway.tenderly.co",
230089
+ "https://base.public.blockpi.network/v1/rpc/public",
230090
+ "https://base.rpc.blxrbdn.com",
230091
+ "https://rpc.baseazul.dev",
230092
+ "https://mainnet.base.org",
230093
+ "https://base-public.nodies.app"
230094
+ ],
230095
+ 42161: [
230096
+ "https://arbitrum-one-rpc.publicnode.com",
230097
+ "https://arbitrum.gateway.tenderly.co",
230098
+ "https://arbitrum.drpc.org",
230099
+ "https://public-arb-mainnet.fastnode.io",
230100
+ "https://arb-one.api.pocket.network",
230101
+ "https://arbitrum-one-public.nodies.app",
230102
+ "https://arb1.arbitrum.io/rpc"
230103
+ ]
230104
+ };
229996
230105
  CHAIN_NATIVE_SYMBOLS = {
229997
230106
  1: "ETH",
229998
230107
  56: "BNB",
@@ -230017,7 +230126,8 @@ var init_chains2 = __esm({
230017
230126
  label: "Ethereum",
230018
230127
  network: "mainnet",
230019
230128
  alchemySubdomain: "eth-mainnet",
230020
- explorerUrl: external_links_default.chainExplorers.ethereum
230129
+ explorerUrl: external_links_default.chainExplorers.ethereum,
230130
+ defaultRpcUrls: FREE_RPC_URLS[1]
230021
230131
  },
230022
230132
  {
230023
230133
  chainId: 42161,
@@ -230025,7 +230135,8 @@ var init_chains2 = __esm({
230025
230135
  label: "Arbitrum",
230026
230136
  network: "mainnet",
230027
230137
  alchemySubdomain: "arb-mainnet",
230028
- explorerUrl: external_links_default.chainExplorers.arbitrum
230138
+ explorerUrl: external_links_default.chainExplorers.arbitrum,
230139
+ defaultRpcUrls: FREE_RPC_URLS[42161]
230029
230140
  },
230030
230141
  {
230031
230142
  chainId: 8453,
@@ -230033,7 +230144,8 @@ var init_chains2 = __esm({
230033
230144
  label: "Base",
230034
230145
  network: "mainnet",
230035
230146
  alchemySubdomain: "base-mainnet",
230036
- explorerUrl: external_links_default.chainExplorers.base
230147
+ explorerUrl: external_links_default.chainExplorers.base,
230148
+ defaultRpcUrls: FREE_RPC_URLS[8453]
230037
230149
  },
230038
230150
  {
230039
230151
  chainId: 137,
@@ -230041,7 +230153,8 @@ var init_chains2 = __esm({
230041
230153
  label: "Polygon",
230042
230154
  network: "mainnet",
230043
230155
  alchemySubdomain: "polygon-mainnet",
230044
- explorerUrl: external_links_default.chainExplorers.polygon
230156
+ explorerUrl: external_links_default.chainExplorers.polygon,
230157
+ defaultRpcUrls: FREE_RPC_URLS[137]
230045
230158
  },
230046
230159
  {
230047
230160
  chainId: 56,
@@ -230049,7 +230162,8 @@ var init_chains2 = __esm({
230049
230162
  label: "BNB Chain",
230050
230163
  network: "mainnet",
230051
230164
  alchemySubdomain: "bnb-mainnet",
230052
- explorerUrl: external_links_default.chainExplorers.bnb
230165
+ explorerUrl: external_links_default.chainExplorers.bnb,
230166
+ defaultRpcUrls: FREE_RPC_URLS[56]
230053
230167
  },
230054
230168
  {
230055
230169
  chainId: 11155111,
@@ -295054,12 +295168,12 @@ var init_lib3 = __esm({
295054
295168
  return Promise.resolve(ab);
295055
295169
  }
295056
295170
  stream() {
295057
- const readable = new Readable3();
295058
- readable._read = function() {
295171
+ const readable2 = new Readable3();
295172
+ readable2._read = function() {
295059
295173
  };
295060
- readable.push(this[BUFFER]);
295061
- readable.push(null);
295062
- return readable;
295174
+ readable2.push(this[BUFFER]);
295175
+ readable2.push(null);
295176
+ return readable2;
295063
295177
  }
295064
295178
  toString() {
295065
295179
  return "[object Blob]";
@@ -316113,6 +316227,19 @@ async function collectRpcUrls(state, prefill) {
316113
316227
  for (const chain2 of state.chains) {
316114
316228
  const existing = prefillRpcFor(chain2.meta.chainId, prefill);
316115
316229
  const derived2 = alchemy.candidate(chain2.meta.chainId);
316230
+ const bundled = chain2.meta.defaultRpcUrls;
316231
+ if (!existing?.length && !derived2 && bundled?.length) {
316232
+ const usePublic = guard(
316233
+ await confirm({
316234
+ message: `Use ${bundled.length} public RPC endpoints for ${chain2.meta.label}?`,
316235
+ initialValue: true
316236
+ })
316237
+ );
316238
+ if (usePublic) {
316239
+ chain2.rpcUrls = [...bundled];
316240
+ continue;
316241
+ }
316242
+ }
316116
316243
  let url2;
316117
316244
  if (existing?.length) {
316118
316245
  url2 = await askRpcUrl(chain2.meta, existing[0]);
@@ -316290,6 +316417,22 @@ async function stepBundlers(state, prefill) {
316290
316417
  for (const chain2 of alchemyChains) chain2.bundlerUrl = chain2.rpcUrls[0];
316291
316418
  }
316292
316419
  }
316420
+ const alchemyCapable = state.chains.filter((chain2) => !chain2.bundlerUrl && chain2.meta.alchemySubdomain);
316421
+ if (alchemyCapable.length > 0) {
316422
+ const useKey = guard(
316423
+ await confirm({
316424
+ message: `Configure the bundler for ${alchemyCapable.map((c6) => c6.meta.label).join(", ")} from one Alchemy API key?`,
316425
+ initialValue: true
316426
+ })
316427
+ );
316428
+ if (useKey) {
316429
+ const apiKey = await askSecret("Alchemy API key");
316430
+ for (const chain2 of alchemyCapable) {
316431
+ const url2 = deriveAlchemyRpc(apiKey, chain2.meta.chainId);
316432
+ if (url2) chain2.bundlerUrl = url2;
316433
+ }
316434
+ }
316435
+ }
316293
316436
  const pimlico = new ProviderDerivation({
316294
316437
  detect: (url2) => parsePimlicoUrl(url2)?.apiKey ?? null,
316295
316438
  derive: derivePimlicoBundler,
@@ -319025,14 +319168,16 @@ var LimitOrderService = class {
319025
319168
  }
319026
319169
  }
319027
319170
  /**
319028
- * The pairs an order can be written against, and the smallest payout each token may carry.
319171
+ * The pairs an order can be written against, the smallest payout each token may carry, and
319172
+ * the tokens the orderbook registers on each chain.
319029
319173
  *
319030
319174
  * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
319031
- * operator is offered exactly these and no combination of symbols they could not post.
319175
+ * operator is offered exactly these and no combination of symbols they could not post. The
319176
+ * chains do the same for the source chains an order can accept.
319032
319177
  */
319033
319178
  async books() {
319034
319179
  const limits = await this.limits();
319035
- return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes };
319180
+ return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes, chains: limits.chains ?? [] };
319036
319181
  }
319037
319182
  /**
319038
319183
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
@@ -319114,6 +319259,18 @@ var LimitOrderService = class {
319114
319259
  );
319115
319260
  }
319116
319261
  }
319262
+ if (book.base !== book.quote) {
319263
+ const input = normalizeSymbol(request3.tokenIn);
319264
+ const lacking = sources.filter((source) => {
319265
+ const listed = limits.chains?.find((chain2) => chain2.id === source);
319266
+ return listed && !listed.tokens.some((token) => normalizeSymbol(token.symbol) === input);
319267
+ });
319268
+ if (lacking.length > 0) {
319269
+ throw new LimitOrderValidationError(
319270
+ `The orderbook has no ${request3.tokenIn} on ${lacking.join(", ")}, so swaps from there cannot pay this order`
319271
+ );
319272
+ }
319273
+ }
319117
319274
  if (ttlSecs < limits.serverInfo.minOrderTtlSecs) {
319118
319275
  throw new LimitOrderValidationError(
319119
319276
  `ttlSecs must be at least the orderbook's minimum of ${limits.serverInfo.minOrderTtlSecs}; got ${ttlSecs}`
@@ -320776,7 +320933,7 @@ var NotificationService = class extends EventEmitter6 {
320776
320933
  title: "Swap filled",
320777
320934
  body: swapBody(event),
320778
320935
  tag: `simplex-swap-${id2}`,
320779
- url: "./orders"
320936
+ url: "./history"
320780
320937
  }).catch((error) => this.logger.error({ err: error }, "Could not publish swap notification"));
320781
320938
  }
320782
320939
  };
@@ -320969,6 +321126,7 @@ init_chains2();
320969
321126
  init_alchemy();
320970
321127
  init_prompt_utils();
320971
321128
  init_state();
321129
+ init_defaults8();
320972
321130
  init_Logger();
320973
321131
  var logger2 = getLogger("setup");
320974
321132
  async function defaultRpcRequest(url2, method, params) {
@@ -321015,6 +321173,18 @@ async function handleSetupRequest(server, setup, req, res, path2, method) {
321015
321173
  };
321016
321174
  return sendJson(res, 200, defaults3);
321017
321175
  }
321176
+ if (endpoint === "orderbook") {
321177
+ if (method !== "GET") return sendJson(res, 405, { error: "Method not allowed" });
321178
+ const url2 = DEFAULT_ORDERBOOK_URLS.mainnet;
321179
+ try {
321180
+ const { books } = await new OrderbookClient(url2, DEFAULT_ORDERBOOK_TIMEOUT_MS).limits();
321181
+ const orderbook = { url: url2, books };
321182
+ return sendJson(res, 200, orderbook);
321183
+ } catch (err) {
321184
+ const reason = err instanceof Error ? err.message : String(err);
321185
+ return sendJson(res, 502, { error: `Could not read the orderbook's markets: ${reason}` });
321186
+ }
321187
+ }
321018
321188
  if (method !== "POST") return sendJson(res, 405, { error: "Method not allowed" });
321019
321189
  let body;
321020
321190
  try {
@@ -323326,14 +323496,10 @@ var RATE_LIMIT_RPC_CODES = /* @__PURE__ */ new Set([-32005, -32097, -32016, 429]
323326
323496
  var RATE_LIMIT_SUSPENSION_MS = 5 * 6e4;
323327
323497
  var SCAN_BUDGET = {
323328
323498
  timeoutMs: 5e3,
323329
- retryCount: 1,
323330
- retryDelayMs: 500,
323331
323499
  deadlineMs: 12e3
323332
323500
  };
323333
323501
  var CONFIRMATION_BUDGET = {
323334
323502
  timeoutMs: 1e4,
323335
- retryCount: 1,
323336
- retryDelayMs: 500,
323337
323503
  deadlineMs: 3e4
323338
323504
  };
323339
323505
  var MALFORMED_RESPONSE_SUSPENSION_MS = 3e4;
@@ -323425,8 +323591,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323425
323591
  chain: chain2,
323426
323592
  transport: http(url2, {
323427
323593
  timeout: budget.timeoutMs,
323428
- retryCount: budget.retryCount,
323429
- retryDelay: budget.retryDelayMs
323594
+ // No transport-level retry. viem's retry sleeps for the provider's
323595
+ // `Retry-After` verbatim and uncapped, which reaches the socket
323596
+ // before {@link suspensionMsFor} can apply its clamp — and even
323597
+ // without that header, retrying inside the task is what makes a
323598
+ // task's worst case (timeout, backoff, timeout) approach the
323599
+ // deadline that is supposed to bound it. One attempt per endpoint
323600
+ // per call; `retryPromise` above the quorum owns the retry policy.
323601
+ retryCount: 0
323430
323602
  })
323431
323603
  })
323432
323604
  );
@@ -323440,11 +323612,10 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323440
323612
  return this.rpcUrls.filter((url2) => (_QuorumPublicClient.benchedUntil.get(url2) ?? 0) > now);
323441
323613
  }
323442
323614
  /**
323443
- * The endpoints this call queries — everyone not benched — and the quorum
323444
- * bar for exactly that set. A benched endpoint is dropped outright: it
323445
- * answers nothing while throttled, so counting it could only fail calls the
323446
- * remaining endpoints agree on. With every endpoint benched, everyone is
323447
- * queried again; there is nobody left to prefer.
323615
+ * The endpoints this call queries: everyone not benched. A benched endpoint is
323616
+ * dropped outright: it answers nothing while throttled, so asking it could only
323617
+ * cost a request. With every endpoint benched, everyone is queried again; there
323618
+ * is nobody left to prefer.
323448
323619
  */
323449
323620
  participants() {
323450
323621
  const now = Date.now();
@@ -323452,12 +323623,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323452
323623
  const active = all7.filter(({ idx }) => (_QuorumPublicClient.benchedUntil.get(this.rpcUrls[idx]) ?? 0) <= now);
323453
323624
  const allBenched = active.length === 0;
323454
323625
  const queried = allBenched ? all7 : active;
323455
- return {
323456
- queried,
323457
- skipped: all7.length - queried.length,
323458
- threshold: quorumThreshold(queried.length),
323459
- allBenched
323460
- };
323626
+ return { queried, skipped: all7.length - queried.length, allBenched };
323461
323627
  }
323462
323628
  /**
323463
323629
  * Records a task failure against its endpoint. Only the failures
@@ -323502,17 +323668,21 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323502
323668
  * retries would otherwise stall every confirmation poll). Stragglers settle
323503
323669
  * out of band and are ignored. `tryDecide` returns undefined to keep
323504
323670
  * waiting; once every task has settled, `finalize` must decide (it throws
323505
- * the QuorumError when the full response set still has no quorum). Early
323506
- * exit never weakens the trust model — a decision still requires the same
323507
- * quorum, it just doesn't wait for votes it no longer needs.
323671
+ * the QuorumError when the full response set still has no quorum).
323672
+ *
323673
+ * Both are handed the bar: the BFT threshold over the endpoints that have not
323674
+ * failed, answered or still out. A failure lowers it rather than counting
323675
+ * against it. Counting the pending endpoints keeps an early decision to a bar
323676
+ * no lower than the full response set would get, so a vote it did not wait for
323677
+ * could never have reversed it.
323508
323678
  *
323509
323679
  * `deadlineMs` bounds the other direction: when the bar is NOT reachable
323510
323680
  * from the fast responders, the call used to wait out every straggler, and
323511
323681
  * the caller's own retry loop multiplied that into minutes of a chain not
323512
323682
  * being scanned at all, silently. At the deadline the outstanding endpoints
323513
323683
  * are treated as failures — named in the QuorumError, so the operator sees
323514
- * which ones held the call up — and `finalize` decides on the rest. The
323515
- * trust model is untouched: a decision still needs the same quorum.
323684
+ * which ones held the call up — and `finalize` decides over the endpoints
323685
+ * that answered.
323516
323686
  */
323517
323687
  settleUntilQuorum(tasks, tryDecide, finalize) {
323518
323688
  return new Promise((resolve11, reject) => {
@@ -323530,13 +323700,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323530
323700
  const evaluate = () => {
323531
323701
  if (done) return;
323532
323702
  try {
323533
- const early = tryDecide(fulfilled);
323703
+ const threshold = quorumThreshold(tasks.length - failures.length);
323704
+ const early = tryDecide(fulfilled, threshold);
323534
323705
  if (early !== void 0) {
323535
323706
  finish(() => resolve11(early));
323536
323707
  return;
323537
323708
  }
323538
323709
  if (outstanding === 0) {
323539
- const value = finalize(fulfilled, failures);
323710
+ const value = finalize(fulfilled, failures, threshold);
323540
323711
  finish(() => resolve11(value));
323541
323712
  }
323542
323713
  } catch (error) {
@@ -323554,7 +323725,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323554
323725
  error: new Error(`no answer within the ${this.deadlineMs}ms quorum deadline`)
323555
323726
  }));
323556
323727
  try {
323557
- const value = finalize(fulfilled, [...failures, ...stragglers]);
323728
+ const value = finalize(fulfilled, [...failures, ...stragglers], quorumThreshold(fulfilled.length));
323558
323729
  finish(() => resolve11(value));
323559
323730
  } catch (error) {
323560
323731
  finish(() => reject(error));
@@ -323581,22 +323752,22 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323581
323752
  });
323582
323753
  }
323583
323754
  /**
323584
- * Highest block head backed by a full quorum. Throws {@link QuorumError}
323585
- * when fewer than `threshold` endpoints respond.
323755
+ * Highest block head backed by a quorum of the endpoints that answer. Throws
323756
+ * {@link QuorumError} when none do.
323586
323757
  */
323587
323758
  async getBlockNumber() {
323588
- const { queried, skipped, threshold, allBenched } = this.participants();
323759
+ const { queried, skipped, allBenched } = this.participants();
323589
323760
  return this.settleUntilQuorum(
323590
323761
  queried.map(({ idx, client }) => ({ idx, task: client.getBlockNumber() })),
323591
323762
  // Early exit as soon as a quorum is satisfiable. Late voters could
323592
323763
  // only raise the reported head; the earlier (lower) head is
323593
323764
  // conservative for every consumer (fewer confirmations counted,
323594
323765
  // smaller scan windows).
323595
- (fulfilled) => this.quorumHead(
323766
+ (fulfilled, threshold) => this.quorumHead(
323596
323767
  fulfilled.map((f7) => f7.value),
323597
323768
  threshold
323598
323769
  ) ?? void 0,
323599
- (fulfilled, failures) => {
323770
+ (fulfilled, failures, threshold) => {
323600
323771
  const head = this.quorumHead(
323601
323772
  fulfilled.map((f7) => f7.value),
323602
323773
  threshold
@@ -323637,7 +323808,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323637
323808
  }
323638
323809
  return views;
323639
323810
  };
323640
- const { queried, skipped, threshold, allBenched } = this.participants();
323811
+ const { queried, skipped, allBenched } = this.participants();
323641
323812
  return this.settleUntilQuorum(
323642
323813
  queried.map(({ idx, client }) => ({
323643
323814
  idx,
@@ -323656,8 +323827,8 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323656
323827
  // Early exit only on a POSITIVE quorum: an agreeing inclusion group can
323657
323828
  // only grow, so the first satisfiable quorum is final. Not-found votes
323658
323829
  // never decide early — "not confirmed" needs the full response set.
323659
- (fulfilled) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
323660
- (fulfilled, failures) => {
323830
+ (fulfilled, threshold) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
323831
+ (fulfilled, failures, threshold) => {
323661
323832
  const confirmations = aggregateConfirmations(toViews(fulfilled), threshold);
323662
323833
  if (confirmations === null) {
323663
323834
  throw new QuorumError(
@@ -323686,7 +323857,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323686
323857
  }
323687
323858
  return groups;
323688
323859
  };
323689
- const { queried, skipped, threshold, allBenched } = this.participants();
323860
+ const { queried, skipped, allBenched } = this.participants();
323690
323861
  return this.settleUntilQuorum(
323691
323862
  queried.map(({ idx, client }) => ({
323692
323863
  idx,
@@ -323698,13 +323869,13 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323698
323869
  // two disjoint groups can't both hold one), so the first satisfiable
323699
323870
  // agreement is final — no need to wait out stragglers that could
323700
323871
  // only join or lose.
323701
- (fulfilled) => {
323872
+ (fulfilled, threshold) => {
323702
323873
  for (const group of groupOf(fulfilled).values()) {
323703
323874
  if (group.providerIdxs.length >= threshold) return group.result;
323704
323875
  }
323705
323876
  return void 0;
323706
323877
  },
323707
- (fulfilled, failures) => {
323878
+ (fulfilled, failures, threshold) => {
323708
323879
  const groups = groupOf(fulfilled);
323709
323880
  for (const group of groups.values()) {
323710
323881
  if (group.providerIdxs.length >= threshold) return group.result;
@@ -362304,8 +362475,15 @@ var IntentFiller = class {
362304
362475
  const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
362305
362476
  this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
362306
362477
  if (validStrategies.length === 0) {
362307
- this.logger.warn({ orderId: order.id }, "No profitable strategy found for order");
362308
- this.monitor.emit("orderSkipped", { orderId: order.id, reason: "No profitable strategy" });
362478
+ const priced = eligibleStrategies.some((s4) => s4 !== null);
362479
+ this.logger.warn(
362480
+ { orderId: order.id },
362481
+ priced ? "No profitable strategy found for order" : "No strategy can fill this order"
362482
+ );
362483
+ this.monitor.emit("orderSkipped", {
362484
+ orderId: order.id,
362485
+ reason: priced ? "No profitable strategy" : "No strategy can fill this order"
362486
+ });
362309
362487
  return null;
362310
362488
  }
362311
362489
  this.logger.info(
@@ -362754,16 +362932,63 @@ function byBestOfferFirst(a6, b5) {
362754
362932
  if (a6.available !== b5.available) return a6.available > b5.available ? -1 : 1;
362755
362933
  return a6.order.id < b5.order.id ? -1 : 1;
362756
362934
  }
362935
+ function whyUnmatched(orders, incoming, resolve11, now = /* @__PURE__ */ new Date()) {
362936
+ let furthest = -1;
362937
+ for (const order of orders) {
362938
+ const failed = firstFailed(order, incoming, resolve11, now);
362939
+ furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
362940
+ }
362941
+ const { source, destination, inputSymbol } = incoming;
362942
+ if (furthest < CHECKS.length) {
362943
+ switch (CHECKS[Math.max(furthest, 0)]) {
362944
+ case "open":
362945
+ return "no limit order is open";
362946
+ case "chain":
362947
+ return `no open limit order fills on ${destination}`;
362948
+ case "input":
362949
+ return `no limit order on ${destination} takes ${inputSymbol} in`;
362950
+ case "output":
362951
+ return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
362952
+ case "source":
362953
+ return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
362954
+ }
362955
+ }
362956
+ const priced = orders.filter((order) => firstFailed(order, incoming, resolve11, now) === null).map((order) => ({
362957
+ order,
362958
+ offer: offerFor({
362959
+ side: order.side,
362960
+ inputAmount: incoming.inputNet,
362961
+ price: BigInt(order.price),
362962
+ outputDecimals: incoming.outputDecimals
362963
+ })
362964
+ }));
362965
+ if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
362966
+ return "the limit orders that meet its rate have nothing left to pay out";
362967
+ }
362968
+ const best = priced.reduce((a6, b5) => b5.offer > a6.offer ? b5 : a6);
362969
+ const symbol3 = limitOrderLegs(best.order).output;
362970
+ return `it asks for ${readable(incoming.requestedOutput)} ${symbol3}; the best limit order offers ${readable(best.offer)} ${symbol3}`;
362971
+ }
362972
+ var CHECKS = ["open", "chain", "input", "output", "source"];
362757
362973
  function serves(order, incoming, resolve11, now) {
362758
- if (order.status !== "open") return false;
362759
- if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return false;
362760
- if (order.fillChain !== incoming.destination) return false;
362974
+ return firstFailed(order, incoming, resolve11, now) === null;
362975
+ }
362976
+ function firstFailed(order, incoming, resolve11, now) {
362977
+ if (order.status !== "open") return "open";
362978
+ if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
362979
+ if (order.fillChain !== incoming.destination) return "chain";
362761
362980
  const legs = limitOrderLegs(order);
362762
- if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return false;
362981
+ if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
362763
362982
  const outputToken = resolve11(legs.output, order.fillChain);
362764
- if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return false;
362765
- if (incoming.source === incoming.destination) return true;
362766
- return order.acceptedSources.includes(incoming.source);
362983
+ if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
362984
+ if (incoming.source === incoming.destination) return null;
362985
+ return order.acceptedSources.includes(incoming.source) ? null : "source";
362986
+ }
362987
+ function readable(amount) {
362988
+ const [whole, fraction] = toHuman(amount).split(".");
362989
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
362990
+ const cut = fraction?.slice(0, 6).replace(/0+$/, "");
362991
+ return cut ? `${grouped}.${cut}` : grouped;
362767
362992
  }
362768
362993
 
362769
362994
  // src/orderbook/usd.ts
@@ -362907,15 +363132,20 @@ var FXFiller = class _FXFiller {
362907
363132
  }
362908
363133
  try {
362909
363134
  if (order.inputs.length !== order.output.assets.length) {
362910
- this.logger.debug(
362911
- { inputs: order.inputs.length, outputs: order.output.assets.length },
363135
+ this.logger.info(
363136
+ { orderId: order.id, inputs: order.inputs.length, outputs: order.output.assets.length },
362912
363137
  "Order input/output length mismatch or empty"
362913
363138
  );
362914
363139
  return false;
362915
363140
  }
362916
363141
  if ((await this.matchOrder(order)).length === 0) {
362917
- this.logger.debug(
362918
- { orderId: order.id, sourceChain: order.source, destChain: order.destination },
363142
+ this.logger.info(
363143
+ {
363144
+ orderId: order.id,
363145
+ sourceChain: order.source,
363146
+ destChain: order.destination,
363147
+ reason: await this.explainUnmatched(order)
363148
+ },
362919
363149
  "No limit order matches this order"
362920
363150
  );
362921
363151
  return false;
@@ -363457,30 +363687,53 @@ var FXFiller = class _FXFiller {
363457
363687
  */
363458
363688
  async matchLeg(order, leg) {
363459
363689
  if (!this.limitOrders) return [];
363690
+ const incoming = await this.incomingFor(order, leg);
363691
+ if (!incoming) return [];
363692
+ return matchLimitOrders(
363693
+ await this.limitOrders.open(),
363694
+ incoming,
363695
+ (symbol3, chain2) => this.registry.getAddress(symbol3, chain2)
363696
+ );
363697
+ }
363698
+ /** Why no limit order serves the order, leg by leg, for the line that says it was passed over. */
363699
+ async explainUnmatched(order) {
363700
+ if (!this.limitOrders) return "limit orders are not loaded";
363701
+ const open2 = await this.limitOrders.open();
363702
+ const reasons = [];
363703
+ for (let leg = 0; leg < order.inputs.length; leg++) {
363704
+ const incoming = await this.incomingFor(order, leg);
363705
+ const reason = incoming ? whyUnmatched(open2, incoming, (symbol3, chain2) => this.registry.getAddress(symbol3, chain2)) : `its input token is not in the asset registry on ${order.source}`;
363706
+ reasons.push(order.inputs.length > 1 ? `leg ${leg}: ${reason}` : reason);
363707
+ }
363708
+ return reasons.join("; ");
363709
+ }
363710
+ /**
363711
+ * One leg as the matcher sees it, or null when its input token is not one the asset
363712
+ * registry knows on the source chain.
363713
+ *
363714
+ * Amounts cross into the matcher at 1e18, the unit limit orders are kept in.
363715
+ */
363716
+ async incomingFor(order, leg) {
363460
363717
  const input = order.inputs[leg];
363461
363718
  const output3 = order.output.assets[leg];
363462
- if (!input || !output3) return [];
363719
+ if (!input || !output3) return null;
363463
363720
  const inputToken = bytes32ToBytes20(input.token);
363464
363721
  const outputToken = bytes32ToBytes20(output3.token);
363465
363722
  const inputSymbol = this.registry.symbolFor(inputToken, order.source);
363466
- if (!inputSymbol) return [];
363723
+ if (!inputSymbol) return null;
363467
363724
  const [inputDecimals, outputDecimals] = await Promise.all([
363468
363725
  this.contractService.getTokenDecimals(inputToken, order.source),
363469
363726
  this.contractService.getTokenDecimals(outputToken, order.destination)
363470
363727
  ]);
363471
- return matchLimitOrders(
363472
- await this.limitOrders.open(),
363473
- {
363474
- source: order.source,
363475
- destination: order.destination,
363476
- inputSymbol,
363477
- outputToken,
363478
- inputNet: toScaled(input.amount, inputDecimals),
363479
- requestedOutput: toScaled(output3.amount, outputDecimals),
363480
- outputDecimals
363481
- },
363482
- (symbol3, chain2) => this.registry.getAddress(symbol3, chain2)
363483
- );
363728
+ return {
363729
+ source: order.source,
363730
+ destination: order.destination,
363731
+ inputSymbol,
363732
+ outputToken,
363733
+ inputNet: toScaled(input.amount, inputDecimals),
363734
+ requestedOutput: toScaled(output3.amount, outputDecimals),
363735
+ outputDecimals
363736
+ };
363484
363737
  }
363485
363738
  /**
363486
363739
  * The order's input in **USD**, or null when no limit order connects its input
@@ -365232,6 +365485,8 @@ var MAX_BLOCK_RANGE = 1000n;
365232
365485
  var PUBLISH_CHUNK = 250;
365233
365486
  var yieldToConsumers = () => new Promise((resolve11) => setImmediate(resolve11));
365234
365487
  var STOP_DRAIN_TIMEOUT_MS = 5e3;
365488
+ var STALL_THRESHOLD_MS = 45e3;
365489
+ var STALL_REPORT_INTERVAL_MS = 6e4;
365235
365490
  var ChainScanner = class {
365236
365491
  constructor(target, loggers = defaultLoggerContext(), scanIntervalMs = DEFAULT_SCAN_INTERVAL_MS) {
365237
365492
  this.target = target;
@@ -365245,6 +365500,18 @@ var ChainScanner = class {
365245
365500
  this.errorHandler = () => {
365246
365501
  };
365247
365502
  this.stopped = false;
365503
+ /**
365504
+ * How far past the cursor the next pass reads, as `toBlock - fromBlock`:
365505
+ * halved after a failed read, doubled after a good one, between 0 (one block)
365506
+ * and {@link MAX_BLOCK_RANGE}.
365507
+ *
365508
+ * Free endpoints cap `eth_getLogs` ranges far below the full span — on
365509
+ * Arbitrum three of seven refused 100 blocks — and the cursor never moves
365510
+ * past a range it failed to read. At a fixed span a catch-up that outgrew
365511
+ * those caps was stuck for good: the range only grew, so the capped endpoints
365512
+ * never rejoined and the quorum never formed.
365513
+ */
365514
+ this.span = MAX_BLOCK_RANGE;
365248
365515
  this.logger = loggers.get("chain-scanner");
365249
365516
  this.quorumClient = new QuorumPublicClient(target.chainId, target.rpcUrls, loggers);
365250
365517
  if (this.quorumClient.size > 1) {
@@ -365287,8 +365554,9 @@ var ChainScanner = class {
365287
365554
  if (this.timer || this.stopped) return;
365288
365555
  this.logger.info({ chainId: this.target.chainId }, "Block scanner started");
365289
365556
  this.timer = setInterval(() => {
365290
- if (this.mutex.isLocked()) return;
365557
+ if (this.mutex.isLocked()) return this.reportStall();
365291
365558
  void this.mutex.runExclusive(async () => {
365559
+ this.pass = { phase: "head", startedAt: Date.now(), reportedAt: 0 };
365292
365560
  try {
365293
365561
  await this.scan();
365294
365562
  } catch (error) {
@@ -365302,10 +365570,66 @@ var ChainScanner = class {
365302
365570
  "Error in block scanner"
365303
365571
  );
365304
365572
  this.errorHandler(error);
365573
+ } finally {
365574
+ this.notePassEnd();
365305
365575
  }
365306
365576
  });
365307
365577
  }, this.scanIntervalMs);
365308
365578
  }
365579
+ /**
365580
+ * Records which await the current pass is sitting on, for the watchdog.
365581
+ *
365582
+ * A no-op when no pass is registered — `scan()` is driven directly by tests
365583
+ * and by nothing else, so there is no watchdog to inform.
365584
+ */
365585
+ enter(phase) {
365586
+ if (this.pass) this.pass.phase = phase;
365587
+ }
365588
+ /**
365589
+ * Says that a pass has outlived the quorum budgets and names the phase it is
365590
+ * stuck in, then repeats every {@link STALL_REPORT_INTERVAL_MS} until it ends.
365591
+ *
365592
+ * Observation only: the pass is left alone. Aborting it would need a cancel
365593
+ * path through the quorum client that does not exist, and the cursor is safe
365594
+ * either way — a pass that never completes never advances it.
365595
+ */
365596
+ reportStall() {
365597
+ const pass = this.pass;
365598
+ if (!pass) return;
365599
+ const now = Date.now();
365600
+ const heldMs = now - pass.startedAt;
365601
+ if (heldMs < STALL_THRESHOLD_MS) return;
365602
+ if (pass.reportedAt !== 0 && now - pass.reportedAt < STALL_REPORT_INTERVAL_MS) return;
365603
+ pass.reportedAt = now;
365604
+ this.logger.warn(
365605
+ {
365606
+ chainId: this.target.chainId,
365607
+ phase: pass.phase,
365608
+ heldMs,
365609
+ cursor: this.cursor?.toString(),
365610
+ rpcUrls: this.target.rpcUrls
365611
+ },
365612
+ "Block scan pass has outlived its budget \u2014 this chain is not being scanned"
365613
+ );
365614
+ }
365615
+ /**
365616
+ * Closes out a pass, and says so if the watchdog complained about it — without
365617
+ * this the log shows a chain going quiet and never shows it coming back.
365618
+ */
365619
+ notePassEnd() {
365620
+ const pass = this.pass;
365621
+ this.pass = void 0;
365622
+ if (!pass || pass.reportedAt === 0) return;
365623
+ this.logger.warn(
365624
+ {
365625
+ chainId: this.target.chainId,
365626
+ phase: pass.phase,
365627
+ heldMs: Date.now() - pass.startedAt,
365628
+ cursor: this.cursor?.toString()
365629
+ },
365630
+ "Block scan pass finished after outliving its budget \u2014 scanning has resumed"
365631
+ );
365632
+ }
365309
365633
  /**
365310
365634
  * Points this loop at different endpoints, keeping the cursor.
365311
365635
  *
@@ -365347,11 +365671,12 @@ var ChainScanner = class {
365347
365671
  }
365348
365672
  if (currentBlock <= this.cursor) return;
365349
365673
  const fromBlock = this.cursor + 1n;
365350
- const toBlock = fromBlock + MAX_BLOCK_RANGE > currentBlock ? currentBlock : fromBlock + MAX_BLOCK_RANGE;
365674
+ const toBlock = fromBlock + this.span > currentBlock ? currentBlock : fromBlock + this.span;
365351
365675
  this.logger.debug(
365352
365676
  { chainId: this.target.chainId, fromBlock, toBlock, gap: Number(toBlock - fromBlock) },
365353
365677
  "Scanning blocks"
365354
365678
  );
365679
+ this.enter("logs");
365355
365680
  let logs;
365356
365681
  try {
365357
365682
  logs = await retryPromise(
@@ -365373,6 +365698,7 @@ var ChainScanner = class {
365373
365698
  );
365374
365699
  } catch (error) {
365375
365700
  if (isBlockRangeError(error)) return;
365701
+ this.narrow(toBlock - fromBlock);
365376
365702
  throw error;
365377
365703
  }
365378
365704
  if (this.stopped) return;
@@ -365383,6 +365709,7 @@ var ChainScanner = class {
365383
365709
  { chainId: this.target.chainId, fromBlock, toBlock, eventCount: placed.length },
365384
365710
  "Found OrderPlaced events in block scan"
365385
365711
  );
365712
+ this.enter("publish-orders");
365386
365713
  await this.publishOrders(placed);
365387
365714
  }
365388
365715
  if (filled.length > 0) {
@@ -365390,10 +365717,36 @@ var ChainScanner = class {
365390
365717
  { chainId: this.target.chainId, fromBlock, toBlock, eventCount: filled.length },
365391
365718
  "Found OrderFilled events in block scan"
365392
365719
  );
365720
+ this.enter("publish-fills");
365393
365721
  await this.publishFills(filled);
365394
365722
  }
365395
365723
  if (this.stopped) return;
365396
365724
  this.cursor = toBlock;
365725
+ this.widen();
365726
+ }
365727
+ /**
365728
+ * Halves the span below the range that just failed, so the next pass asks
365729
+ * for something a range-capped endpoint will answer. Halving the attempted
365730
+ * range rather than the span matters near the head, where a pass reads fewer
365731
+ * blocks than the span allows.
365732
+ */
365733
+ narrow(attempted) {
365734
+ const next = attempted / 2n;
365735
+ if (next >= this.span) return;
365736
+ this.span = next;
365737
+ this.logger.warn(
365738
+ { chainId: this.target.chainId, span: Number(next) },
365739
+ "Narrowing the block scan range after a failed read"
365740
+ );
365741
+ }
365742
+ /** Doubles the span back toward {@link MAX_BLOCK_RANGE} after a good read. */
365743
+ widen() {
365744
+ if (this.span >= MAX_BLOCK_RANGE) return;
365745
+ const next = this.span === 0n ? 1n : this.span * 2n;
365746
+ this.span = next > MAX_BLOCK_RANGE ? MAX_BLOCK_RANGE : next;
365747
+ if (this.span === MAX_BLOCK_RANGE) {
365748
+ this.logger.info({ chainId: this.target.chainId }, "Block scan range back to full");
365749
+ }
365397
365750
  }
365398
365751
  async publishOrders(logs) {
365399
365752
  const rebuilt = reconstructOrdersFromLogs(logs, {
@@ -365762,7 +366115,7 @@ var LimitOrderController = class {
365762
366115
  ]);
365763
366116
  return { order, fills, bids };
365764
366117
  }
365765
- /** The pairs the orderbook lists, and the smallest payout each token may carry. */
366118
+ /** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
365766
366119
  books() {
365767
366120
  return this.service.books();
365768
366121
  }