@hyperbridge/sdk 2.8.16 → 2.8.18

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,
@@ -18923,18 +18961,6 @@ var OrderbookQuoteNotConvergedError = class extends Error {
18923
18961
  rounds;
18924
18962
  lastAmountIn;
18925
18963
  };
18926
- var InsufficientOrderbookLiquidityError = class extends Error {
18927
- constructor(route, maxFillableIn) {
18928
- super(
18929
- `The HyperFX orderbook cannot fill ${route.tokenIn} -> ${route.tokenOut} on ${route.sourceChain} -> ${route.destinationChain}; max fillable input is ${maxFillableIn} raw units`
18930
- );
18931
- this.route = route;
18932
- this.maxFillableIn = maxFillableIn;
18933
- this.name = "InsufficientOrderbookLiquidityError";
18934
- }
18935
- route;
18936
- maxFillableIn;
18937
- };
18938
18964
 
18939
18965
  // src/protocols/intents/orderbook/market.ts
18940
18966
  var MAX_EXACT_OUTPUT_ROUNDS = 16;
@@ -18946,26 +18972,67 @@ var OrderbookMarket = class {
18946
18972
  configService;
18947
18973
  orderbook;
18948
18974
  async quoteIntent(params, sourceChain, destinationChain) {
18975
+ const orderbook = this.orderbook();
18976
+ if (!params.optimistic) {
18977
+ const { quote: quote2, tokenIn: tokenIn2, tokenOut: tokenOut2 } = await this.priceIntent(
18978
+ params,
18979
+ sourceChain,
18980
+ destinationChain,
18981
+ (route, amountIn) => orderbook.quotePessimistic(route, amountIn),
18982
+ levelQuoteTotals
18983
+ );
18984
+ return {
18985
+ route: quote2.route,
18986
+ side: quote2.side,
18987
+ amountIn: fromOrderbookAmount(quote2.amountIn, tokenIn2.decimals),
18988
+ amountOut: fromOrderbookAmount(quote2.amountOut, tokenOut2.decimals),
18989
+ rate: quote2.rate,
18990
+ priceBucket: quote2.priceBucket,
18991
+ slippageBps: quote2.slippageBps,
18992
+ fillable: quote2.fillable,
18993
+ maxFillableIn: fromOrderbookAmount(quote2.maxFillableIn, tokenIn2.decimals)
18994
+ };
18995
+ }
18996
+ const { quote, tokenIn, tokenOut } = await this.priceIntent(
18997
+ params,
18998
+ sourceChain,
18999
+ destinationChain,
19000
+ (route, amountIn) => orderbook.quote(route, amountIn),
19001
+ swapQuoteTotals
19002
+ );
19003
+ return {
19004
+ route: quote.route,
19005
+ side: quote.side,
19006
+ amountIn: fromOrderbookAmount(quote.amountIn, tokenIn.decimals),
19007
+ slippageBps: quote.slippageBps,
19008
+ fillable: quote.fillable,
19009
+ maxFillableIn: fromOrderbookAmount(quote.maxFillableIn, tokenIn.decimals),
19010
+ legs: quote.fills.map((fill) => ({
19011
+ advertisedSize: fromOrderbookAmount(fill.advertisedSize, tokenOut.decimals),
19012
+ orderRate: fill.orderRate,
19013
+ amountIn: fromOrderbookAmount(fill.amountIn, tokenIn.decimals),
19014
+ amountOut: fromOrderbookAmount(fill.amountOut, tokenOut.decimals)
19015
+ }))
19016
+ };
19017
+ }
19018
+ /**
19019
+ * The orderbook's quote for an exact input, or for the input found to deliver
19020
+ * an exact output, with the assets it trades. A route that cannot fill the
19021
+ * trade answers with an unfillable quote rather than an error.
19022
+ */
19023
+ async priceIntent(params, sourceChain, destinationChain, price, totals) {
18949
19024
  validateQuoteParams(params);
18950
19025
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
18951
19026
  const tokenOut = this.assetByAddress(destinationChain, params.tokenOut);
18952
19027
  const route = toRoute(tokenIn, tokenOut);
18953
19028
  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);
19029
+ const quote2 = await price(route, toOrderbookAmount(params.amountIn, tokenIn.decimals));
19030
+ totals(quote2);
19031
+ return { quote: quote2, tokenIn, tokenOut };
18958
19032
  }
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
- );
19033
+ const targetOut = toOrderbookAmount(params.amountOut, tokenOut.decimals);
19034
+ const quote = await this.quoteExactOutput(route, targetOut, tokenIn, price, totals);
19035
+ return { quote, tokenIn, tokenOut };
18969
19036
  }
18970
19037
  async availableLiquidity(params, sourceChain, destinationChain) {
18971
19038
  const tokenIn = this.assetByAddress(sourceChain, params.tokenIn);
@@ -19018,22 +19085,29 @@ var OrderbookMarket = class {
19018
19085
  }
19019
19086
  /**
19020
19087
  * 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`.
19088
+ * guessing the input from the best rate, then keeping the part of each quote
19089
+ * filled better than its worst rate and re-pricing the rest of `targetOut` at
19090
+ * the input-to-output ratio that part filled at, until a quote delivers it.
19091
+ * That ratio is the orderbook's own, with the protocol fee already taken off.
19092
+ *
19093
+ * An output the route cannot deliver is answered by quoting an input past its
19094
+ * `maxFillableIn`, which the orderbook serves as unfillable: the smallest such
19095
+ * input guessed from the best rate, or one raw unit when no order serves it.
19023
19096
  */
19024
- async quoteExactOutput(route, targetOut, tokenIn) {
19025
- const orderbook = this.orderbook();
19026
- const liquidity = await orderbook.routeLiquidity(route);
19027
- if (liquidity.bestRate === null) throw this.insufficient(route, 0n, tokenIn);
19097
+ async quoteExactOutput(route, targetOut, tokenIn, price, totals) {
19098
+ const liquidity = await this.orderbook().routeLiquidity(route);
19028
19099
  const inputUnit = toOrderbookAmount(1n, tokenIn.decimals);
19029
- let amountIn = roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
19100
+ let amountIn = liquidity.bestRate === null ? inputUnit : roundUpTo(requiredInput(liquidity.side, targetOut, liquidity.bestRate), inputUnit);
19030
19101
  let lastAmountIn = amountIn;
19031
19102
  for (let round = 0; round < MAX_EXACT_OUTPUT_ROUNDS; round++) {
19032
- 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;
19103
+ const served = await price(route, amountIn);
19104
+ const quote = totals(served);
19105
+ if (quote.fillable && quote.amountOut >= targetOut) return served;
19106
+ if (!quote.fillable && amountIn > liquidity.maxFillableIn) return served;
19035
19107
  lastAmountIn = amountIn;
19036
- 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;
19037
19111
  amountIn = next > amountIn ? next : amountIn + inputUnit;
19038
19112
  }
19039
19113
  throw new OrderbookQuoteNotConvergedError(
@@ -19042,9 +19116,6 @@ var OrderbookMarket = class {
19042
19116
  fromOrderbookAmount(lastAmountIn, tokenIn.decimals)
19043
19117
  );
19044
19118
  }
19045
- insufficient(route, maxFillableIn, tokenIn) {
19046
- return new InsufficientOrderbookLiquidityError(route, fromOrderbookAmount(maxFillableIn, tokenIn.decimals));
19047
- }
19048
19119
  assetByAddress(chain, address) {
19049
19120
  const resolvedChain = resolveChain(chain);
19050
19121
  return withDecimals(
@@ -19089,24 +19160,32 @@ function toRoute(tokenIn, tokenOut) {
19089
19160
  function requiredInput(side, amountOut, rate) {
19090
19161
  return side === "BID" ? divCeil(amountOut * ORDERBOOK_SCALE, rate) : divCeil(amountOut * rate, ORDERBOOK_SCALE);
19091
19162
  }
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];
19163
+ function swapQuoteTotals(quote) {
19164
+ const worstFill = quote.fills.at(-1);
19165
+ if (quote.fillable && !worstFill)
19166
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no fills");
19095
19167
  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
- }
19168
+ side: quote.side,
19169
+ amountIn: quote.amountIn,
19170
+ amountOut: quote.fills.reduce((total, fill) => total + fill.amountOut, 0n),
19171
+ fillable: quote.fillable,
19172
+ maxFillableIn: quote.maxFillableIn,
19173
+ worstIn: worstFill?.amountIn ?? 0n,
19174
+ worstOut: worstFill?.amountOut ?? 0n
19175
+ };
19176
+ }
19177
+ function levelQuoteTotals(quote) {
19178
+ if (quote.fillable && quote.rate === null) {
19179
+ throw new OrderbookRequestError("the orderbook served a fillable quote with no rate");
19180
+ }
19181
+ return {
19182
+ side: quote.side,
19183
+ amountIn: quote.amountIn,
19184
+ amountOut: quote.amountOut,
19185
+ fillable: quote.fillable,
19186
+ maxFillableIn: quote.maxFillableIn,
19187
+ worstIn: quote.amountIn,
19188
+ worstOut: quote.amountOut
19110
19189
  };
19111
19190
  }
19112
19191
  function toOrderbookAmount(amount, decimals) {
@@ -19270,22 +19349,6 @@ var IntentGateway = class _IntentGateway {
19270
19349
  this.orderbook = typeof orderbook === "string" ? new HyperFxOrderbook(orderbook) : orderbook;
19271
19350
  return this;
19272
19351
  }
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
19352
  async quoteIntent(params) {
19290
19353
  return this.market.quoteIntent(params, this.source.config.stateMachineId, this.dest.config.stateMachineId);
19291
19354
  }
@@ -24603,7 +24666,6 @@ exports.HyperFungibleToken = HyperFungibleToken;
24603
24666
  exports.HyperFungibleTokenABI = HyperFungibleTokenABI;
24604
24667
  exports.HyperFxOrderbook = HyperFxOrderbook;
24605
24668
  exports.INCLUSION_TIMEOUT_MS = INCLUSION_TIMEOUT_MS;
24606
- exports.InsufficientOrderbookLiquidityError = InsufficientOrderbookLiquidityError;
24607
24669
  exports.IntentGateway = IntentGateway;
24608
24670
  exports.IntentGatewayABI = ABI3;
24609
24671
  exports.IntentOrderStatus = IntentOrderStatus;