@hyperbridge/simplex 0.16.5 → 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,
@@ -318650,10 +318793,16 @@ var CANCEL_ORDER_MUTATION = `
318650
318793
  `;
318651
318794
  var OrderbookRequestError2 = class extends Error {
318652
318795
  };
318796
+ function graphqlEndpoint(url2) {
318797
+ const trimmed = url2.trim();
318798
+ if (trimmed.includes("?")) return trimmed;
318799
+ const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
318800
+ return withoutTrailingSlash.endsWith("/graphql") ? withoutTrailingSlash : `${withoutTrailingSlash}/graphql`;
318801
+ }
318653
318802
  var OrderbookClient = class {
318654
318803
  constructor(url2, requestTimeoutMs, loggers = defaultLoggerContext()) {
318655
- this.url = url2;
318656
318804
  this.requestTimeoutMs = requestTimeoutMs;
318805
+ this.url = graphqlEndpoint(url2);
318657
318806
  this.logger = loggers.get("orderbook");
318658
318807
  }
318659
318808
  /** The limits to validate against before posting, plus the books on offer. */
@@ -318751,7 +318900,9 @@ var OrderbookClient = class {
318751
318900
  clearTimeout(timer);
318752
318901
  }
318753
318902
  if (!response.ok) {
318754
- throw new OrderbookRequestError2(`Orderbook returned HTTP ${response.status} ${response.statusText}`);
318903
+ throw new OrderbookRequestError2(
318904
+ `Orderbook at ${this.url} returned HTTP ${response.status} ${response.statusText}`
318905
+ );
318755
318906
  }
318756
318907
  const body = await response.json().catch((err) => {
318757
318908
  throw new OrderbookRequestError2(`Orderbook returned a body that is not JSON: ${describe(err)}`);
@@ -318870,7 +319021,7 @@ var HEARTBEAT_TYPES = {
318870
319021
  var LimitOrderValidationError = class extends Error {
318871
319022
  };
318872
319023
  var LimitOrderService = class {
318873
- constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce) {
319024
+ constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce, vaultBalances) {
318874
319025
  this.store = store;
318875
319026
  this.client = client;
318876
319027
  this.contractService = contractService;
@@ -318880,6 +319031,7 @@ var LimitOrderService = class {
318880
319031
  this.defaultTtlSecs = defaultTtlSecs;
318881
319032
  this.delegationService = delegationService;
318882
319033
  this.startingNonce = startingNonce;
319034
+ this.vaultBalances = vaultBalances;
318883
319035
  this.logger = loggers.get("limit-orders");
318884
319036
  }
318885
319037
  /**
@@ -319015,6 +319167,18 @@ var LimitOrderService = class {
319015
319167
  throw err;
319016
319168
  }
319017
319169
  }
319170
+ /**
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.
319173
+ *
319174
+ * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
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.
319177
+ */
319178
+ async books() {
319179
+ const limits = await this.limits();
319180
+ return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes, chains: limits.chains ?? [] };
319181
+ }
319018
319182
  /**
319019
319183
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
319020
319184
  *
@@ -319095,6 +319259,18 @@ var LimitOrderService = class {
319095
319259
  );
319096
319260
  }
319097
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
+ }
319098
319274
  if (ttlSecs < limits.serverInfo.minOrderTtlSecs) {
319099
319275
  throw new LimitOrderValidationError(
319100
319276
  `ttlSecs must be at least the orderbook's minimum of ${limits.serverInfo.minOrderTtlSecs}; got ${ttlSecs}`
@@ -319122,28 +319298,61 @@ var LimitOrderService = class {
319122
319298
  * balance on the fill chain, so the operator hears it while they are creating
319123
319299
  * the order.
319124
319300
  *
319301
+ * A fill pays out of the wallet and then withdraws any shortfall from the
319302
+ * configured ERC-4626 vaults in the same batch, so what those vaults hold of
319303
+ * the payout token backs the order too and is counted here. Without it an
319304
+ * operator who sweeps inventory into a vault is refused orders their filler
319305
+ * would have filled.
319306
+ *
319125
319307
  * Each order is checked against the whole balance, not against what is left of
319126
319308
  * it after the others. One balance backs every order resting on it — that is
319127
319309
  * what quoting both sides of a book is — and the orderbook says as much: it
319128
319310
  * advertises each entry at `min(quoted, balance)` rather than dividing the
319129
319311
  * balance between them. Whichever order fills first draws the inventory down,
319130
319312
  * and the rest are cut to what is left. Netting them here would refuse the
319131
- * second side of every book.
319313
+ * second side of every book. Vault positions are counted the same way: the
319314
+ * whole position, not what is left after reservations for fills in flight.
319132
319315
  */
319133
319316
  async assertWalletCanPay(symbol3, chain2, payout) {
319134
319317
  const token = this.assetRegistry.getAddress(symbol3, chain2);
319135
319318
  if (!token) return;
319136
319319
  const decimals = await this.decimalsFor(symbol3, token, chain2);
319137
- const balance = toScaled(
319320
+ const wallet = toScaled(
319138
319321
  await this.contractService.getTokenBalance(chain2, token, this.signer.address),
319139
319322
  decimals
319140
319323
  );
319141
- if (balance < payout) {
319324
+ const vaults = await this.vaultHoldings(token, chain2);
319325
+ if (wallet + vaults < payout) {
319326
+ const held = vaults > 0n ? `${toHuman(wallet)} in the wallet and ${toHuman(vaults)} in vaults` : toHuman(wallet);
319142
319327
  throw new LimitOrderValidationError(
319143
- `The wallet holds ${toHuman(balance)} ${symbol3} on ${chain2}, which cannot pay out ${toHuman(payout)}`
319328
+ `The wallet holds ${held} ${symbol3} on ${chain2}, which cannot pay out ${toHuman(payout)}`
319144
319329
  );
319145
319330
  }
319146
319331
  }
319332
+ /**
319333
+ * What the configured vaults hold of `token` on `chain`, in scaled units.
319334
+ *
319335
+ * Zero when no vault is configured. A snapshot that cannot be read is also
319336
+ * zero: the wallet alone then has to cover the order, which refuses one the
319337
+ * filler might have managed rather than accepting one it could not.
319338
+ */
319339
+ async vaultHoldings(token, chain2) {
319340
+ if (!this.vaultBalances) return 0n;
319341
+ let positions;
319342
+ try {
319343
+ positions = await this.vaultBalances.getBalanceSnapshot(chain2);
319344
+ } catch (err) {
319345
+ this.logger.warn({ chain: chain2, token, err }, "Could not read vault balances; backing the order on the wallet alone");
319346
+ return 0n;
319347
+ }
319348
+ const wanted = token.toLowerCase();
319349
+ let total = 0n;
319350
+ for (const position of positions) {
319351
+ if (position.chain !== chain2 || position.asset.toLowerCase() !== wanted) continue;
319352
+ total += toScaled(position.positionAssets, position.decimals);
319353
+ }
319354
+ return total;
319355
+ }
319147
319356
  /**
319148
319357
  * Works a limit order down by what a fill delivered, and puts the rest back on
319149
319358
  * the orderbook.
@@ -320724,7 +320933,7 @@ var NotificationService = class extends EventEmitter6 {
320724
320933
  title: "Swap filled",
320725
320934
  body: swapBody(event),
320726
320935
  tag: `simplex-swap-${id2}`,
320727
- url: "./orders"
320936
+ url: "./history"
320728
320937
  }).catch((error) => this.logger.error({ err: error }, "Could not publish swap notification"));
320729
320938
  }
320730
320939
  };
@@ -320917,6 +321126,7 @@ init_chains2();
320917
321126
  init_alchemy();
320918
321127
  init_prompt_utils();
320919
321128
  init_state();
321129
+ init_defaults8();
320920
321130
  init_Logger();
320921
321131
  var logger2 = getLogger("setup");
320922
321132
  async function defaultRpcRequest(url2, method, params) {
@@ -320963,6 +321173,18 @@ async function handleSetupRequest(server, setup, req, res, path2, method) {
320963
321173
  };
320964
321174
  return sendJson(res, 200, defaults3);
320965
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
+ }
320966
321188
  if (method !== "POST") return sendJson(res, 405, { error: "Method not allowed" });
320967
321189
  let body;
320968
321190
  try {
@@ -321732,6 +321954,11 @@ var UiServer = class {
321732
321954
  if (method === "POST") return this.handleLimitOrderCreate(req, res);
321733
321955
  return sendJson(res, 405, { error: "Method not allowed" });
321734
321956
  }
321957
+ if (path2 === "/api/orderbook/books") {
321958
+ if (this.mode !== "operator") return sendJson(res, 409, { error: "Filler is not running" });
321959
+ if (method !== "GET") return sendJson(res, 405, { error: "Method not allowed" });
321960
+ return this.handleLimitOrders(res, () => this.operator.limitOrders.books());
321961
+ }
321735
321962
  const limitOrderMatch = path2.match(/^\/api\/limit-orders\/([\w-]+)$/);
321736
321963
  if (limitOrderMatch) {
321737
321964
  if (this.mode !== "operator") return sendJson(res, 409, { error: "Filler is not running" });
@@ -323269,14 +323496,10 @@ var RATE_LIMIT_RPC_CODES = /* @__PURE__ */ new Set([-32005, -32097, -32016, 429]
323269
323496
  var RATE_LIMIT_SUSPENSION_MS = 5 * 6e4;
323270
323497
  var SCAN_BUDGET = {
323271
323498
  timeoutMs: 5e3,
323272
- retryCount: 1,
323273
- retryDelayMs: 500,
323274
323499
  deadlineMs: 12e3
323275
323500
  };
323276
323501
  var CONFIRMATION_BUDGET = {
323277
323502
  timeoutMs: 1e4,
323278
- retryCount: 1,
323279
- retryDelayMs: 500,
323280
323503
  deadlineMs: 3e4
323281
323504
  };
323282
323505
  var MALFORMED_RESPONSE_SUSPENSION_MS = 3e4;
@@ -323368,8 +323591,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323368
323591
  chain: chain2,
323369
323592
  transport: http(url2, {
323370
323593
  timeout: budget.timeoutMs,
323371
- retryCount: budget.retryCount,
323372
- 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
323373
323602
  })
323374
323603
  })
323375
323604
  );
@@ -323383,11 +323612,10 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323383
323612
  return this.rpcUrls.filter((url2) => (_QuorumPublicClient.benchedUntil.get(url2) ?? 0) > now);
323384
323613
  }
323385
323614
  /**
323386
- * The endpoints this call queries — everyone not benched — and the quorum
323387
- * bar for exactly that set. A benched endpoint is dropped outright: it
323388
- * answers nothing while throttled, so counting it could only fail calls the
323389
- * remaining endpoints agree on. With every endpoint benched, everyone is
323390
- * 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.
323391
323619
  */
323392
323620
  participants() {
323393
323621
  const now = Date.now();
@@ -323395,12 +323623,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323395
323623
  const active = all7.filter(({ idx }) => (_QuorumPublicClient.benchedUntil.get(this.rpcUrls[idx]) ?? 0) <= now);
323396
323624
  const allBenched = active.length === 0;
323397
323625
  const queried = allBenched ? all7 : active;
323398
- return {
323399
- queried,
323400
- skipped: all7.length - queried.length,
323401
- threshold: quorumThreshold(queried.length),
323402
- allBenched
323403
- };
323626
+ return { queried, skipped: all7.length - queried.length, allBenched };
323404
323627
  }
323405
323628
  /**
323406
323629
  * Records a task failure against its endpoint. Only the failures
@@ -323445,17 +323668,21 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323445
323668
  * retries would otherwise stall every confirmation poll). Stragglers settle
323446
323669
  * out of band and are ignored. `tryDecide` returns undefined to keep
323447
323670
  * waiting; once every task has settled, `finalize` must decide (it throws
323448
- * the QuorumError when the full response set still has no quorum). Early
323449
- * exit never weakens the trust model — a decision still requires the same
323450
- * 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.
323451
323678
  *
323452
323679
  * `deadlineMs` bounds the other direction: when the bar is NOT reachable
323453
323680
  * from the fast responders, the call used to wait out every straggler, and
323454
323681
  * the caller's own retry loop multiplied that into minutes of a chain not
323455
323682
  * being scanned at all, silently. At the deadline the outstanding endpoints
323456
323683
  * are treated as failures — named in the QuorumError, so the operator sees
323457
- * which ones held the call up — and `finalize` decides on the rest. The
323458
- * 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.
323459
323686
  */
323460
323687
  settleUntilQuorum(tasks, tryDecide, finalize) {
323461
323688
  return new Promise((resolve11, reject) => {
@@ -323473,13 +323700,14 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323473
323700
  const evaluate = () => {
323474
323701
  if (done) return;
323475
323702
  try {
323476
- const early = tryDecide(fulfilled);
323703
+ const threshold = quorumThreshold(tasks.length - failures.length);
323704
+ const early = tryDecide(fulfilled, threshold);
323477
323705
  if (early !== void 0) {
323478
323706
  finish(() => resolve11(early));
323479
323707
  return;
323480
323708
  }
323481
323709
  if (outstanding === 0) {
323482
- const value = finalize(fulfilled, failures);
323710
+ const value = finalize(fulfilled, failures, threshold);
323483
323711
  finish(() => resolve11(value));
323484
323712
  }
323485
323713
  } catch (error) {
@@ -323497,7 +323725,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323497
323725
  error: new Error(`no answer within the ${this.deadlineMs}ms quorum deadline`)
323498
323726
  }));
323499
323727
  try {
323500
- const value = finalize(fulfilled, [...failures, ...stragglers]);
323728
+ const value = finalize(fulfilled, [...failures, ...stragglers], quorumThreshold(fulfilled.length));
323501
323729
  finish(() => resolve11(value));
323502
323730
  } catch (error) {
323503
323731
  finish(() => reject(error));
@@ -323524,22 +323752,22 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323524
323752
  });
323525
323753
  }
323526
323754
  /**
323527
- * Highest block head backed by a full quorum. Throws {@link QuorumError}
323528
- * 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.
323529
323757
  */
323530
323758
  async getBlockNumber() {
323531
- const { queried, skipped, threshold, allBenched } = this.participants();
323759
+ const { queried, skipped, allBenched } = this.participants();
323532
323760
  return this.settleUntilQuorum(
323533
323761
  queried.map(({ idx, client }) => ({ idx, task: client.getBlockNumber() })),
323534
323762
  // Early exit as soon as a quorum is satisfiable. Late voters could
323535
323763
  // only raise the reported head; the earlier (lower) head is
323536
323764
  // conservative for every consumer (fewer confirmations counted,
323537
323765
  // smaller scan windows).
323538
- (fulfilled) => this.quorumHead(
323766
+ (fulfilled, threshold) => this.quorumHead(
323539
323767
  fulfilled.map((f7) => f7.value),
323540
323768
  threshold
323541
323769
  ) ?? void 0,
323542
- (fulfilled, failures) => {
323770
+ (fulfilled, failures, threshold) => {
323543
323771
  const head = this.quorumHead(
323544
323772
  fulfilled.map((f7) => f7.value),
323545
323773
  threshold
@@ -323580,7 +323808,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323580
323808
  }
323581
323809
  return views;
323582
323810
  };
323583
- const { queried, skipped, threshold, allBenched } = this.participants();
323811
+ const { queried, skipped, allBenched } = this.participants();
323584
323812
  return this.settleUntilQuorum(
323585
323813
  queried.map(({ idx, client }) => ({
323586
323814
  idx,
@@ -323599,8 +323827,8 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323599
323827
  // Early exit only on a POSITIVE quorum: an agreeing inclusion group can
323600
323828
  // only grow, so the first satisfiable quorum is final. Not-found votes
323601
323829
  // never decide early — "not confirmed" needs the full response set.
323602
- (fulfilled) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
323603
- (fulfilled, failures) => {
323830
+ (fulfilled, threshold) => aggregateConfirmations(toViews(fulfilled), threshold) ?? void 0,
323831
+ (fulfilled, failures, threshold) => {
323604
323832
  const confirmations = aggregateConfirmations(toViews(fulfilled), threshold);
323605
323833
  if (confirmations === null) {
323606
323834
  throw new QuorumError(
@@ -323629,7 +323857,7 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323629
323857
  }
323630
323858
  return groups;
323631
323859
  };
323632
- const { queried, skipped, threshold, allBenched } = this.participants();
323860
+ const { queried, skipped, allBenched } = this.participants();
323633
323861
  return this.settleUntilQuorum(
323634
323862
  queried.map(({ idx, client }) => ({
323635
323863
  idx,
@@ -323641,13 +323869,13 @@ var _QuorumPublicClient = class _QuorumPublicClient {
323641
323869
  // two disjoint groups can't both hold one), so the first satisfiable
323642
323870
  // agreement is final — no need to wait out stragglers that could
323643
323871
  // only join or lose.
323644
- (fulfilled) => {
323872
+ (fulfilled, threshold) => {
323645
323873
  for (const group of groupOf(fulfilled).values()) {
323646
323874
  if (group.providerIdxs.length >= threshold) return group.result;
323647
323875
  }
323648
323876
  return void 0;
323649
323877
  },
323650
- (fulfilled, failures) => {
323878
+ (fulfilled, failures, threshold) => {
323651
323879
  const groups = groupOf(fulfilled);
323652
323880
  for (const group of groups.values()) {
323653
323881
  if (group.providerIdxs.length >= threshold) return group.result;
@@ -362247,8 +362475,15 @@ var IntentFiller = class {
362247
362475
  const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
362248
362476
  this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
362249
362477
  if (validStrategies.length === 0) {
362250
- this.logger.warn({ orderId: order.id }, "No profitable strategy found for order");
362251
- 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
+ });
362252
362487
  return null;
362253
362488
  }
362254
362489
  this.logger.info(
@@ -362697,16 +362932,63 @@ function byBestOfferFirst(a6, b5) {
362697
362932
  if (a6.available !== b5.available) return a6.available > b5.available ? -1 : 1;
362698
362933
  return a6.order.id < b5.order.id ? -1 : 1;
362699
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"];
362700
362973
  function serves(order, incoming, resolve11, now) {
362701
- if (order.status !== "open") return false;
362702
- if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return false;
362703
- 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";
362704
362980
  const legs = limitOrderLegs(order);
362705
- if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return false;
362981
+ if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
362706
362982
  const outputToken = resolve11(legs.output, order.fillChain);
362707
- if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return false;
362708
- if (incoming.source === incoming.destination) return true;
362709
- 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;
362710
362992
  }
362711
362993
 
362712
362994
  // src/orderbook/usd.ts
@@ -362850,15 +363132,20 @@ var FXFiller = class _FXFiller {
362850
363132
  }
362851
363133
  try {
362852
363134
  if (order.inputs.length !== order.output.assets.length) {
362853
- this.logger.debug(
362854
- { 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 },
362855
363137
  "Order input/output length mismatch or empty"
362856
363138
  );
362857
363139
  return false;
362858
363140
  }
362859
363141
  if ((await this.matchOrder(order)).length === 0) {
362860
- this.logger.debug(
362861
- { 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
+ },
362862
363149
  "No limit order matches this order"
362863
363150
  );
362864
363151
  return false;
@@ -363400,30 +363687,53 @@ var FXFiller = class _FXFiller {
363400
363687
  */
363401
363688
  async matchLeg(order, leg) {
363402
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) {
363403
363717
  const input = order.inputs[leg];
363404
363718
  const output3 = order.output.assets[leg];
363405
- if (!input || !output3) return [];
363719
+ if (!input || !output3) return null;
363406
363720
  const inputToken = bytes32ToBytes20(input.token);
363407
363721
  const outputToken = bytes32ToBytes20(output3.token);
363408
363722
  const inputSymbol = this.registry.symbolFor(inputToken, order.source);
363409
- if (!inputSymbol) return [];
363723
+ if (!inputSymbol) return null;
363410
363724
  const [inputDecimals, outputDecimals] = await Promise.all([
363411
363725
  this.contractService.getTokenDecimals(inputToken, order.source),
363412
363726
  this.contractService.getTokenDecimals(outputToken, order.destination)
363413
363727
  ]);
363414
- return matchLimitOrders(
363415
- await this.limitOrders.open(),
363416
- {
363417
- source: order.source,
363418
- destination: order.destination,
363419
- inputSymbol,
363420
- outputToken,
363421
- inputNet: toScaled(input.amount, inputDecimals),
363422
- requestedOutput: toScaled(output3.amount, outputDecimals),
363423
- outputDecimals
363424
- },
363425
- (symbol3, chain2) => this.registry.getAddress(symbol3, chain2)
363426
- );
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
+ };
363427
363737
  }
363428
363738
  /**
363429
363739
  * The order's input in **USD**, or null when no limit order connects its input
@@ -364615,7 +364925,10 @@ async function bootFiller(config5, options) {
364615
364925
  runtimeSigner,
364616
364926
  config5.orderbook.defaultTtlSecs ?? DEFAULT_LIMIT_ORDER_TTL_SECONDS,
364617
364927
  new DelegationService(chainClientManager, configService, runtimeSigner),
364618
- options.loggers
364928
+ options.loggers,
364929
+ initialOrderNonce,
364930
+ // A fill withdraws a payout shortfall from the vaults, so what they hold backs an order too.
364931
+ vaultVenue
364619
364932
  );
364620
364933
  intentFiller.setLimitOrderService(limitOrderService);
364621
364934
  const lifecycle = new LimitOrderLifecycle(
@@ -365172,6 +365485,8 @@ var MAX_BLOCK_RANGE = 1000n;
365172
365485
  var PUBLISH_CHUNK = 250;
365173
365486
  var yieldToConsumers = () => new Promise((resolve11) => setImmediate(resolve11));
365174
365487
  var STOP_DRAIN_TIMEOUT_MS = 5e3;
365488
+ var STALL_THRESHOLD_MS = 45e3;
365489
+ var STALL_REPORT_INTERVAL_MS = 6e4;
365175
365490
  var ChainScanner = class {
365176
365491
  constructor(target, loggers = defaultLoggerContext(), scanIntervalMs = DEFAULT_SCAN_INTERVAL_MS) {
365177
365492
  this.target = target;
@@ -365185,6 +365500,18 @@ var ChainScanner = class {
365185
365500
  this.errorHandler = () => {
365186
365501
  };
365187
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;
365188
365515
  this.logger = loggers.get("chain-scanner");
365189
365516
  this.quorumClient = new QuorumPublicClient(target.chainId, target.rpcUrls, loggers);
365190
365517
  if (this.quorumClient.size > 1) {
@@ -365227,8 +365554,9 @@ var ChainScanner = class {
365227
365554
  if (this.timer || this.stopped) return;
365228
365555
  this.logger.info({ chainId: this.target.chainId }, "Block scanner started");
365229
365556
  this.timer = setInterval(() => {
365230
- if (this.mutex.isLocked()) return;
365557
+ if (this.mutex.isLocked()) return this.reportStall();
365231
365558
  void this.mutex.runExclusive(async () => {
365559
+ this.pass = { phase: "head", startedAt: Date.now(), reportedAt: 0 };
365232
365560
  try {
365233
365561
  await this.scan();
365234
365562
  } catch (error) {
@@ -365242,10 +365570,66 @@ var ChainScanner = class {
365242
365570
  "Error in block scanner"
365243
365571
  );
365244
365572
  this.errorHandler(error);
365573
+ } finally {
365574
+ this.notePassEnd();
365245
365575
  }
365246
365576
  });
365247
365577
  }, this.scanIntervalMs);
365248
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
+ }
365249
365633
  /**
365250
365634
  * Points this loop at different endpoints, keeping the cursor.
365251
365635
  *
@@ -365287,11 +365671,12 @@ var ChainScanner = class {
365287
365671
  }
365288
365672
  if (currentBlock <= this.cursor) return;
365289
365673
  const fromBlock = this.cursor + 1n;
365290
- const toBlock = fromBlock + MAX_BLOCK_RANGE > currentBlock ? currentBlock : fromBlock + MAX_BLOCK_RANGE;
365674
+ const toBlock = fromBlock + this.span > currentBlock ? currentBlock : fromBlock + this.span;
365291
365675
  this.logger.debug(
365292
365676
  { chainId: this.target.chainId, fromBlock, toBlock, gap: Number(toBlock - fromBlock) },
365293
365677
  "Scanning blocks"
365294
365678
  );
365679
+ this.enter("logs");
365295
365680
  let logs;
365296
365681
  try {
365297
365682
  logs = await retryPromise(
@@ -365313,6 +365698,7 @@ var ChainScanner = class {
365313
365698
  );
365314
365699
  } catch (error) {
365315
365700
  if (isBlockRangeError(error)) return;
365701
+ this.narrow(toBlock - fromBlock);
365316
365702
  throw error;
365317
365703
  }
365318
365704
  if (this.stopped) return;
@@ -365323,6 +365709,7 @@ var ChainScanner = class {
365323
365709
  { chainId: this.target.chainId, fromBlock, toBlock, eventCount: placed.length },
365324
365710
  "Found OrderPlaced events in block scan"
365325
365711
  );
365712
+ this.enter("publish-orders");
365326
365713
  await this.publishOrders(placed);
365327
365714
  }
365328
365715
  if (filled.length > 0) {
@@ -365330,10 +365717,36 @@ var ChainScanner = class {
365330
365717
  { chainId: this.target.chainId, fromBlock, toBlock, eventCount: filled.length },
365331
365718
  "Found OrderFilled events in block scan"
365332
365719
  );
365720
+ this.enter("publish-fills");
365333
365721
  await this.publishFills(filled);
365334
365722
  }
365335
365723
  if (this.stopped) return;
365336
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
+ }
365337
365750
  }
365338
365751
  async publishOrders(logs) {
365339
365752
  const rebuilt = reconstructOrdersFromLogs(logs, {
@@ -365702,6 +366115,10 @@ var LimitOrderController = class {
365702
366115
  ]);
365703
366116
  return { order, fills, bids };
365704
366117
  }
366118
+ /** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
366119
+ books() {
366120
+ return this.service.books();
366121
+ }
365705
366122
  /** Creates the order, posts it, and reports what the orderbook made of it. */
365706
366123
  async create(request3) {
365707
366124
  const posted = await this.service.create(request3);