@hyperbridge/sdk 2.8.16 → 2.8.17

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.
@@ -18777,20 +18777,27 @@ var ROUTE_LIQUIDITY_QUERY = `
18777
18777
  query RouteLiquidity($route: RouteInput!) {
18778
18778
  books { id base quote }
18779
18779
  routeLiquidity(route: $route) {
18780
- route bestRate depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
18780
+ route bestRate slippageBps depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
18781
18781
  }
18782
18782
  }`;
18783
18783
  var QUOTE_QUERY = `
18784
18784
  query Quote($route: RouteInput!, $amountIn: BigInt!) {
18785
18785
  quote(route: $route, amountIn: $amountIn) {
18786
- route side amountIn amountOut rate fillable depth maxFillableIn
18787
- fills { orderRate amountOut advertisedSize }
18786
+ route side amountIn slippageBps fillable maxFillableIn
18787
+ fills { advertisedSize orderRate amountIn amountOut }
18788
+ }
18789
+ }`;
18790
+ var QUOTE_PESSIMISTIC_QUERY = `
18791
+ query QuotePessimistic($route: RouteInput!, $amountIn: BigInt!) {
18792
+ quotePessimistic(route: $route, amountIn: $amountIn) {
18793
+ route side amountIn amountOut rate priceBucket slippageBps fillable maxFillableIn
18788
18794
  }
18789
18795
  }`;
18790
18796
  var ORDERBOOK_QUERIES = {
18791
18797
  topOfBook: TOP_OF_BOOK_QUERY,
18792
18798
  routeLiquidity: ROUTE_LIQUIDITY_QUERY,
18793
- quote: QUOTE_QUERY
18799
+ quote: QUOTE_QUERY,
18800
+ quotePessimistic: QUOTE_PESSIMISTIC_QUERY
18794
18801
  };
18795
18802
  var HyperFxOrderbook = class {
18796
18803
  client;
@@ -18819,8 +18826,9 @@ var HyperFxOrderbook = class {
18819
18826
  return {
18820
18827
  route: raw.route,
18821
18828
  book,
18822
- side: book.base === route.tokenIn ? "BID" : "ASK",
18823
- bestRate: raw.bestRate === null ? null : BigInt(raw.bestRate),
18829
+ side: sameSymbol(book.base, route.tokenIn) ? "BID" : "ASK",
18830
+ bestRate: optionalBigInt(raw.bestRate),
18831
+ slippageBps: raw.slippageBps,
18824
18832
  depthIn: BigInt(raw.depthIn),
18825
18833
  depthOut: BigInt(raw.depthOut),
18826
18834
  availableLiquidity: BigInt(raw.availableLiquidity),
@@ -18829,7 +18837,10 @@ var HyperFxOrderbook = class {
18829
18837
  solverCount: raw.solverCount
18830
18838
  };
18831
18839
  }
18832
- /** The clearing price for `amountIn` (1e18 tokenIn) on a route, which may combine several solvers' orders. */
18840
+ /**
18841
+ * The optimistic quote for `amountIn` (1e18 tokenIn) on a route: the route's orders, best price
18842
+ * first, each taking what it can at its own price. The fills' `amountOut`s sum to the output.
18843
+ */
18833
18844
  async quote(route, amountIn) {
18834
18845
  const { quote: raw } = await this.request(QUOTE_QUERY, {
18835
18846
  route,
@@ -18839,18 +18850,39 @@ var HyperFxOrderbook = class {
18839
18850
  route: raw.route,
18840
18851
  side: raw.side,
18841
18852
  amountIn: BigInt(raw.amountIn),
18842
- amountOut: BigInt(raw.amountOut),
18843
- rate: raw.rate === null ? null : BigInt(raw.rate),
18853
+ slippageBps: raw.slippageBps,
18844
18854
  fillable: raw.fillable,
18845
- depth: BigInt(raw.depth),
18846
18855
  maxFillableIn: BigInt(raw.maxFillableIn),
18847
18856
  fills: raw.fills.map((fill) => ({
18857
+ advertisedSize: BigInt(fill.advertisedSize),
18848
18858
  orderRate: BigInt(fill.orderRate),
18849
- amountOut: BigInt(fill.amountOut),
18850
- advertisedSize: BigInt(fill.advertisedSize)
18859
+ amountIn: BigInt(fill.amountIn),
18860
+ amountOut: BigInt(fill.amountOut)
18851
18861
  }))
18852
18862
  };
18853
18863
  }
18864
+ /**
18865
+ * The pessimistic quote for `amountIn` (1e18 tokenIn) on a route: one price, from the first level
18866
+ * deep enough to fill the whole trade by itself, at which every order in it fills; the route's
18867
+ * worst price when no one level can but its levels together can.
18868
+ */
18869
+ async quotePessimistic(route, amountIn) {
18870
+ const { quotePessimistic: raw } = await this.request(
18871
+ QUOTE_PESSIMISTIC_QUERY,
18872
+ { route, amountIn: amountIn.toString() }
18873
+ );
18874
+ return {
18875
+ route: raw.route,
18876
+ side: raw.side,
18877
+ amountIn: BigInt(raw.amountIn),
18878
+ amountOut: BigInt(raw.amountOut),
18879
+ rate: optionalBigInt(raw.rate),
18880
+ priceBucket: optionalBigInt(raw.priceBucket),
18881
+ slippageBps: raw.slippageBps,
18882
+ fillable: raw.fillable,
18883
+ maxFillableIn: BigInt(raw.maxFillableIn)
18884
+ };
18885
+ }
18854
18886
  async request(query, variables) {
18855
18887
  try {
18856
18888
  return await this.client.request(query, variables);
@@ -18864,13 +18896,19 @@ function describeError(error) {
18864
18896
  if (graphqlErrors?.length) return graphqlErrors.map((e) => e.message).join("; ");
18865
18897
  return error instanceof Error ? error.message : String(error);
18866
18898
  }
18899
+ function sameSymbol(a, b) {
18900
+ return a.toLowerCase() === b.toLowerCase();
18901
+ }
18867
18902
  function findBook(books, tokenA, tokenB) {
18868
18903
  const book = books.find(
18869
- (b) => b.base === tokenA && b.quote === tokenB || b.base === tokenB && b.quote === tokenA
18904
+ (b) => sameSymbol(b.base, tokenA) && sameSymbol(b.quote, tokenB) || sameSymbol(b.base, tokenB) && sameSymbol(b.quote, tokenA)
18870
18905
  );
18871
18906
  if (!book) throw new OrderbookRequestError(`no book trades ${tokenA} for ${tokenB}`);
18872
18907
  return book;
18873
18908
  }
18909
+ function optionalBigInt(value) {
18910
+ return value === null ? null : BigInt(value);
18911
+ }
18874
18912
  function parseRate(raw) {
18875
18913
  return {
18876
18914
  side: raw.side,
@@ -18935,26 +18973,69 @@ var OrderbookMarket = class {
18935
18973
  configService;
18936
18974
  orderbook;
18937
18975
  async quoteIntent(params, sourceChain, destinationChain) {
18976
+ const orderbook = this.orderbook();
18977
+ if (!params.optimistic) {
18978
+ const { quote: quote2, tokenIn: tokenIn2, tokenOut: tokenOut2 } = await this.priceIntent(
18979
+ params,
18980
+ sourceChain,
18981
+ destinationChain,
18982
+ (route, amountIn) => orderbook.quotePessimistic(route, amountIn),
18983
+ levelQuoteTotals
18984
+ );
18985
+ return {
18986
+ route: quote2.route,
18987
+ side: quote2.side,
18988
+ amountIn: fromOrderbookAmount(quote2.amountIn, tokenIn2.decimals),
18989
+ amountOut: fromOrderbookAmount(quote2.amountOut, tokenOut2.decimals),
18990
+ rate: quote2.rate,
18991
+ priceBucket: quote2.priceBucket,
18992
+ slippageBps: quote2.slippageBps,
18993
+ fillable: quote2.fillable,
18994
+ maxFillableIn: fromOrderbookAmount(quote2.maxFillableIn, tokenIn2.decimals)
18995
+ };
18996
+ }
18997
+ const { quote, tokenIn, tokenOut } = await this.priceIntent(
18998
+ params,
18999
+ sourceChain,
19000
+ destinationChain,
19001
+ (route, amountIn) => orderbook.quote(route, amountIn),
19002
+ swapQuoteTotals
19003
+ );
19004
+ return {
19005
+ route: quote.route,
19006
+ side: quote.side,
19007
+ amountIn: fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
19008
+ slippageBps: quote.slippageBps,
19009
+ fillable: quote.fillable,
19010
+ maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19011
+ legs: quote.fills.map((fill) => ({
19012
+ advertisedSize: fromOrderbookAmount(fill.advertisedSize, tokenOut.decimals),
19013
+ orderRate: fill.orderRate,
19014
+ amountIn: fromOrderbookAmount(fill.amountIn, tokenIn.decimals),
19015
+ amountOut: fromOrderbookAmount(fill.amountOut, tokenOut.decimals)
19016
+ }))
19017
+ };
19018
+ }
19019
+ /**
19020
+ * The fillable quote for an exact input, or for the input found to deliver an
19021
+ * exact output, with the assets it trades.
19022
+ */
19023
+ async priceIntent(params, sourceChain, destinationChain, price, totals) {
18938
19024
  validateQuoteParams(params);
18939
19025
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
18940
19026
  const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
18941
19027
  const route = toRoute(tokenIn, tokenOut);
18942
19028
  if (params.amountIn !== void 0) {
18943
- const quote2 = await this.orderbook().quote(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
18944
- const amountOut2 = quote2.fillable ? fromOrderbookAmount(quote2.amountOut, tokenOut.decimals) : 0n;
18945
- if (amountOut2 === 0n) throw this.insufficient(route, quote2.maxFillableIn, tokenIn);
18946
- return buildQuote("EXACT_INPUT", params.amountIn, amountOut2, quote2, tokenIn, tokenOut);
19029
+ const quote2 = await price(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
19030
+ const { fillable, amountOut, maxFillableIn } = totals(quote2);
19031
+ if (!fillable || fromOrderbookAmount(amountOut, tokenOut.decimals) === 0n) {
19032
+ throw this.insufficient(route, maxFillableIn, tokenIn);
19033
+ }
19034
+ return { quote: quote2, tokenIn, tokenOut };
18947
19035
  }
18948
- const amountOut = params.amountOut;
18949
- const quote = await this.quoteExactOutput(route, toOrderbookAmount(amountOut, tokenOut.decimals), tokenIn);
18950
- return buildQuote(
18951
- "EXACT_OUTPUT",
18952
- fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
18953
- amountOut,
18954
- quote,
18955
- tokenIn,
18956
- tokenOut
18957
- );
19036
+ const targetOut = toOrderbookAmount(params.amountOut, tokenOut.decimals);
19037
+ const quote = await this.quoteExactOutput(route, targetOut, tokenIn, price, totals);
19038
+ return { quote, tokenIn, tokenOut };
18958
19039
  }
18959
19040
  async availableLiquidity(params, sourceChain, destinationChain) {
18960
19041
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
@@ -19007,22 +19088,26 @@ var OrderbookMarket = class {
19007
19088
  }
19008
19089
  /**
19009
19090
  * The orderbook only quotes an input amount, so an output is priced by
19010
- * guessing the input from the best rate and raising it at each clearing
19011
- * price until the quote delivers `targetOut`.
19091
+ * guessing the input from the best rate, then keeping the part of each quote
19092
+ * filled better than its worst rate and re-pricing the rest of `targetOut` at
19093
+ * the input-to-output ratio that part filled at, until a quote delivers it.
19094
+ * That ratio is the orderbook's own, with the protocol fee already taken off.
19012
19095
  */
19013
- async quoteExactOutput(route, targetOut, tokenIn) {
19014
- const orderbook = this.orderbook();
19015
- const liquidity = await orderbook.routeLiquidity(route);
19096
+ async quoteExactOutput(route, targetOut, tokenIn, price, totals) {
19097
+ const liquidity = await this.orderbook().routeLiquidity(route);
19016
19098
  if (liquidity.bestRate === null) throw this.insufficient(route, 0n, tokenIn);
19017
19099
  const inputUnit = toOrderbookAmount(1n, tokenIn.decimals);
19018
19100
  let amountIn = roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
19019
19101
  let lastAmountIn = amountIn;
19020
19102
  for (let round = 0; round < MAX_EXACT_OUTPUT_ROUNDS; round++) {
19021
19103
  if (amountIn > liquidity.maxFillableIn) throw this.insufficient(route, liquidity.maxFillableIn, tokenIn);
19022
- const quote = await orderbook.quote(route, amountIn);
19023
- if (quote.fillable && quote.amountOut >= targetOut) return quote;
19104
+ const served = await price(route, amountIn);
19105
+ const quote = totals(served);
19106
+ if (quote.fillable && quote.amountOut >= targetOut) return served;
19024
19107
  lastAmountIn = amountIn;
19025
- const next = quote.rate === null ? amountIn : roundUpTo(requiredInput(quote.side, targetOut, quote.rate), inputUnit);
19108
+ const betterIn = quote.amountIn - quote.worstIn;
19109
+ const betterOut = quote.amountOut - quote.worstOut;
19110
+ const next = quote.fillable && quote.worstOut > 0n ? betterIn + roundUpTo(divCeil((targetOut - betterOut) * quote.worstIn, quote.worstOut), inputUnit) : amountIn;
19026
19111
  amountIn = next > amountIn ? next : amountIn + inputUnit;
19027
19112
  }
19028
19113
  throw new OrderbookQuoteNotConvergedError(
@@ -19078,24 +19163,32 @@ function toRoute(tokenIn, tokenOut) {
19078
19163
  function requiredInput(side, amountOut, rate) {
19079
19164
  return side === "BID" ? divCeil(amountOut * ORDERBOOK_SCALE, rate) : divCeil(amountOut * rate, ORDERBOOK_SCALE);
19080
19165
  }
19081
- function buildQuote(tradeType, amountIn, amountOut, quote, tokenIn, tokenOut) {
19082
- if (quote.rate === null) throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19083
- const [base3, quoteToken] = quote.side === "BID" ? [tokenIn, tokenOut] : [tokenOut, tokenIn];
19166
+ function swapQuoteTotals(quote) {
19167
+ const worstFill = quote.fills.at(-1);
19168
+ if (quote.fillable && !worstFill)
19169
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no fills");
19084
19170
  return {
19085
- tradeType,
19086
- amountIn,
19087
- amountOut,
19088
- quoteMetadata: {
19089
- sourceChain: tokenIn.chain,
19090
- destinationChain: tokenOut.chain,
19091
- route: quote.route,
19092
- side: quote.side,
19093
- baseTokenSymbol: base3.symbol,
19094
- quoteTokenSymbol: quoteToken.symbol,
19095
- rate: format(quote.rate),
19096
- maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19097
- orderCount: quote.fills.length
19098
- }
19171
+ side: quote.side,
19172
+ amountIn: quote.amountIn,
19173
+ amountOut: quote.fills.reduce((total, fill) => total + fill.amountOut, 0n),
19174
+ fillable: quote.fillable,
19175
+ maxFillableIn: quote.maxFillableIn,
19176
+ worstIn: worstFill?.amountIn ?? 0n,
19177
+ worstOut: worstFill?.amountOut ?? 0n
19178
+ };
19179
+ }
19180
+ function levelQuoteTotals(quote) {
19181
+ if (quote.fillable && quote.rate === null) {
19182
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19183
+ }
19184
+ return {
19185
+ side: quote.side,
19186
+ amountIn: quote.amountIn,
19187
+ amountOut: quote.amountOut,
19188
+ fillable: quote.fillable,
19189
+ maxFillableIn: quote.maxFillableIn,
19190
+ worstIn: quote.amountIn,
19191
+ worstOut: quote.amountOut
19099
19192
  };
19100
19193
  }
19101
19194
  function toOrderbookAmount(amount, decimals) {
@@ -19259,22 +19352,6 @@ var IntentGateway = class _IntentGateway {
19259
19352
  this.orderbook = typeof orderbook === "string" ? new HyperFxOrderbook(orderbook) : orderbook;
19260
19353
  return this;
19261
19354
  }
19262
- /**
19263
- * Quotes an intent between this gateway's source and destination chains from
19264
- * the HyperFX orderbook. Provide exactly one of `amountIn` or `amountOut`, in
19265
- * raw token units.
19266
- *
19267
- * An exact-input quote is the orderbook's clearing price for `amountIn`,
19268
- * which may combine several solvers' orders. An exact-output quote finds the
19269
- * smallest input whose clearing price delivers `amountOut`. A cross-chain
19270
- * route only counts orders whose solvers accept the source chain.
19271
- *
19272
- * The orderbook's rates already carry the gateway protocol fee, so the
19273
- * returned amounts can be placed as the order's inputs and outputs directly.
19274
- *
19275
- * @throws {InsufficientOrderbookLiquidityError} When the route cannot fill the amount.
19276
- * @throws {OrderbookRequestError} When the orderbook is unreachable or trades no book for the pair.
19277
- */
19278
19355
  async quoteIntent(params) {
19279
19356
  return this.market.quoteIntent(params, this.source.config.stateMachineId, this.dest.config.stateMachineId);
19280
19357
  }