@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.d.cts CHANGED
@@ -2733,9 +2733,10 @@ type CancelOrderResult = {
2733
2733
  * {@link OrderbookRequestError} because it could not get one.
2734
2734
  */
2735
2735
  declare class OrderbookClient {
2736
- private readonly url;
2737
2736
  private readonly requestTimeoutMs;
2738
2737
  private logger;
2738
+ /** The configured endpoint, with `/graphql` if it was left off. */
2739
+ private readonly url;
2739
2740
  constructor(url: string, requestTimeoutMs: number, loggers?: LoggerContext);
2740
2741
  /** The limits to validate against before posting, plus the books on offer. */
2741
2742
  limits(): Promise<OrderbookLimits>;
@@ -2844,6 +2845,13 @@ interface ReconcileReport {
2844
2845
  * orderbook's answer is then written back onto that row: an accepted order
2845
2846
  * carries its commitment, a rejected one carries the reason.
2846
2847
  */
2848
+ /**
2849
+ * The configured ERC-4626 vaults' holdings, as `VaultFundingPlanner` reports them. A fill sources
2850
+ * a payout shortfall from these, so they back a limit order as much as the wallet does.
2851
+ */
2852
+ interface VaultHoldings {
2853
+ getBalanceSnapshot(chain?: string): Promise<VaultBalancePosition[]>;
2854
+ }
2847
2855
  declare class LimitOrderService {
2848
2856
  private readonly store;
2849
2857
  private readonly client;
@@ -2855,12 +2863,16 @@ declare class LimitOrderService {
2855
2863
  private readonly delegationService?;
2856
2864
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
2857
2865
  private readonly startingNonce;
2866
+ /** The configured ERC-4626 vaults, when the operator runs any. */
2867
+ private readonly vaultBalances?;
2858
2868
  private logger;
2859
2869
  private cachedLimits?;
2860
2870
  private onEvent?;
2861
2871
  constructor(store: LimitOrderStore, client: OrderbookClient, contractService: ContractInteractionService, configService: FillerConfigService, assetRegistry: AssetRegistry, signer: Signer, defaultTtlSecs: number, delegationService?: DelegationService | undefined, loggers?: LoggerContext,
2862
2872
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
2863
- startingNonce?: () => bigint);
2873
+ startingNonce?: () => bigint,
2874
+ /** The configured ERC-4626 vaults, when the operator runs any. */
2875
+ vaultBalances?: VaultHoldings | undefined);
2864
2876
  /**
2865
2877
  * Attaches the listener fill-driven changes are reported to. `Simplex` does
2866
2878
  * this after boot, the same way the filler is handed the service itself.
@@ -2896,6 +2908,16 @@ declare class LimitOrderService {
2896
2908
  */
2897
2909
  private withdraw;
2898
2910
  private signAndCancel;
2911
+ /**
2912
+ * The pairs an order can be written against, and the smallest payout each token may carry.
2913
+ *
2914
+ * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
2915
+ * operator is offered exactly these and no combination of symbols they could not post.
2916
+ */
2917
+ books(): Promise<{
2918
+ books: Book[];
2919
+ minOrderSizes: TokenMinSize[];
2920
+ }>;
2899
2921
  /**
2900
2922
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
2901
2923
  *
@@ -2915,15 +2937,30 @@ declare class LimitOrderService {
2915
2937
  * balance on the fill chain, so the operator hears it while they are creating
2916
2938
  * the order.
2917
2939
  *
2940
+ * A fill pays out of the wallet and then withdraws any shortfall from the
2941
+ * configured ERC-4626 vaults in the same batch, so what those vaults hold of
2942
+ * the payout token backs the order too and is counted here. Without it an
2943
+ * operator who sweeps inventory into a vault is refused orders their filler
2944
+ * would have filled.
2945
+ *
2918
2946
  * Each order is checked against the whole balance, not against what is left of
2919
2947
  * it after the others. One balance backs every order resting on it — that is
2920
2948
  * what quoting both sides of a book is — and the orderbook says as much: it
2921
2949
  * advertises each entry at `min(quoted, balance)` rather than dividing the
2922
2950
  * balance between them. Whichever order fills first draws the inventory down,
2923
2951
  * and the rest are cut to what is left. Netting them here would refuse the
2924
- * second side of every book.
2952
+ * second side of every book. Vault positions are counted the same way: the
2953
+ * whole position, not what is left after reservations for fills in flight.
2925
2954
  */
2926
2955
  private assertWalletCanPay;
2956
+ /**
2957
+ * What the configured vaults hold of `token` on `chain`, in scaled units.
2958
+ *
2959
+ * Zero when no vault is configured. A snapshot that cannot be read is also
2960
+ * zero: the wallet alone then has to cover the order, which refuses one the
2961
+ * filler might have managed rather than accepting one it could not.
2962
+ */
2963
+ private vaultHoldings;
2927
2964
  /**
2928
2965
  * Works a limit order down by what a fill delivered, and puts the rest back on
2929
2966
  * the orderbook.
@@ -3794,6 +3831,11 @@ declare class LimitOrderController {
3794
3831
  fills: LimitOrderFill[];
3795
3832
  bids: StoredBid[];
3796
3833
  } | null>;
3834
+ /** The pairs the orderbook lists, and the smallest payout each token may carry. */
3835
+ books(): Promise<{
3836
+ books: Book[];
3837
+ minOrderSizes: TokenMinSize[];
3838
+ }>;
3797
3839
  /** Creates the order, posts it, and reports what the orderbook made of it. */
3798
3840
  create(request: CreateLimitOrderRequest): Promise<PostedLimitOrder>;
3799
3841
  cancel(id: string): Promise<CancelledLimitOrder>;
package/dist/index.d.ts CHANGED
@@ -2733,9 +2733,10 @@ type CancelOrderResult = {
2733
2733
  * {@link OrderbookRequestError} because it could not get one.
2734
2734
  */
2735
2735
  declare class OrderbookClient {
2736
- private readonly url;
2737
2736
  private readonly requestTimeoutMs;
2738
2737
  private logger;
2738
+ /** The configured endpoint, with `/graphql` if it was left off. */
2739
+ private readonly url;
2739
2740
  constructor(url: string, requestTimeoutMs: number, loggers?: LoggerContext);
2740
2741
  /** The limits to validate against before posting, plus the books on offer. */
2741
2742
  limits(): Promise<OrderbookLimits>;
@@ -2844,6 +2845,13 @@ interface ReconcileReport {
2844
2845
  * orderbook's answer is then written back onto that row: an accepted order
2845
2846
  * carries its commitment, a rejected one carries the reason.
2846
2847
  */
2848
+ /**
2849
+ * The configured ERC-4626 vaults' holdings, as `VaultFundingPlanner` reports them. A fill sources
2850
+ * a payout shortfall from these, so they back a limit order as much as the wallet does.
2851
+ */
2852
+ interface VaultHoldings {
2853
+ getBalanceSnapshot(chain?: string): Promise<VaultBalancePosition[]>;
2854
+ }
2847
2855
  declare class LimitOrderService {
2848
2856
  private readonly store;
2849
2857
  private readonly client;
@@ -2855,12 +2863,16 @@ declare class LimitOrderService {
2855
2863
  private readonly delegationService?;
2856
2864
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
2857
2865
  private readonly startingNonce;
2866
+ /** The configured ERC-4626 vaults, when the operator runs any. */
2867
+ private readonly vaultBalances?;
2858
2868
  private logger;
2859
2869
  private cachedLimits?;
2860
2870
  private onEvent?;
2861
2871
  constructor(store: LimitOrderStore, client: OrderbookClient, contractService: ContractInteractionService, configService: FillerConfigService, assetRegistry: AssetRegistry, signer: Signer, defaultTtlSecs: number, delegationService?: DelegationService | undefined, loggers?: LoggerContext,
2862
2872
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
2863
- startingNonce?: () => bigint);
2873
+ startingNonce?: () => bigint,
2874
+ /** The configured ERC-4626 vaults, when the operator runs any. */
2875
+ vaultBalances?: VaultHoldings | undefined);
2864
2876
  /**
2865
2877
  * Attaches the listener fill-driven changes are reported to. `Simplex` does
2866
2878
  * this after boot, the same way the filler is handed the service itself.
@@ -2896,6 +2908,16 @@ declare class LimitOrderService {
2896
2908
  */
2897
2909
  private withdraw;
2898
2910
  private signAndCancel;
2911
+ /**
2912
+ * The pairs an order can be written against, and the smallest payout each token may carry.
2913
+ *
2914
+ * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
2915
+ * operator is offered exactly these and no combination of symbols they could not post.
2916
+ */
2917
+ books(): Promise<{
2918
+ books: Book[];
2919
+ minOrderSizes: TokenMinSize[];
2920
+ }>;
2899
2921
  /**
2900
2922
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
2901
2923
  *
@@ -2915,15 +2937,30 @@ declare class LimitOrderService {
2915
2937
  * balance on the fill chain, so the operator hears it while they are creating
2916
2938
  * the order.
2917
2939
  *
2940
+ * A fill pays out of the wallet and then withdraws any shortfall from the
2941
+ * configured ERC-4626 vaults in the same batch, so what those vaults hold of
2942
+ * the payout token backs the order too and is counted here. Without it an
2943
+ * operator who sweeps inventory into a vault is refused orders their filler
2944
+ * would have filled.
2945
+ *
2918
2946
  * Each order is checked against the whole balance, not against what is left of
2919
2947
  * it after the others. One balance backs every order resting on it — that is
2920
2948
  * what quoting both sides of a book is — and the orderbook says as much: it
2921
2949
  * advertises each entry at `min(quoted, balance)` rather than dividing the
2922
2950
  * balance between them. Whichever order fills first draws the inventory down,
2923
2951
  * and the rest are cut to what is left. Netting them here would refuse the
2924
- * second side of every book.
2952
+ * second side of every book. Vault positions are counted the same way: the
2953
+ * whole position, not what is left after reservations for fills in flight.
2925
2954
  */
2926
2955
  private assertWalletCanPay;
2956
+ /**
2957
+ * What the configured vaults hold of `token` on `chain`, in scaled units.
2958
+ *
2959
+ * Zero when no vault is configured. A snapshot that cannot be read is also
2960
+ * zero: the wallet alone then has to cover the order, which refuses one the
2961
+ * filler might have managed rather than accepting one it could not.
2962
+ */
2963
+ private vaultHoldings;
2927
2964
  /**
2928
2965
  * Works a limit order down by what a fill delivered, and puts the rest back on
2929
2966
  * the orderbook.
@@ -3794,6 +3831,11 @@ declare class LimitOrderController {
3794
3831
  fills: LimitOrderFill[];
3795
3832
  bids: StoredBid[];
3796
3833
  } | null>;
3834
+ /** The pairs the orderbook lists, and the smallest payout each token may carry. */
3835
+ books(): Promise<{
3836
+ books: Book[];
3837
+ minOrderSizes: TokenMinSize[];
3838
+ }>;
3797
3839
  /** Creates the order, posts it, and reports what the orderbook made of it. */
3798
3840
  create(request: CreateLimitOrderRequest): Promise<PostedLimitOrder>;
3799
3841
  cancel(id: string): Promise<CancelledLimitOrder>;
package/dist/index.js CHANGED
@@ -2597,10 +2597,16 @@ var CANCEL_ORDER_MUTATION = `
2597
2597
  `;
2598
2598
  var OrderbookRequestError = class extends Error {
2599
2599
  };
2600
+ function graphqlEndpoint(url) {
2601
+ const trimmed = url.trim();
2602
+ if (trimmed.includes("?")) return trimmed;
2603
+ const withoutTrailingSlash = trimmed.replace(/\/+$/, "");
2604
+ return withoutTrailingSlash.endsWith("/graphql") ? withoutTrailingSlash : `${withoutTrailingSlash}/graphql`;
2605
+ }
2600
2606
  var OrderbookClient = class {
2601
2607
  constructor(url, requestTimeoutMs, loggers = defaultLoggerContext()) {
2602
- this.url = url;
2603
2608
  this.requestTimeoutMs = requestTimeoutMs;
2609
+ this.url = graphqlEndpoint(url);
2604
2610
  this.logger = loggers.get("orderbook");
2605
2611
  }
2606
2612
  /** The limits to validate against before posting, plus the books on offer. */
@@ -2698,7 +2704,9 @@ var OrderbookClient = class {
2698
2704
  clearTimeout(timer);
2699
2705
  }
2700
2706
  if (!response.ok) {
2701
- throw new OrderbookRequestError(`Orderbook returned HTTP ${response.status} ${response.statusText}`);
2707
+ throw new OrderbookRequestError(
2708
+ `Orderbook at ${this.url} returned HTTP ${response.status} ${response.statusText}`
2709
+ );
2702
2710
  }
2703
2711
  const body = await response.json().catch((err) => {
2704
2712
  throw new OrderbookRequestError(`Orderbook returned a body that is not JSON: ${describe(err)}`);
@@ -2813,7 +2821,7 @@ var HEARTBEAT_TYPES = {
2813
2821
  var LimitOrderValidationError = class extends Error {
2814
2822
  };
2815
2823
  var LimitOrderService = class {
2816
- constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce) {
2824
+ constructor(store, client, contractService, configService, assetRegistry, signer, defaultTtlSecs, delegationService, loggers = defaultLoggerContext(), startingNonce = initialOrderNonce, vaultBalances) {
2817
2825
  this.store = store;
2818
2826
  this.client = client;
2819
2827
  this.contractService = contractService;
@@ -2823,6 +2831,7 @@ var LimitOrderService = class {
2823
2831
  this.defaultTtlSecs = defaultTtlSecs;
2824
2832
  this.delegationService = delegationService;
2825
2833
  this.startingNonce = startingNonce;
2834
+ this.vaultBalances = vaultBalances;
2826
2835
  this.logger = loggers.get("limit-orders");
2827
2836
  }
2828
2837
  /**
@@ -2958,6 +2967,16 @@ var LimitOrderService = class {
2958
2967
  throw err;
2959
2968
  }
2960
2969
  }
2970
+ /**
2971
+ * The pairs an order can be written against, and the smallest payout each token may carry.
2972
+ *
2973
+ * The orderbook lists the books; `resolveBook` refuses a request naming anything else, so the
2974
+ * operator is offered exactly these and no combination of symbols they could not post.
2975
+ */
2976
+ async books() {
2977
+ const limits = await this.limits();
2978
+ return { books: limits.books, minOrderSizes: limits.serverInfo.minOrderSizes };
2979
+ }
2961
2980
  /**
2962
2981
  * Reads `serverInfo` and `books`, reusing the last read for {@link LIMITS_TTL_MS}.
2963
2982
  *
@@ -3065,28 +3084,61 @@ var LimitOrderService = class {
3065
3084
  * balance on the fill chain, so the operator hears it while they are creating
3066
3085
  * the order.
3067
3086
  *
3087
+ * A fill pays out of the wallet and then withdraws any shortfall from the
3088
+ * configured ERC-4626 vaults in the same batch, so what those vaults hold of
3089
+ * the payout token backs the order too and is counted here. Without it an
3090
+ * operator who sweeps inventory into a vault is refused orders their filler
3091
+ * would have filled.
3092
+ *
3068
3093
  * Each order is checked against the whole balance, not against what is left of
3069
3094
  * it after the others. One balance backs every order resting on it — that is
3070
3095
  * what quoting both sides of a book is — and the orderbook says as much: it
3071
3096
  * advertises each entry at `min(quoted, balance)` rather than dividing the
3072
3097
  * balance between them. Whichever order fills first draws the inventory down,
3073
3098
  * and the rest are cut to what is left. Netting them here would refuse the
3074
- * second side of every book.
3099
+ * second side of every book. Vault positions are counted the same way: the
3100
+ * whole position, not what is left after reservations for fills in flight.
3075
3101
  */
3076
3102
  async assertWalletCanPay(symbol, chain, payout) {
3077
3103
  const token = this.assetRegistry.getAddress(symbol, chain);
3078
3104
  if (!token) return;
3079
3105
  const decimals = await this.decimalsFor(symbol, token, chain);
3080
- const balance = toScaled(
3106
+ const wallet = toScaled(
3081
3107
  await this.contractService.getTokenBalance(chain, token, this.signer.address),
3082
3108
  decimals
3083
3109
  );
3084
- if (balance < payout) {
3110
+ const vaults = await this.vaultHoldings(token, chain);
3111
+ if (wallet + vaults < payout) {
3112
+ const held = vaults > 0n ? `${toHuman(wallet)} in the wallet and ${toHuman(vaults)} in vaults` : toHuman(wallet);
3085
3113
  throw new LimitOrderValidationError(
3086
- `The wallet holds ${toHuman(balance)} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
3114
+ `The wallet holds ${held} ${symbol} on ${chain}, which cannot pay out ${toHuman(payout)}`
3087
3115
  );
3088
3116
  }
3089
3117
  }
3118
+ /**
3119
+ * What the configured vaults hold of `token` on `chain`, in scaled units.
3120
+ *
3121
+ * Zero when no vault is configured. A snapshot that cannot be read is also
3122
+ * zero: the wallet alone then has to cover the order, which refuses one the
3123
+ * filler might have managed rather than accepting one it could not.
3124
+ */
3125
+ async vaultHoldings(token, chain) {
3126
+ if (!this.vaultBalances) return 0n;
3127
+ let positions;
3128
+ try {
3129
+ positions = await this.vaultBalances.getBalanceSnapshot(chain);
3130
+ } catch (err) {
3131
+ this.logger.warn({ chain, token, err }, "Could not read vault balances; backing the order on the wallet alone");
3132
+ return 0n;
3133
+ }
3134
+ const wanted = token.toLowerCase();
3135
+ let total = 0n;
3136
+ for (const position of positions) {
3137
+ if (position.chain !== chain || position.asset.toLowerCase() !== wanted) continue;
3138
+ total += toScaled(position.positionAssets, position.decimals);
3139
+ }
3140
+ return total;
3141
+ }
3090
3142
  /**
3091
3143
  * Works a limit order down by what a fill delivered, and puts the rest back on
3092
3144
  * the orderbook.
@@ -23563,7 +23615,10 @@ async function bootFiller(config, options) {
23563
23615
  runtimeSigner,
23564
23616
  config.orderbook.defaultTtlSecs ?? DEFAULT_LIMIT_ORDER_TTL_SECONDS,
23565
23617
  new DelegationService(chainClientManager, configService, runtimeSigner),
23566
- options.loggers
23618
+ options.loggers,
23619
+ initialOrderNonce,
23620
+ // A fill withdraws a payout shortfall from the vaults, so what they hold backs an order too.
23621
+ vaultVenue
23567
23622
  );
23568
23623
  intentFiller.setLimitOrderService(limitOrderService);
23569
23624
  const lifecycle = new LimitOrderLifecycle(
@@ -24634,6 +24689,10 @@ var LimitOrderController = class {
24634
24689
  ]);
24635
24690
  return { order, fills, bids };
24636
24691
  }
24692
+ /** The pairs the orderbook lists, and the smallest payout each token may carry. */
24693
+ books() {
24694
+ return this.service.books();
24695
+ }
24637
24696
  /** Creates the order, posts it, and reports what the orderbook made of it. */
24638
24697
  async create(request) {
24639
24698
  const posted = await this.service.create(request);