@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.
@@ -18788,20 +18788,27 @@ var ROUTE_LIQUIDITY_QUERY = `
18788
18788
  query RouteLiquidity($route: RouteInput!) {
18789
18789
  books { id base quote }
18790
18790
  routeLiquidity(route: $route) {
18791
- route bestRate depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
18791
+ route bestRate slippageBps depthIn depthOut availableLiquidity maxFillableIn orderCount solverCount
18792
18792
  }
18793
18793
  }`;
18794
18794
  var QUOTE_QUERY = `
18795
18795
  query Quote($route: RouteInput!, $amountIn: BigInt!) {
18796
18796
  quote(route: $route, amountIn: $amountIn) {
18797
- route side amountIn amountOut rate fillable depth maxFillableIn
18798
- fills { orderRate amountOut advertisedSize }
18797
+ route side amountIn slippageBps fillable maxFillableIn
18798
+ fills { advertisedSize orderRate amountIn amountOut }
18799
+ }
18800
+ }`;
18801
+ var QUOTE_PESSIMISTIC_QUERY = `
18802
+ query QuotePessimistic($route: RouteInput!, $amountIn: BigInt!) {
18803
+ quotePessimistic(route: $route, amountIn: $amountIn) {
18804
+ route side amountIn amountOut rate priceBucket slippageBps fillable maxFillableIn
18799
18805
  }
18800
18806
  }`;
18801
18807
  var ORDERBOOK_QUERIES = {
18802
18808
  topOfBook: TOP_OF_BOOK_QUERY,
18803
18809
  routeLiquidity: ROUTE_LIQUIDITY_QUERY,
18804
- quote: QUOTE_QUERY
18810
+ quote: QUOTE_QUERY,
18811
+ quotePessimistic: QUOTE_PESSIMISTIC_QUERY
18805
18812
  };
18806
18813
  var HyperFxOrderbook = class {
18807
18814
  client;
@@ -18830,8 +18837,9 @@ var HyperFxOrderbook = class {
18830
18837
  return {
18831
18838
  route: raw.route,
18832
18839
  book,
18833
- side: book.base === route.tokenIn ? "BID" : "ASK",
18834
- bestRate: raw.bestRate === null ? null : BigInt(raw.bestRate),
18840
+ side: sameSymbol(book.base, route.tokenIn) ? "BID" : "ASK",
18841
+ bestRate: optionalBigInt(raw.bestRate),
18842
+ slippageBps: raw.slippageBps,
18835
18843
  depthIn: BigInt(raw.depthIn),
18836
18844
  depthOut: BigInt(raw.depthOut),
18837
18845
  availableLiquidity: BigInt(raw.availableLiquidity),
@@ -18840,7 +18848,10 @@ var HyperFxOrderbook = class {
18840
18848
  solverCount: raw.solverCount
18841
18849
  };
18842
18850
  }
18843
- /** The clearing price for `amountIn` (1e18 tokenIn) on a route, which may combine several solvers' orders. */
18851
+ /**
18852
+ * The optimistic quote for `amountIn` (1e18 tokenIn) on a route: the route's orders, best price
18853
+ * first, each taking what it can at its own price. The fills' `amountOut`s sum to the output.
18854
+ */
18844
18855
  async quote(route, amountIn) {
18845
18856
  const { quote: raw } = await this.request(QUOTE_QUERY, {
18846
18857
  route,
@@ -18850,18 +18861,39 @@ var HyperFxOrderbook = class {
18850
18861
  route: raw.route,
18851
18862
  side: raw.side,
18852
18863
  amountIn: BigInt(raw.amountIn),
18853
- amountOut: BigInt(raw.amountOut),
18854
- rate: raw.rate === null ? null : BigInt(raw.rate),
18864
+ slippageBps: raw.slippageBps,
18855
18865
  fillable: raw.fillable,
18856
- depth: BigInt(raw.depth),
18857
18866
  maxFillableIn: BigInt(raw.maxFillableIn),
18858
18867
  fills: raw.fills.map((fill) => ({
18868
+ advertisedSize: BigInt(fill.advertisedSize),
18859
18869
  orderRate: BigInt(fill.orderRate),
18860
- amountOut: BigInt(fill.amountOut),
18861
- advertisedSize: BigInt(fill.advertisedSize)
18870
+ amountIn: BigInt(fill.amountIn),
18871
+ amountOut: BigInt(fill.amountOut)
18862
18872
  }))
18863
18873
  };
18864
18874
  }
18875
+ /**
18876
+ * The pessimistic quote for `amountIn` (1e18 tokenIn) on a route: one price, from the first level
18877
+ * deep enough to fill the whole trade by itself, at which every order in it fills; the route's
18878
+ * worst price when no one level can but its levels together can.
18879
+ */
18880
+ async quotePessimistic(route, amountIn) {
18881
+ const { quotePessimistic: raw } = await this.request(
18882
+ QUOTE_PESSIMISTIC_QUERY,
18883
+ { route, amountIn: amountIn.toString() }
18884
+ );
18885
+ return {
18886
+ route: raw.route,
18887
+ side: raw.side,
18888
+ amountIn: BigInt(raw.amountIn),
18889
+ amountOut: BigInt(raw.amountOut),
18890
+ rate: optionalBigInt(raw.rate),
18891
+ priceBucket: optionalBigInt(raw.priceBucket),
18892
+ slippageBps: raw.slippageBps,
18893
+ fillable: raw.fillable,
18894
+ maxFillableIn: BigInt(raw.maxFillableIn)
18895
+ };
18896
+ }
18865
18897
  async request(query, variables) {
18866
18898
  try {
18867
18899
  return await this.client.request(query, variables);
@@ -18875,13 +18907,19 @@ function describeError(error) {
18875
18907
  if (graphqlErrors?.length) return graphqlErrors.map((e) => e.message).join("; ");
18876
18908
  return error instanceof Error ? error.message : String(error);
18877
18909
  }
18910
+ function sameSymbol(a, b) {
18911
+ return a.toLowerCase() === b.toLowerCase();
18912
+ }
18878
18913
  function findBook(books, tokenA, tokenB) {
18879
18914
  const book = books.find(
18880
- (b) => b.base === tokenA && b.quote === tokenB || b.base === tokenB && b.quote === tokenA
18915
+ (b) => sameSymbol(b.base, tokenA) && sameSymbol(b.quote, tokenB) || sameSymbol(b.base, tokenB) && sameSymbol(b.quote, tokenA)
18881
18916
  );
18882
18917
  if (!book) throw new OrderbookRequestError(`no book trades ${tokenA} for ${tokenB}`);
18883
18918
  return book;
18884
18919
  }
18920
+ function optionalBigInt(value) {
18921
+ return value === null ? null : BigInt(value);
18922
+ }
18885
18923
  function parseRate(raw) {
18886
18924
  return {
18887
18925
  side: raw.side,
@@ -18946,26 +18984,69 @@ var OrderbookMarket = class {
18946
18984
  configService;
18947
18985
  orderbook;
18948
18986
  async quoteIntent(params, sourceChain, destinationChain) {
18987
+ const orderbook = this.orderbook();
18988
+ if (!params.optimistic) {
18989
+ const { quote: quote2, tokenIn: tokenIn2, tokenOut: tokenOut2 } = await this.priceIntent(
18990
+ params,
18991
+ sourceChain,
18992
+ destinationChain,
18993
+ (route, amountIn) => orderbook.quotePessimistic(route, amountIn),
18994
+ levelQuoteTotals
18995
+ );
18996
+ return {
18997
+ route: quote2.route,
18998
+ side: quote2.side,
18999
+ amountIn: fromOrderbookAmount(quote2.amountIn, tokenIn2.decimals),
19000
+ amountOut: fromOrderbookAmount(quote2.amountOut, tokenOut2.decimals),
19001
+ rate: quote2.rate,
19002
+ priceBucket: quote2.priceBucket,
19003
+ slippageBps: quote2.slippageBps,
19004
+ fillable: quote2.fillable,
19005
+ maxFillableIn: fromOrderbookAmount(quote2.maxFillableIn, tokenIn2.decimals)
19006
+ };
19007
+ }
19008
+ const { quote, tokenIn, tokenOut } = await this.priceIntent(
19009
+ params,
19010
+ sourceChain,
19011
+ destinationChain,
19012
+ (route, amountIn) => orderbook.quote(route, amountIn),
19013
+ swapQuoteTotals
19014
+ );
19015
+ return {
19016
+ route: quote.route,
19017
+ side: quote.side,
19018
+ amountIn: fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
19019
+ slippageBps: quote.slippageBps,
19020
+ fillable: quote.fillable,
19021
+ maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19022
+ legs: quote.fills.map((fill) => ({
19023
+ advertisedSize: fromOrderbookAmount(fill.advertisedSize, tokenOut.decimals),
19024
+ orderRate: fill.orderRate,
19025
+ amountIn: fromOrderbookAmount(fill.amountIn, tokenIn.decimals),
19026
+ amountOut: fromOrderbookAmount(fill.amountOut, tokenOut.decimals)
19027
+ }))
19028
+ };
19029
+ }
19030
+ /**
19031
+ * The fillable quote for an exact input, or for the input found to deliver an
19032
+ * exact output, with the assets it trades.
19033
+ */
19034
+ async priceIntent(params, sourceChain, destinationChain, price, totals) {
18949
19035
  validateQuoteParams(params);
18950
19036
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
18951
19037
  const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
18952
19038
  const route = toRoute(tokenIn, tokenOut);
18953
19039
  if (params.amountIn !== void 0) {
18954
- const quote2 = await this.orderbook().quote(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
18955
- const amountOut2 = quote2.fillable ? fromOrderbookAmount(quote2.amountOut, tokenOut.decimals) : 0n;
18956
- if (amountOut2 === 0n) throw this.insufficient(route, quote2.maxFillableIn, tokenIn);
18957
- return buildQuote("EXACT_INPUT", params.amountIn, amountOut2, quote2, tokenIn, tokenOut);
19040
+ const quote2 = await price(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
19041
+ const { fillable, amountOut, maxFillableIn } = totals(quote2);
19042
+ if (!fillable || fromOrderbookAmount(amountOut, tokenOut.decimals) === 0n) {
19043
+ throw this.insufficient(route, maxFillableIn, tokenIn);
19044
+ }
19045
+ return { quote: quote2, tokenIn, tokenOut };
18958
19046
  }
18959
- const amountOut = params.amountOut;
18960
- const quote = await this.quoteExactOutput(route, toOrderbookAmount(amountOut, tokenOut.decimals), tokenIn);
18961
- return buildQuote(
18962
- "EXACT_OUTPUT",
18963
- fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
18964
- amountOut,
18965
- quote,
18966
- tokenIn,
18967
- tokenOut
18968
- );
19047
+ const targetOut = toOrderbookAmount(params.amountOut, tokenOut.decimals);
19048
+ const quote = await this.quoteExactOutput(route, targetOut, tokenIn, price, totals);
19049
+ return { quote, tokenIn, tokenOut };
18969
19050
  }
18970
19051
  async availableLiquidity(params, sourceChain, destinationChain) {
18971
19052
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
@@ -19018,22 +19099,26 @@ var OrderbookMarket = class {
19018
19099
  }
19019
19100
  /**
19020
19101
  * The orderbook only quotes an input amount, so an output is priced by
19021
- * guessing the input from the best rate and raising it at each clearing
19022
- * price until the quote delivers `targetOut`.
19102
+ * guessing the input from the best rate, then keeping the part of each quote
19103
+ * filled better than its worst rate and re-pricing the rest of `targetOut` at
19104
+ * the input-to-output ratio that part filled at, until a quote delivers it.
19105
+ * That ratio is the orderbook's own, with the protocol fee already taken off.
19023
19106
  */
19024
- async quoteExactOutput(route, targetOut, tokenIn) {
19025
- const orderbook = this.orderbook();
19026
- const liquidity = await orderbook.routeLiquidity(route);
19107
+ async quoteExactOutput(route, targetOut, tokenIn, price, totals) {
19108
+ const liquidity = await this.orderbook().routeLiquidity(route);
19027
19109
  if (liquidity.bestRate === null) throw this.insufficient(route, 0n, tokenIn);
19028
19110
  const inputUnit = toOrderbookAmount(1n, tokenIn.decimals);
19029
19111
  let amountIn = roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
19030
19112
  let lastAmountIn = amountIn;
19031
19113
  for (let round = 0; round < MAX_EXACT_OUTPUT_ROUNDS; round++) {
19032
19114
  if (amountIn > liquidity.maxFillableIn) throw this.insufficient(route, liquidity.maxFillableIn, tokenIn);
19033
- const quote = await orderbook.quote(route, amountIn);
19034
- if (quote.fillable && quote.amountOut >= targetOut) return quote;
19115
+ const served = await price(route, amountIn);
19116
+ const quote = totals(served);
19117
+ if (quote.fillable && quote.amountOut >= targetOut) return served;
19035
19118
  lastAmountIn = amountIn;
19036
- const next = quote.rate === null ? amountIn : roundUpTo(requiredInput(quote.side, targetOut, quote.rate), inputUnit);
19119
+ const betterIn = quote.amountIn - quote.worstIn;
19120
+ const betterOut = quote.amountOut - quote.worstOut;
19121
+ const next = quote.fillable && quote.worstOut > 0n ? betterIn + roundUpTo(divCeil((targetOut - betterOut) * quote.worstIn, quote.worstOut), inputUnit) : amountIn;
19037
19122
  amountIn = next > amountIn ? next : amountIn + inputUnit;
19038
19123
  }
19039
19124
  throw new OrderbookQuoteNotConvergedError(
@@ -19089,24 +19174,32 @@ function toRoute(tokenIn, tokenOut) {
19089
19174
  function requiredInput(side, amountOut, rate) {
19090
19175
  return side === "BID" ? divCeil(amountOut * ORDERBOOK_SCALE, rate) : divCeil(amountOut * rate, ORDERBOOK_SCALE);
19091
19176
  }
19092
- function buildQuote(tradeType, amountIn, amountOut, quote, tokenIn, tokenOut) {
19093
- if (quote.rate === null) throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19094
- const [base3, quoteToken] = quote.side === "BID" ? [tokenIn, tokenOut] : [tokenOut, tokenIn];
19177
+ function swapQuoteTotals(quote) {
19178
+ const worstFill = quote.fills.at(-1);
19179
+ if (quote.fillable && !worstFill)
19180
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no fills");
19095
19181
  return {
19096
- tradeType,
19097
- amountIn,
19098
- amountOut,
19099
- quoteMetadata: {
19100
- sourceChain: tokenIn.chain,
19101
- destinationChain: tokenOut.chain,
19102
- route: quote.route,
19103
- side: quote.side,
19104
- baseTokenSymbol: base3.symbol,
19105
- quoteTokenSymbol: quoteToken.symbol,
19106
- rate: format(quote.rate),
19107
- maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19108
- orderCount: quote.fills.length
19109
- }
19182
+ side: quote.side,
19183
+ amountIn: quote.amountIn,
19184
+ amountOut: quote.fills.reduce((total, fill) => total + fill.amountOut, 0n),
19185
+ fillable: quote.fillable,
19186
+ maxFillableIn: quote.maxFillableIn,
19187
+ worstIn: worstFill?.amountIn ?? 0n,
19188
+ worstOut: worstFill?.amountOut ?? 0n
19189
+ };
19190
+ }
19191
+ function levelQuoteTotals(quote) {
19192
+ if (quote.fillable && quote.rate === null) {
19193
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19194
+ }
19195
+ return {
19196
+ side: quote.side,
19197
+ amountIn: quote.amountIn,
19198
+ amountOut: quote.amountOut,
19199
+ fillable: quote.fillable,
19200
+ maxFillableIn: quote.maxFillableIn,
19201
+ worstIn: quote.amountIn,
19202
+ worstOut: quote.amountOut
19110
19203
  };
19111
19204
  }
19112
19205
  function toOrderbookAmount(amount, decimals) {
@@ -19270,22 +19363,6 @@ var IntentGateway = class _IntentGateway {
19270
19363
  this.orderbook = typeof orderbook === "string" ? new HyperFxOrderbook(orderbook) : orderbook;
19271
19364
  return this;
19272
19365
  }
19273
- /**
19274
- * Quotes an intent between this gateway's source and destination chains from
19275
- * the HyperFX orderbook. Provide exactly one of `amountIn` or `amountOut`, in
19276
- * raw token units.
19277
- *
19278
- * An exact-input quote is the orderbook's clearing price for `amountIn`,
19279
- * which may combine several solvers' orders. An exact-output quote finds the
19280
- * smallest input whose clearing price delivers `amountOut`. A cross-chain
19281
- * route only counts orders whose solvers accept the source chain.
19282
- *
19283
- * The orderbook's rates already carry the gateway protocol fee, so the
19284
- * returned amounts can be placed as the order's inputs and outputs directly.
19285
- *
19286
- * @throws {InsufficientOrderbookLiquidityError} When the route cannot fill the amount.
19287
- * @throws {OrderbookRequestError} When the orderbook is unreachable or trades no book for the pair.
19288
- */
19289
19366
  async quoteIntent(params) {
19290
19367
  return this.market.quoteIntent(params, this.source.config.stateMachineId, this.dest.config.stateMachineId);
19291
19368
  }