@hyperbridge/simplex 0.16.5 → 0.16.6

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.
package/dist/index.cjs CHANGED
@@ -2654,10 +2654,16 @@ var CANCEL_ORDER_MUTATION = `
2654
2654
  `;
2655
2655
  var OrderbookRequestError = class extends Error {
2656
2656
  };
2657
+ function graphqlEndpoint(url) {
2658
+ const trimmed = url.trim();
2659
+ if (trimmed.includes("?")) return trimmed;
2660
+ const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
2661
+ return withoutTrailingSlash.endsWith("/graphql") ? withoutTrailingSlash : `${withoutTrailingSlash}/graphql`;
2662
+ }
2657
2663
  var OrderbookClient = class {
2658
2664
  constructor(url, requestTimeoutMs, loggers = defaultLoggerContext()) {
2659
- this.url = url;
2660
2665
  this.requestTimeoutMs = requestTimeoutMs;
2666
+ this.url = graphqlEndpoint(url);
2661
2667
  this.logger = loggers.get("orderbook");
2662
2668
  }
2663
2669
  /** The limits to validate against before posting, plus the books on offer. */
@@ -2755,7 +2761,9 @@ var OrderbookClient = class {
2755
2761
  clearTimeout(timer);
2756
2762
  }
2757
2763
  if (!response.ok) {
2758
- throw new OrderbookRequestError(`Orderbook returned HTTP ${response.status} ${response.statusText}`);
2764
+ throw new OrderbookRequestError(
2765
+ `Orderbook at ${this.url} returned HTTP ${response.status} ${response.statusText}`
2766
+ );
2759
2767
  }
2760
2768
  const body = await response.json().catch((err) => {
2761
2769
  throw new OrderbookRequestError(`Orderbook returned a body that is not JSON: ${describe(err)}`);
@@ -2870,7 +2878,7 @@ var HEARTBEAT_TYPES = {
2870
2878
  var LimitOrderValidationError = class extends Error {
2871
2879
  };
2872
2880
  var LimitOrderService = class {
2873
- constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce) {
2881
+ constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce, vaultBalances) {
2874
2882
  this.store = store;
2875
2883
  this.client = client;
2876
2884
  this.contractService = contractService;
@@ -2880,6 +2888,7 @@ var LimitOrderService = class {
2880
2888
  this.defaultTtlSecs = defaultTtlSecs;
2881
2889
  this.delegationService = delegationService;
2882
2890
  this.startingNonce = startingNonce;
2891
+ this.vaultBalances = vaultBalances;
2883
2892
  this.logger = loggers.get("limit-orders");
2884
2893
  }
2885
2894
  /**
@@ -3015,6 +3024,16 @@ var LimitOrderService = class {
3015
3024
  throw err;
3016
3025
  }
3017
3026
  }
3027
+ /**
3028
+ * The pairs an order can be written against, and the smallest payout each token may carry.
3029
+ *
3030
+ * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
3031
+ * operator is offered exactly these and no combination of symbols they could not post.
3032
+ */
3033
+ async books() {
3034
+ const limits = await this.limits();
3035
+ return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes };
3036
+ }
3018
3037
  /**
3019
3038
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
3020
3039
  *
@@ -3122,28 +3141,61 @@ var LimitOrderService = class {
3122
3141
  * balance on the fill chain, so the operator hears it while they are creating
3123
3142
  * the order.
3124
3143
  *
3144
+ * A fill pays out of the wallet and then withdraws any shortfall from the
3145
+ * configured ERC-4626 vaults in the same batch, so what those vaults hold of
3146
+ * the payout token backs the order too and is counted here. Without it an
3147
+ * operator who sweeps inventory into a vault is refused orders their filler
3148
+ * would have filled.
3149
+ *
3125
3150
  * Each order is checked against the whole balance, not against what is left of
3126
3151
  * it after the others. One balance backs every order resting on it — that is
3127
3152
  * what quoting both sides of a book is — and the orderbook says as much: it
3128
3153
  * advertises each entry at `min(quoted, balance)` rather than dividing the
3129
3154
  * balance between them. Whichever order fills first draws the inventory down,
3130
3155
  * and the rest are cut to what is left. Netting them here would refuse the
3131
- * second side of every book.
3156
+ * second side of every book. Vault positions are counted the same way: the
3157
+ * whole position, not what is left after reservations for fills in flight.
3132
3158
  */
3133
3159
  async assertWalletCanPay(symbol, chain, payout) {
3134
3160
  const token = this.assetRegistry.getAddress(symbol, chain);
3135
3161
  if (!token) return;
3136
3162
  const decimals = await this.decimalsFor(symbol, token, chain);
3137
- const balance = toScaled(
3163
+ const wallet = toScaled(
3138
3164
  await this.contractService.getTokenBalance(chain, token, this.signer.address),
3139
3165
  decimals
3140
3166
  );
3141
- if (balance < payout) {
3167
+ const vaults = await this.vaultHoldings(token, chain);
3168
+ if (wallet + vaults < payout) {
3169
+ const held = vaults > 0n ? `${toHuman(wallet)} in the wallet and ${toHuman(vaults)} in vaults` : toHuman(wallet);
3142
3170
  throw new LimitOrderValidationError(
3143
- `The wallet holds ${toHuman(balance)} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
3171
+ `The wallet holds ${held} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
3144
3172
  );
3145
3173
  }
3146
3174
  }
3175
+ /**
3176
+ * What the configured vaults hold of `token` on `chain`, in scaled units.
3177
+ *
3178
+ * Zero when no vault is configured. A snapshot that cannot be read is also
3179
+ * zero: the wallet alone then has to cover the order, which refuses one the
3180
+ * filler might have managed rather than accepting one it could not.
3181
+ */
3182
+ async vaultHoldings(token, chain) {
3183
+ if (!this.vaultBalances) return 0n;
3184
+ let positions;
3185
+ try {
3186
+ positions = await this.vaultBalances.getBalanceSnapshot(chain);
3187
+ } catch (err) {
3188
+ this.logger.warn({ chain, token, err }, "Could not read vault balances; backing the order on the wallet alone");
3189
+ return 0n;
3190
+ }
3191
+ const wanted = token.toLowerCase();
3192
+ let total = 0n;
3193
+ for (const position of positions) {
3194
+ if (position.chain !== chain || position.asset.toLowerCase() !== wanted) continue;
3195
+ total += toScaled(position.positionAssets, position.decimals);
3196
+ }
3197
+ return total;
3198
+ }
3147
3199
  /**
3148
3200
  * Works a limit order down by what a fill delivered, and puts the rest back on
3149
3201
  * the orderbook.
@@ -23559,7 +23611,10 @@ async function bootFiller(config, options) {
23559
23611
  runtimeSigner,
23560
23612
  config.orderbook.defaultTtlSecs ?? DEFAULT_LIMIT_ORDER_TTL_SECONDS,
23561
23613
  new DelegationService(chainClientManager, configService, runtimeSigner),
23562
- options.loggers
23614
+ options.loggers,
23615
+ initialOrderNonce,
23616
+ // A fill withdraws a payout shortfall from the vaults, so what they hold backs an order too.
23617
+ vaultVenue
23563
23618
  );
23564
23619
  intentFiller.setLimitOrderService(limitOrderService);
23565
23620
  const lifecycle = new LimitOrderLifecycle(
@@ -24630,6 +24685,10 @@ var LimitOrderController = class {
24630
24685
  ]);
24631
24686
  return { order, fills, bids };
24632
24687
  }
24688
+ /** The pairs the orderbook lists, and the smallest payout each token may carry. */
24689
+ books() {
24690
+ return this.service.books();
24691
+ }
24633
24692
  /** Creates the order, posts it, and reports what the orderbook made of it. */
24634
24693
  async create(request) {
24635
24694
  const posted = await this.service.create(request);