@hyperbridge/simplex 0.16.8 → 0.16.12

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
@@ -32,6 +32,8 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AssetController: () => AssetController,
34
34
  ChainController: () => ChainController,
35
+ LimitOrderController: () => LimitOrderController,
36
+ LimitOrderValidationError: () => LimitOrderValidationError,
35
37
  LoggerContext: () => LoggerContext,
36
38
  MemoryDataStore: () => MemoryDataStore,
37
39
  OrderScanner: () => OrderScanner,
@@ -44,6 +46,7 @@ __export(src_exports, {
44
46
  WalletController: () => WalletController,
45
47
  addLogSink: () => addLogSink,
46
48
  assertConfirmationCoverage: () => assertConfirmationCoverage,
49
+ canDerive: () => canDerive,
47
50
  configureLogger: () => configureLogger,
48
51
  createSigner: () => createSigner,
49
52
  digestSigner: () => digestSigner,
@@ -56,10 +59,12 @@ __export(src_exports, {
56
59
  pickAnchorStable: () => pickAnchorStable,
57
60
  privateKeySigner: () => privateKeySigner,
58
61
  registrySymbols: () => registrySymbols,
62
+ secretPhraseSigner: () => secretPhraseSigner,
59
63
  turnkeySigner: () => turnkeySigner,
60
64
  validateAssetDefinitions: () => validateAssetDefinitions,
61
65
  validateConfig: () => validateConfig,
62
66
  validatePairConfigs: () => validatePairConfigs,
67
+ validateSecretPhrase: () => validateSecretPhrase,
63
68
  validateSignerConfig: () => validateSignerConfig,
64
69
  validateVaultToml: () => validateVaultToml,
65
70
  viemSigner: () => viemSigner
@@ -70,7 +75,7 @@ module.exports = __toCommonJS(src_exports);
70
75
  var import_node_events3 = require("node:events");
71
76
 
72
77
  // src/core/boot.ts
73
- var import_viem30 = require("viem");
78
+ var import_viem32 = require("viem");
74
79
 
75
80
  // src/core/filler.ts
76
81
  var import_util_crypto = require("@polkadot/util-crypto");
@@ -1160,6 +1165,7 @@ var import_viem2 = require("viem");
1160
1165
  var import_sdk2 = require("@hyperbridge/sdk");
1161
1166
  var DEFAULT_BLOCK_SCAN_INTERVAL_SECONDS = 3;
1162
1167
  var MIN_BLOCK_SCAN_INTERVAL_SECONDS = 0.1;
1168
+ var DEFAULT_MIN_ORDER_SIZE_USD = 20;
1163
1169
  function validateRpcUrls(rpcUrls) {
1164
1170
  if (rpcUrls.length === 0) {
1165
1171
  throw new Error("rpcUrls must contain at least one URL");
@@ -1582,6 +1588,13 @@ var FillerConfigService = class {
1582
1588
  getBidValiditySeconds() {
1583
1589
  return this.fillerConfig?.bidValiditySeconds ?? 300;
1584
1590
  }
1591
+ /**
1592
+ * The order size, in USD, below which `order.fees` must cover the execution cost.
1593
+ * Defaults to 20. An order at or above it is filled whatever fees it carries.
1594
+ */
1595
+ getMinOrderSizeUsd() {
1596
+ return this.fillerConfig?.minOrderSizeUsd ?? DEFAULT_MIN_ORDER_SIZE_USD;
1597
+ }
1585
1598
  /** Ceiling bps above user-requested output. Default 500 (5%). */
1586
1599
  getMaxOverfillBps() {
1587
1600
  return BigInt(this.fillerConfig?.overfillProtection?.maxOverfillBps ?? 500);
@@ -1685,6 +1698,12 @@ function validateConfig(config, cliWatchOnly = false) {
1685
1698
  );
1686
1699
  }
1687
1700
  }
1701
+ const minOrderSize = config.simplex.minOrderSizeUsd;
1702
+ if (minOrderSize !== void 0) {
1703
+ if (!Number.isFinite(minOrderSize) || minOrderSize < 0) {
1704
+ throw new Error(`simplex.minOrderSizeUsd must be a number >= 0 (USD); got ${minOrderSize}`);
1705
+ }
1706
+ }
1688
1707
  if (config.allowlist) {
1689
1708
  for (const user of config.allowlist.users ?? []) {
1690
1709
  if (!(0, import_viem2.isAddress)(user)) {
@@ -2838,11 +2857,21 @@ var import_node_crypto = require("node:crypto");
2838
2857
  var import_sdk4 = require("@hyperbridge/sdk");
2839
2858
 
2840
2859
  // src/orderbook/amounts.ts
2860
+ var import_viem6 = require("viem");
2841
2861
  var ORDERBOOK_SCALE = 10n ** 18n;
2842
2862
  function toRaw(amount, decimals) {
2843
2863
  const shift = 18 - decimals;
2844
2864
  return shift >= 0 ? amount / 10n ** BigInt(shift) : amount * 10n ** BigInt(-shift);
2845
2865
  }
2866
+ function budgetIdFor(limitOrderId) {
2867
+ return (0, import_viem6.keccak256)((0, import_viem6.stringToBytes)(limitOrderId));
2868
+ }
2869
+ function budgetFor(order, token, decimals) {
2870
+ return { budgetId: budgetIdFor(order.id), cap: toRaw(BigInt(order.size), decimals), token };
2871
+ }
2872
+ function budgetRoom(budget, spent, decimals) {
2873
+ return spent >= budget.cap ? 0n : toScaled(budget.cap - spent, decimals);
2874
+ }
2846
2875
  function fromHuman(amount) {
2847
2876
  const text = amount.trim();
2848
2877
  if (!/^[0-9]+(\.[0-9]+)?$/.test(text)) {
@@ -2863,6 +2892,10 @@ function toScaled(amount, decimals) {
2863
2892
  const shift = 18 - decimals;
2864
2893
  return shift >= 0 ? amount * 10n ** BigInt(shift) : amount / 10n ** BigInt(-shift);
2865
2894
  }
2895
+ function toScaledUp(amount, decimals) {
2896
+ const shift = 18 - decimals;
2897
+ return shift >= 0 ? amount * 10n ** BigInt(shift) : divCeil(amount, 10n ** BigInt(-shift));
2898
+ }
2866
2899
  function divCeil(numerator, denominator) {
2867
2900
  return (numerator + denominator - 1n) / denominator;
2868
2901
  }
@@ -2904,6 +2937,87 @@ function inputFor(params) {
2904
2937
  return divCeil(scaled, unit) * unit;
2905
2938
  }
2906
2939
 
2940
+ // src/orderbook/matching.ts
2941
+ function limitOrderLegs(order) {
2942
+ return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
2943
+ }
2944
+ function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
2945
+ const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
2946
+ const offer = offerFor({
2947
+ side: order.side,
2948
+ inputAmount: incoming.inputNet,
2949
+ price: BigInt(order.price),
2950
+ outputDecimals: incoming.outputDecimals
2951
+ });
2952
+ const available = BigInt(order.remaining);
2953
+ return { order, offer, available, payout: offer < available ? offer : available };
2954
+ }).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
2955
+ return qualifying;
2956
+ }
2957
+ function byBestOfferFirst(a, b) {
2958
+ if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
2959
+ if (a.available !== b.available) return a.available > b.available ? -1 : 1;
2960
+ return a.order.id < b.order.id ? -1 : 1;
2961
+ }
2962
+ function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
2963
+ let furthest = -1;
2964
+ for (const order of orders) {
2965
+ const failed = firstFailed(order, incoming, resolve, now);
2966
+ furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
2967
+ }
2968
+ const { source, destination, inputSymbol } = incoming;
2969
+ if (furthest < CHECKS.length) {
2970
+ switch (CHECKS[Math.max(furthest, 0)]) {
2971
+ case "open":
2972
+ return "no limit order is open";
2973
+ case "chain":
2974
+ return `no open limit order fills on ${destination}`;
2975
+ case "input":
2976
+ return `no limit order on ${destination} takes ${inputSymbol} in`;
2977
+ case "output":
2978
+ return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
2979
+ case "source":
2980
+ return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
2981
+ }
2982
+ }
2983
+ const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
2984
+ order,
2985
+ offer: offerFor({
2986
+ side: order.side,
2987
+ inputAmount: incoming.inputNet,
2988
+ price: BigInt(order.price),
2989
+ outputDecimals: incoming.outputDecimals
2990
+ })
2991
+ }));
2992
+ if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
2993
+ return "the limit orders that meet its rate have nothing left to pay out";
2994
+ }
2995
+ const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
2996
+ const symbol = limitOrderLegs(best.order).output;
2997
+ return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
2998
+ }
2999
+ var CHECKS = ["open", "chain", "input", "output", "source"];
3000
+ function serves(order, incoming, resolve, now) {
3001
+ return firstFailed(order, incoming, resolve, now) === null;
3002
+ }
3003
+ function firstFailed(order, incoming, resolve, now) {
3004
+ if (order.status !== "open") return "open";
3005
+ if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
3006
+ if (order.fillChain !== incoming.destination) return "chain";
3007
+ const legs = limitOrderLegs(order);
3008
+ if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
3009
+ const outputToken = resolve(legs.output, order.fillChain);
3010
+ if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
3011
+ if (incoming.source === incoming.destination) return null;
3012
+ return order.acceptedSources.includes(incoming.source) ? null : "source";
3013
+ }
3014
+ function readable(amount) {
3015
+ const [whole, fraction] = toHuman(amount).split(".");
3016
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3017
+ const cut = fraction?.slice(0, 6).replace(/0+$/, "");
3018
+ return cut ? `${grouped}.${cut}` : grouped;
3019
+ }
3020
+
2907
3021
  // src/orderbook/limit-orders.ts
2908
3022
  var LIMITS_TTL_MS = 5 * 60 * 1e3;
2909
3023
  var POSTABLE = ["open", "resizing"];
@@ -3418,6 +3532,58 @@ var LimitOrderService = class {
3418
3532
  }
3419
3533
  return report;
3420
3534
  }
3535
+ /**
3536
+ * Lowers every open order's `remaining` to what the solver's account will still
3537
+ * pay for it, and puts the order back on the book at that size.
3538
+ *
3539
+ * The account tallies each order's payouts on the fill chain and refuses a fill
3540
+ * that would take the tally past the order's size. A fill that never settled
3541
+ * here, or a store restored from an older copy, leaves `remaining` above that
3542
+ * room and the order posted at a size the account will refuse. A tally behind
3543
+ * the store changes nothing: `remaining` is never raised.
3544
+ *
3545
+ * Answers how many orders were lowered.
3546
+ */
3547
+ async reconcileTallies() {
3548
+ let lowered = 0;
3549
+ for (const order of await this.store.list({ status: "open" })) {
3550
+ try {
3551
+ const clamped = await this.clampToTally(order);
3552
+ if (!clamped) continue;
3553
+ lowered++;
3554
+ await this.resize(clamped, BigInt(order.remaining) - BigInt(clamped.remaining));
3555
+ } catch (err) {
3556
+ this.logger.error({ id: order.id, err }, "Could not bring the limit order in line with its on-chain tally");
3557
+ }
3558
+ }
3559
+ return lowered;
3560
+ }
3561
+ /** The order as the store now holds it when its tally lowered it, null when it was left alone. */
3562
+ async clampToTally(order) {
3563
+ const token = this.assetRegistry.getAddress(limitOrderLegs(order).output, order.fillChain);
3564
+ if (!token) return null;
3565
+ const decimals = await this.contractService.getTokenDecimals(token, order.fillChain);
3566
+ const budget = budgetFor(order, token, decimals);
3567
+ const spent = await this.contractService.limitOrderSpent(order.fillChain, budget.budgetId);
3568
+ if (spent === null) return null;
3569
+ const room = budgetRoom(budget, spent, decimals);
3570
+ const over = BigInt(order.remaining) - (room + BigInt(order.reserved));
3571
+ if (over < toScaled(1n, decimals)) return null;
3572
+ const clamped = await this.store.clampRemaining(order.id, room.toString());
3573
+ if (!clamped) return null;
3574
+ this.logger.warn(
3575
+ {
3576
+ id: order.id,
3577
+ before: order.remaining,
3578
+ after: clamped.remaining,
3579
+ spent: spent.toString(),
3580
+ cap: budget.cap.toString(),
3581
+ reserved: clamped.reserved
3582
+ },
3583
+ "Limit order had more left than its on-chain tally allows; lowering it"
3584
+ );
3585
+ return clamped;
3586
+ }
3421
3587
  /**
3422
3588
  * Withdraws every order that has outlived the operator's own `expiresAt`.
3423
3589
  *
@@ -3679,7 +3845,7 @@ async function patchRuntimeState(store, patch) {
3679
3845
  }
3680
3846
 
3681
3847
  // src/services/wallet/mpcvault.ts
3682
- var import_viem6 = require("viem");
3848
+ var import_viem7 = require("viem");
3683
3849
  var grpc = __toESM(require("@grpc/grpc-js"), 1);
3684
3850
 
3685
3851
  // src/proto/mpcvault/platform/v1/api.ts
@@ -12243,18 +12409,18 @@ var MpcVaultService = class {
12243
12409
  // Signature assembly
12244
12410
  // -----------------------------------------------------------------------
12245
12411
  signatureFromParts(parts, normalizeV = false) {
12246
- const r = (0, import_viem6.padHex)((0, import_viem6.toHex)(BigInt(parts.R || "0")), { size: 32 });
12247
- const s = (0, import_viem6.padHex)((0, import_viem6.toHex)(BigInt(parts.S || "0")), { size: 32 });
12412
+ const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
12413
+ const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
12248
12414
  const vInt = this.parseFlexibleBigInt(parts.V || "0");
12249
- let v = (0, import_viem6.padHex)((0, import_viem6.toHex)(vInt), { size: 1 });
12415
+ let v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt), { size: 1 });
12250
12416
  if (normalizeV) {
12251
- v = (0, import_viem6.padHex)((0, import_viem6.toHex)(vInt < 27n ? vInt + 27n : vInt), { size: 1 });
12417
+ v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt < 27n ? vInt + 27n : vInt), { size: 1 });
12252
12418
  }
12253
- return (0, import_viem6.concatHex)([r, s, v]);
12419
+ return (0, import_viem7.concatHex)([r, s, v]);
12254
12420
  }
12255
12421
  ecdsaPartsToSignatureComponents(parts) {
12256
- const r = (0, import_viem6.padHex)((0, import_viem6.toHex)(BigInt(parts.R || "0")), { size: 32 });
12257
- const s = (0, import_viem6.padHex)((0, import_viem6.toHex)(BigInt(parts.S || "0")), { size: 32 });
12422
+ const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
12423
+ const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
12258
12424
  const vInt = this.parseFlexibleBigInt(parts.V || "0");
12259
12425
  const yParity = vInt >= 27n ? Number(vInt - 27n) : Number(vInt);
12260
12426
  if (yParity !== 0 && yParity !== 1) {
@@ -12290,7 +12456,7 @@ var MpcVaultService = class {
12290
12456
  const messageBytes = Buffer.from(messageHash.slice(2), "hex");
12291
12457
  const prefix = Buffer.from(`Ethereum Signed Message:
12292
12458
  ${messageBytes.length}`);
12293
- const ethSignedMessageHash = (0, import_viem6.keccak256)((0, import_viem6.concatHex)([(0, import_viem6.toHex)(prefix), messageHash]));
12459
+ const ethSignedMessageHash = (0, import_viem7.keccak256)((0, import_viem7.concatHex)([(0, import_viem7.toHex)(prefix), messageHash]));
12294
12460
  const ethSignedBytes = Buffer.from(ethSignedMessageHash.slice(2), "hex");
12295
12461
  const uuid = await this.createSigningRequest({
12296
12462
  vaultUuid: this.vaultUuid,
@@ -12378,11 +12544,12 @@ var SignerType = /* @__PURE__ */ ((SignerType2) => {
12378
12544
  SignerType2["PrivateKey"] = "privateKey";
12379
12545
  SignerType2["MpcVault"] = "mpcVault";
12380
12546
  SignerType2["Turnkey"] = "turnkey";
12547
+ SignerType2["SecretPhrase"] = "secretPhrase";
12381
12548
  return SignerType2;
12382
12549
  })(SignerType || {});
12383
12550
 
12384
12551
  // src/services/wallet/accounts/mpc.ts
12385
- var import_viem7 = require("viem");
12552
+ var import_viem8 = require("viem");
12386
12553
  var import_utils = require("viem/utils");
12387
12554
  function mpcVaultSigner(config) {
12388
12555
  const service = new MpcVaultService({
@@ -12417,9 +12584,9 @@ function mpcVaultSigner(config) {
12417
12584
  if (tx.authorizationList?.length) {
12418
12585
  const serializable = { ...tx, type: "eip7702" };
12419
12586
  const signature = await service.signRawHashComponents(
12420
- (0, import_viem7.keccak256)((0, import_viem7.serializeTransaction)(serializable))
12587
+ (0, import_viem8.keccak256)((0, import_viem8.serializeTransaction)(serializable))
12421
12588
  );
12422
- return (0, import_viem7.serializeTransaction)(serializable, signature);
12589
+ return (0, import_viem8.serializeTransaction)(serializable, signature);
12423
12590
  }
12424
12591
  return service.signTransaction({
12425
12592
  chainId: tx.chainId,
@@ -12444,10 +12611,10 @@ function requireChainId(value) {
12444
12611
  var import_accounts = require("viem/accounts");
12445
12612
 
12446
12613
  // src/services/wallet/accounts/viem.ts
12447
- var import_viem8 = require("viem");
12614
+ var import_viem9 = require("viem");
12448
12615
  var import_utils2 = require("viem/utils");
12449
12616
  function splitSignature(signature, source) {
12450
- const sig = (0, import_viem8.parseSignature)(signature);
12617
+ const sig = (0, import_viem9.parseSignature)(signature);
12451
12618
  const yParity = sig.yParity ?? (sig.v !== void 0 ? Number(sig.v >= 27n ? sig.v - 27n : sig.v) : void 0);
12452
12619
  if (yParity !== 0 && yParity !== 1) {
12453
12620
  throw new Error(`Failed to derive yParity from ${source} signature`);
@@ -12499,9 +12666,54 @@ function privateKeySigner(privateKey) {
12499
12666
  return viemSigner((0, import_accounts.privateKeyToAccount)(privateKey));
12500
12667
  }
12501
12668
 
12669
+ // src/services/wallet/accounts/secretphrase.ts
12670
+ var import_bip39 = require("@scure/bip39");
12671
+ var import_accounts2 = require("viem/accounts");
12672
+ var WORD_COUNTS = [12, 15, 18, 21, 24];
12673
+ var MAX_DERIVATION_INDEX = 2 ** 31 - 1;
12674
+ function normaliseSecretPhrase(phrase) {
12675
+ return phrase.trim().split(/\s+/).join(" ").toLowerCase();
12676
+ }
12677
+ function validateSecretPhrase(phrase) {
12678
+ if (typeof phrase !== "string") throw new Error("Secret phrase must be a string");
12679
+ const normalised = normaliseSecretPhrase(phrase);
12680
+ const words = normalised === "" ? [] : normalised.split(" ");
12681
+ if (!WORD_COUNTS.includes(words.length)) {
12682
+ throw new Error(`Secret phrase must have ${WORD_COUNTS.join(", ")} words; got ${words.length}`);
12683
+ }
12684
+ if (words.some((word) => !import_accounts2.english.includes(word))) {
12685
+ throw new Error("Secret phrase contains a word that is not in the BIP-39 English wordlist");
12686
+ }
12687
+ if (!(0, import_bip39.validateMnemonic)(normalised, import_accounts2.english)) {
12688
+ throw new Error("Secret phrase checksum does not match; check the words and their order");
12689
+ }
12690
+ }
12691
+ function assertDerivationIndex(index, name = "Derivation index") {
12692
+ const expected = `${name} must be an integer between 0 and ${MAX_DERIVATION_INDEX}`;
12693
+ if (typeof index !== "number") throw new Error(expected);
12694
+ if (!Number.isInteger(index) || index < 0 || index > MAX_DERIVATION_INDEX) {
12695
+ throw new Error(`${expected}; got ${index}`);
12696
+ }
12697
+ }
12698
+ function secretPhraseSigner(config) {
12699
+ validateSecretPhrase(config.phrase);
12700
+ const phrase = normaliseSecretPhrase(config.phrase);
12701
+ const accountIndex = config.accountIndex ?? 0;
12702
+ assertDerivationIndex(accountIndex, "accountIndex");
12703
+ const derive = (index) => {
12704
+ assertDerivationIndex(index);
12705
+ return { ...viemSigner((0, import_accounts2.mnemonicToAccount)(phrase, { addressIndex: index })), mode: "secretPhrase" };
12706
+ };
12707
+ return { ...derive(accountIndex), accountIndex, derive };
12708
+ }
12709
+ function canDerive(signer) {
12710
+ const candidate = signer;
12711
+ return typeof candidate.derive === "function" && typeof candidate.accountIndex === "number";
12712
+ }
12713
+
12502
12714
  // src/services/wallet/accounts/turnkey.ts
12503
12715
  var import_sdk_server = require("@turnkey/sdk-server");
12504
- var import_viem10 = require("@turnkey/viem");
12716
+ var import_viem12 = require("@turnkey/viem");
12505
12717
  async function turnkeySigner(config) {
12506
12718
  const turnkey = new import_sdk_server.Turnkey({
12507
12719
  defaultOrganizationId: config.organizationId,
@@ -12509,7 +12721,7 @@ async function turnkeySigner(config) {
12509
12721
  apiPrivateKey: config.apiPrivateKey,
12510
12722
  apiPublicKey: config.apiPublicKey
12511
12723
  });
12512
- const account = await (0, import_viem10.createAccount)({
12724
+ const account = await (0, import_viem12.createAccount)({
12513
12725
  client: turnkey.apiClient(),
12514
12726
  organizationId: config.organizationId,
12515
12727
  signWith: config.signWith
@@ -12522,6 +12734,7 @@ async function createSigner(config) {
12522
12734
  if (config.type === "privateKey" /* PrivateKey */) return privateKeySigner(config.key);
12523
12735
  if (config.type === "mpcVault" /* MpcVault */) return mpcVaultSigner(config);
12524
12736
  if (config.type === "turnkey" /* Turnkey */) return turnkeySigner(config);
12737
+ if (config.type === "secretPhrase" /* SecretPhrase */) return secretPhraseSigner(config);
12525
12738
  throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
12526
12739
  }
12527
12740
  function validateSignerConfig(config) {
@@ -12547,15 +12760,23 @@ function validateSignerConfig(config) {
12547
12760
  if (!config.signWith) throw new Error("simplex.signer.signWith is required");
12548
12761
  return;
12549
12762
  }
12763
+ if (config.type === "secretPhrase" /* SecretPhrase */) {
12764
+ if (!config.phrase) {
12765
+ throw new Error("simplex.signer.phrase is required when simplex.signer.type=secretPhrase");
12766
+ }
12767
+ validateSecretPhrase(config.phrase);
12768
+ if (config.accountIndex !== void 0) assertDerivationIndex(config.accountIndex, "simplex.signer.accountIndex");
12769
+ return;
12770
+ }
12550
12771
  throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
12551
12772
  }
12552
12773
 
12553
12774
  // src/services/wallet/account.ts
12554
- var import_viem12 = require("viem");
12775
+ var import_viem14 = require("viem");
12555
12776
  var import_utils3 = require("viem/utils");
12556
- var import_accounts2 = require("viem/accounts");
12777
+ var import_accounts3 = require("viem/accounts");
12557
12778
  function accountFor(signer) {
12558
- return (0, import_accounts2.toAccount)({
12779
+ return (0, import_accounts3.toAccount)({
12559
12780
  address: signer.address,
12560
12781
  signTypedData: (typedData) => signer.signTypedData(typedData),
12561
12782
  signTransaction: async (transaction) => await signer.signTransaction(toSignerTransaction(transaction)),
@@ -12595,7 +12816,7 @@ function digestSigner(opts) {
12595
12816
  return {
12596
12817
  address: opts.address,
12597
12818
  mode: opts.mode,
12598
- signTypedData: async (typedData) => (0, import_viem12.serializeSignature)(await sign((0, import_viem12.hashTypedData)(typedData))),
12819
+ signTypedData: async (typedData) => (0, import_viem14.serializeSignature)(await sign((0, import_viem14.hashTypedData)(typedData))),
12599
12820
  signAuthorization: (auth) => sign(
12600
12821
  (0, import_utils3.hashAuthorization)({
12601
12822
  address: auth.contractAddress,
@@ -12605,8 +12826,8 @@ function digestSigner(opts) {
12605
12826
  ),
12606
12827
  signTransaction: async (tx) => {
12607
12828
  const serializable = tx;
12608
- const signature = await sign((0, import_viem12.keccak256)((0, import_viem12.serializeTransaction)(serializable)));
12609
- return (0, import_viem12.serializeTransaction)(serializable, signature);
12829
+ const signature = await sign((0, import_viem14.keccak256)((0, import_viem14.serializeTransaction)(serializable)));
12830
+ return (0, import_viem14.serializeTransaction)(serializable, signature);
12610
12831
  }
12611
12832
  };
12612
12833
  }
@@ -12635,6 +12856,12 @@ async function deriveSubstrateKeyPair(key) {
12635
12856
  }
12636
12857
 
12637
12858
  // src/services/CacheService.ts
12859
+ function budgetToCache(budget) {
12860
+ return { budgetId: budget.budgetId, cap: budget.cap.toString(), token: budget.token };
12861
+ }
12862
+ function budgetFromCache(budget) {
12863
+ return { budgetId: budget.budgetId, cap: BigInt(budget.cap), token: budget.token };
12864
+ }
12638
12865
  var CacheService = class {
12639
12866
  constructor(loggers = defaultLoggerContext()) {
12640
12867
  this.CACHE_EXPIRY_MS = 1 * 60 * 1e3;
@@ -12648,6 +12875,7 @@ var CacheService = class {
12648
12875
  matchedLimitOrders: {},
12649
12876
  bidPlans: {},
12650
12877
  partialFills: {},
12878
+ feeChecksWaived: {},
12651
12879
  feeTokens: {},
12652
12880
  tokenDecimals: {},
12653
12881
  solverSelection: {}
@@ -12687,6 +12915,10 @@ var CacheService = class {
12687
12915
  stalePartialIds.forEach((orderId) => {
12688
12916
  delete this.cacheData.partialFills[orderId];
12689
12917
  });
12918
+ const staleWaivedIds = Object.entries(this.cacheData.feeChecksWaived).filter(([_, data]) => !this.isCacheValid(data.timestamp)).map(([orderId]) => orderId);
12919
+ staleWaivedIds.forEach((orderId) => {
12920
+ delete this.cacheData.feeChecksWaived[orderId];
12921
+ });
12690
12922
  }
12691
12923
  getGasEstimate(orderId) {
12692
12924
  try {
@@ -12792,7 +13024,24 @@ var CacheService = class {
12792
13024
  return null;
12793
13025
  }
12794
13026
  }
12795
- setFillerOutputs(orderId, outputs, inputs = []) {
13027
+ /**
13028
+ * The budget of the limit order that priced the outputs cached for this order, or
13029
+ * null when no limit order did.
13030
+ *
13031
+ * Kept on the same entry as the outputs, so the two are replaced and expire
13032
+ * together: a bid can never sign one limit order's payout beside another's budget.
13033
+ */
13034
+ getBidBudget(orderId) {
13035
+ try {
13036
+ const cache = this.cacheData.fillerOutputs[orderId];
13037
+ if (cache?.budget && this.isCacheValid(cache.timestamp)) return budgetFromCache(cache.budget);
13038
+ return null;
13039
+ } catch (error) {
13040
+ this.logger.error({ err: error }, "Error getting bid budget");
13041
+ return null;
13042
+ }
13043
+ }
13044
+ setFillerOutputs(orderId, outputs, inputs = [], budget) {
12796
13045
  try {
12797
13046
  this.cleanupStaleData();
12798
13047
  this.cacheData.fillerOutputs[orderId] = {
@@ -12804,6 +13053,7 @@ var CacheService = class {
12804
13053
  token: o.token,
12805
13054
  amount: o.amount.toString()
12806
13055
  })),
13056
+ ...budget ? { budget: budgetToCache(budget) } : {},
12807
13057
  timestamp: Date.now()
12808
13058
  };
12809
13059
  } catch (error) {
@@ -12849,7 +13099,8 @@ var CacheService = class {
12849
13099
  data: call.data
12850
13100
  })),
12851
13101
  partialFill: plan.partialFill,
12852
- profit: plan.profit
13102
+ profit: plan.profit,
13103
+ ...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
12853
13104
  }));
12854
13105
  } catch (error) {
12855
13106
  this.logger.error({ err: error }, "Error getting bid plans");
@@ -12878,7 +13129,8 @@ var CacheService = class {
12878
13129
  data: call.data
12879
13130
  })),
12880
13131
  partialFill: plan.partialFill,
12881
- profit: plan.profit
13132
+ profit: plan.profit,
13133
+ ...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
12882
13134
  })),
12883
13135
  timestamp: Date.now()
12884
13136
  };
@@ -12979,6 +13231,24 @@ var CacheService = class {
12979
13231
  clearPartialFill(orderId) {
12980
13232
  delete this.cacheData.partialFills[orderId];
12981
13233
  }
13234
+ /**
13235
+ * Whether the strategy's completed evaluation let a bid through without holding
13236
+ * `order.fees` to the execution cost, because the order is at or above the
13237
+ * operator's minimum size.
13238
+ *
13239
+ * Written and cleared the way {@link isPartialFill} is, for the same reason: the
13240
+ * caller exempts such an order from its profit floor.
13241
+ */
13242
+ isFeeCheckWaived(orderId) {
13243
+ const cache = this.cacheData.feeChecksWaived[orderId];
13244
+ return cache !== void 0 && this.isCacheValid(cache.timestamp) && cache.waived;
13245
+ }
13246
+ setFeeCheckWaived(orderId, waived) {
13247
+ this.cacheData.feeChecksWaived[orderId] = { waived, timestamp: Date.now() };
13248
+ }
13249
+ clearFeeCheckWaived(orderId) {
13250
+ delete this.cacheData.feeChecksWaived[orderId];
13251
+ }
12982
13252
  getFeeTokenWithDecimals(chain) {
12983
13253
  try {
12984
13254
  const cache = this.cacheData.feeTokens[chain];
@@ -13036,12 +13306,12 @@ var CacheService = class {
13036
13306
  };
13037
13307
 
13038
13308
  // src/services/ChainClientManager.ts
13039
- var import_viem15 = require("viem");
13040
- var import_accounts3 = require("viem/accounts");
13309
+ var import_viem17 = require("viem");
13310
+ var import_accounts4 = require("viem/accounts");
13041
13311
  var import_sdk6 = require("@hyperbridge/sdk");
13042
13312
 
13043
13313
  // src/services/QuorumPublicClient.ts
13044
- var import_viem14 = require("viem");
13314
+ var import_viem16 = require("viem");
13045
13315
  var import_sdk5 = require("@hyperbridge/sdk");
13046
13316
  function quorumThreshold(numProviders) {
13047
13317
  return Math.floor(2 * numProviders / 3) + 1;
@@ -13141,9 +13411,9 @@ var _QuorumPublicClient = class _QuorumPublicClient {
13141
13411
  this.deadlineMs = budget.deadlineMs;
13142
13412
  const chain = (0, import_sdk5.getViemChain)(chainId);
13143
13413
  this.clients = this.rpcUrls.map(
13144
- (url) => (0, import_viem14.createPublicClient)({
13414
+ (url) => (0, import_viem16.createPublicClient)({
13145
13415
  chain,
13146
- transport: (0, import_viem14.http)(url, {
13416
+ transport: (0, import_viem16.http)(url, {
13147
13417
  timeout: budget.timeoutMs,
13148
13418
  // No transport-level retry. viem's retry sleeps for the provider's
13149
13419
  // `Retry-After` verbatim and uncapped, which reaches the socket
@@ -13559,9 +13829,9 @@ var HTTP_TRANSPORT_OPTS = {
13559
13829
  };
13560
13830
  function buildTransport(rpcUrls) {
13561
13831
  if (rpcUrls.length === 1) {
13562
- return (0, import_viem15.http)(rpcUrls[0], HTTP_TRANSPORT_OPTS);
13832
+ return (0, import_viem17.http)(rpcUrls[0], HTTP_TRANSPORT_OPTS);
13563
13833
  }
13564
- return (0, import_viem15.fallback)(rpcUrls.map((url) => (0, import_viem15.http)(url, HTTP_TRANSPORT_OPTS)));
13834
+ return (0, import_viem17.fallback)(rpcUrls.map((url) => (0, import_viem17.http)(url, HTTP_TRANSPORT_OPTS)));
13565
13835
  }
13566
13836
  function walletClientCacheKey(chainId, accountAddress) {
13567
13837
  return `${chainId}:${accountAddress.toLowerCase()}`;
@@ -13587,7 +13857,7 @@ var ViemClientFactoryImpl = class {
13587
13857
  if (!this.publicClients.has(chainConfig.chainId)) {
13588
13858
  const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
13589
13859
  const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
13590
- const publicClient = (0, import_viem15.createPublicClient)({
13860
+ const publicClient = (0, import_viem17.createPublicClient)({
13591
13861
  chain,
13592
13862
  transport: buildTransport(urls)
13593
13863
  });
@@ -13600,7 +13870,7 @@ var ViemClientFactoryImpl = class {
13600
13870
  if (!this.walletClients.has(key)) {
13601
13871
  const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
13602
13872
  const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
13603
- const walletClient = (0, import_viem15.createWalletClient)({
13873
+ const walletClient = (0, import_viem17.createWalletClient)({
13604
13874
  chain,
13605
13875
  account,
13606
13876
  transport: buildTransport(urls)
@@ -13615,7 +13885,7 @@ var ChainClientManager = class {
13615
13885
  this.clientFactory = new ViemClientFactoryImpl();
13616
13886
  this.quorumClients = /* @__PURE__ */ new Map();
13617
13887
  this.configService = configService;
13618
- this.signer = signer ?? privateKeySigner((0, import_accounts3.generatePrivateKey)());
13888
+ this.signer = signer ?? privateKeySigner((0, import_accounts4.generatePrivateKey)());
13619
13889
  this.account = accountFor(this.signer);
13620
13890
  }
13621
13891
  /** This filler's logging destination, relayed from the config service. */
@@ -13684,9 +13954,47 @@ var ChainClientManager = class {
13684
13954
  };
13685
13955
 
13686
13956
  // src/services/ContractInteractionService.ts
13687
- var import_viem21 = require("viem");
13957
+ var import_viem23 = require("viem");
13688
13958
  var import_sdk7 = require("@hyperbridge/sdk");
13689
13959
 
13960
+ // src/config/abis/SolverAccount.ts
13961
+ var SOLVER_ACCOUNT_ABI = [
13962
+ {
13963
+ inputs: [
13964
+ { name: "orderId", type: "bytes32" },
13965
+ { name: "cap", type: "uint256" },
13966
+ { name: "token", type: "address" },
13967
+ { name: "approved", type: "uint256" },
13968
+ { name: "fee", type: "uint256" }
13969
+ ],
13970
+ name: "debitOrder",
13971
+ outputs: [],
13972
+ stateMutability: "nonpayable",
13973
+ type: "function"
13974
+ },
13975
+ {
13976
+ inputs: [{ name: "orderId", type: "bytes32" }],
13977
+ name: "spent",
13978
+ outputs: [{ name: "", type: "uint256" }],
13979
+ stateMutability: "view",
13980
+ type: "function"
13981
+ },
13982
+ {
13983
+ inputs: [{ name: "sender", type: "address" }],
13984
+ name: "AccountUnauthorized",
13985
+ type: "error"
13986
+ },
13987
+ {
13988
+ inputs: [
13989
+ { name: "orderId", type: "bytes32" },
13990
+ { name: "total", type: "uint256" },
13991
+ { name: "cap", type: "uint256" }
13992
+ ],
13993
+ name: "LimitOrderExceeded",
13994
+ type: "error"
13995
+ }
13996
+ ];
13997
+
13690
13998
  // src/config/abis/EvmHost.ts
13691
13999
  var EVM_HOST = [
13692
14000
  {
@@ -17539,10 +17847,10 @@ var ENTRYPOINT_ABI = [
17539
17847
  ];
17540
17848
 
17541
17849
  // src/services/paymaster/provider/simplex.ts
17542
- var import_viem20 = require("viem");
17850
+ var import_viem22 = require("viem");
17543
17851
 
17544
17852
  // src/services/paymaster/types.ts
17545
- var import_viem16 = require("viem");
17853
+ var import_viem18 = require("viem");
17546
17854
  var RECOMMENDED_AMOUNT_USD = 5n;
17547
17855
  var VERIFICATION_GAS_LIMIT_PERMIT2 = 200000n;
17548
17856
  var VERIFICATION_GAS_LIMIT_PERMIT = 250000n;
@@ -17550,19 +17858,19 @@ var PERMIT2_DEADLINE_SECONDS = 3600n;
17550
17858
  var POST_OP_GAS_LIMIT_SIMPLEX = 40000n;
17551
17859
  var DEPOSIT_HEADROOM_PERCENT = 150n;
17552
17860
  function packPaymasterAndData(pm) {
17553
- return (0, import_viem16.encodePacked)(
17861
+ return (0, import_viem18.encodePacked)(
17554
17862
  ["address", "uint128", "uint128", "bytes"],
17555
17863
  [pm.paymaster, pm.paymasterVerificationGasLimit, pm.paymasterPostOpGasLimit, pm.paymasterData]
17556
17864
  );
17557
17865
  }
17558
17866
 
17559
17867
  // src/services/paymaster/permit.ts
17560
- var import_viem19 = require("viem");
17868
+ var import_viem21 = require("viem");
17561
17869
 
17562
17870
  // src/config/abis/EIP2612.ts
17563
- var import_viem17 = require("viem");
17871
+ var import_viem19 = require("viem");
17564
17872
  var EIP2612_ABI = [
17565
- ...import_viem17.erc20Abi,
17873
+ ...import_viem19.erc20Abi,
17566
17874
  {
17567
17875
  inputs: [{ internalType: "address", name: "owner", type: "address" }],
17568
17876
  stateMutability: "view",
@@ -17580,7 +17888,7 @@ var EIP2612_ABI = [
17580
17888
  ];
17581
17889
 
17582
17890
  // src/services/paymaster/permit2.ts
17583
- var import_viem18 = require("viem");
17891
+ var import_viem20 = require("viem");
17584
17892
  function permit2TransferTypedData(p) {
17585
17893
  return {
17586
17894
  types: {
@@ -17615,16 +17923,16 @@ function permit2TransferTypedData(p) {
17615
17923
  };
17616
17924
  }
17617
17925
  function randomPermit2Nonce() {
17618
- return (0, import_viem18.bytesToBigInt)(crypto.getRandomValues(new Uint8Array(32)));
17926
+ return (0, import_viem20.bytesToBigInt)(crypto.getRandomValues(new Uint8Array(32)));
17619
17927
  }
17620
17928
  async function signPermit2Transfer(signer, p) {
17621
17929
  const signature = await signer.signTypedData(permit2TransferTypedData(p));
17622
17930
  return normalizeSignature65(signature);
17623
17931
  }
17624
17932
  function normalizeSignature65(signature) {
17625
- const length = (0, import_viem18.size)(signature);
17933
+ const length = (0, import_viem20.size)(signature);
17626
17934
  if (length === 64) {
17627
- return (0, import_viem18.serializeSignature)((0, import_viem18.compactSignatureToSignature)((0, import_viem18.parseCompactSignature)(signature)));
17935
+ return (0, import_viem20.serializeSignature)((0, import_viem20.compactSignatureToSignature)((0, import_viem20.parseCompactSignature)(signature)));
17628
17936
  }
17629
17937
  if (length !== 65) {
17630
17938
  throw new Error(`Permit2 signature must be 64 or 65 bytes, got ${length}`);
@@ -17638,7 +17946,7 @@ function normalizeSignature65(signature) {
17638
17946
 
17639
17947
  // src/services/paymaster/permit.ts
17640
17948
  async function signEip2612Permit(client, signer, owner, spender, tokenAddress, value, chainId) {
17641
- const token = (0, import_viem19.getContract)({
17949
+ const token = (0, import_viem21.getContract)({
17642
17950
  client,
17643
17951
  address: tokenAddress,
17644
17952
  abi: EIP2612_ABI
@@ -17676,7 +17984,7 @@ async function signEip2612Permit(client, signer, owner, spender, tokenAddress, v
17676
17984
  spender,
17677
17985
  value,
17678
17986
  nonce,
17679
- deadline: import_viem19.maxUint256
17987
+ deadline: import_viem21.maxUint256
17680
17988
  }
17681
17989
  };
17682
17990
  return normalizeSignature65(await signer.signTypedData(typedData));
@@ -17804,7 +18112,7 @@ async function selectToken(client, solverAccount, tokens, permit2) {
17804
18112
  for (const token of tokens) {
17805
18113
  const balance = await client.readContract({
17806
18114
  address: token.address,
17807
- abi: import_viem20.erc20Abi,
18115
+ abi: import_viem22.erc20Abi,
17808
18116
  functionName: "balanceOf",
17809
18117
  args: [solverAccount]
17810
18118
  });
@@ -17834,7 +18142,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
17834
18142
  permit2Support.set(key, { supported: true, checkedAt: Date.now() });
17835
18143
  return true;
17836
18144
  } catch (error) {
17837
- const isRevert = error instanceof import_viem20.BaseError && error.walk((e) => e instanceof import_viem20.ContractFunctionRevertedError || e instanceof import_viem20.ContractFunctionZeroDataError);
18145
+ const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
17838
18146
  if (isRevert) {
17839
18147
  permit2Support.set(key, { supported: false, checkedAt: Date.now() });
17840
18148
  return false;
@@ -17845,7 +18153,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
17845
18153
  async function readAllowance(client, token, owner, spender) {
17846
18154
  return await client.readContract({
17847
18155
  address: token,
17848
- abi: import_viem20.erc20Abi,
18156
+ abi: import_viem22.erc20Abi,
17849
18157
  functionName: "allowance",
17850
18158
  args: [owner, spender]
17851
18159
  });
@@ -17865,7 +18173,7 @@ async function buildPermit2Mode(signer, p) {
17865
18173
  const r = `0x${signature.slice(2, 66)}`;
17866
18174
  const s = `0x${signature.slice(66, 130)}`;
17867
18175
  const v = Number.parseInt(signature.slice(130, 132), 16);
17868
- const paymasterData = (0, import_viem20.encodePacked)(
18176
+ const paymasterData = (0, import_viem22.encodePacked)(
17869
18177
  ["uint8", "address", "uint256", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
17870
18178
  [2, p.token, p.amount, nonce, deadline, v, r, s]
17871
18179
  );
@@ -17889,9 +18197,9 @@ async function buildPermitMode(client, signer, solverAccount, paymasterAddress,
17889
18197
  const r = `0x${permitSignature.slice(2, 66)}`;
17890
18198
  const s = `0x${permitSignature.slice(66, 130)}`;
17891
18199
  const v = Number.parseInt(permitSignature.slice(130, 132), 16);
17892
- const paymasterData = (0, import_viem20.encodePacked)(
18200
+ const paymasterData = (0, import_viem22.encodePacked)(
17893
18201
  ["uint8", "address", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
17894
- [0, tokenAddress, permitAmount, import_viem20.maxUint256, v, r, s]
18202
+ [0, tokenAddress, permitAmount, import_viem22.maxUint256, v, r, s]
17895
18203
  );
17896
18204
  return {
17897
18205
  paymaster: paymasterAddress,
@@ -17905,7 +18213,7 @@ async function ensureFundedApprove(client, walletClient, chainId, solverAccount,
17905
18213
  const key = `${chainId}:${tokenAddress.toLowerCase()}:${solverAccount.toLowerCase()}`;
17906
18214
  const inFlight = approvalsInFlight.get(key);
17907
18215
  if (inFlight) return inFlight;
17908
- const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender, import_viem20.maxUint256).finally(
18216
+ const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender, import_viem22.maxUint256).finally(
17909
18217
  () => {
17910
18218
  approvalsInFlight.delete(key);
17911
18219
  }
@@ -17923,7 +18231,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
17923
18231
  const requiredNative = APPROVE_TX_GAS * gasPrice * (needsReset ? 2n : 1n);
17924
18232
  if (nativeBalance < requiredNative) {
17925
18233
  throw new Error(
17926
- `SimplexPaymaster needs a one-time Permit2 approval for the fee token on this chain; send native dust (>= ${(0, import_viem20.formatEther)(requiredNative)}) to ${solverAccount}`
18234
+ `SimplexPaymaster needs a one-time Permit2 approval for the fee token on this chain; send native dust (>= ${(0, import_viem22.formatEther)(requiredNative)}) to ${solverAccount}`
17927
18235
  );
17928
18236
  }
17929
18237
  if (needsReset) {
@@ -17934,7 +18242,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
17934
18242
  async function sendApproveTx(client, walletClient, tokenAddress, spender, amount) {
17935
18243
  const hash = await walletClient.writeContract({
17936
18244
  address: tokenAddress,
17937
- abi: import_viem20.erc20Abi,
18245
+ abi: import_viem22.erc20Abi,
17938
18246
  functionName: "approve",
17939
18247
  args: [spender, amount],
17940
18248
  chain: walletClient.chain,
@@ -17959,7 +18267,7 @@ async function tokenSupportsPermit(client, tokenAddress) {
17959
18267
  });
17960
18268
  return true;
17961
18269
  } catch (error) {
17962
- const isRevert = error instanceof import_viem20.BaseError && error.walk((e) => e instanceof import_viem20.ContractFunctionRevertedError || e instanceof import_viem20.ContractFunctionZeroDataError);
18270
+ const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
17963
18271
  if (isRevert) return false;
17964
18272
  throw error;
17965
18273
  }
@@ -18066,6 +18374,10 @@ function describeShortfall(balances) {
18066
18374
  // src/services/ContractInteractionService.ts
18067
18375
  import_decimal2.Decimal.config({ precision: 28, rounding: 4 });
18068
18376
  var FUNDING_GAS_PER_CALL = 400000n;
18377
+ var DEBIT_ORDER_GAS = 60000n;
18378
+ function settles(budget) {
18379
+ return !!budget && budget.token.toLowerCase() !== import_sdk7.ADDRESS_ZERO;
18380
+ }
18069
18381
  var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
18070
18382
  var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
18071
18383
  var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
@@ -18226,7 +18538,10 @@ var ContractInteractionService = class {
18226
18538
  order,
18227
18539
  prependCalls: void 0,
18228
18540
  maxPriorityFeePerGasBumpPercent: gasFeeBumpConfig?.maxPriorityFeePerGasBumpPercent,
18229
- maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent
18541
+ maxFeePerGasBumpPercent: gasFeeBumpConfig?.maxFeePerGasBumpPercent,
18542
+ // No bid without the bundler's own figures: an op signed with the SDK's
18543
+ // fixed fallback limits does not execute, so a failed estimate skips the order.
18544
+ requireBundlerEstimate: true
18230
18545
  });
18231
18546
  this.logger.info({ orderId: order.id }, "Caching gas estimate");
18232
18547
  this.logger.info({ estimate }, "Estimate");
@@ -18249,23 +18564,25 @@ var ContractInteractionService = class {
18249
18564
  callGasLimit: estimate.callGasLimit
18250
18565
  };
18251
18566
  } catch (error) {
18252
- this.logger.error({ err: error }, "Error estimating gas, using generous fallback values");
18567
+ this.logger.error({ err: error, orderId: order.id }, "Error estimating gas, not bidding on this order");
18253
18568
  throw new Error(`Failed to estimate gas: ${error instanceof Error ? error.message : "Unknown error"}`);
18254
18569
  }
18255
18570
  }
18256
18571
  /**
18257
18572
  * The call gas a bid needs: the shared estimate plus room for the funding calls
18258
- * this bid prepends.
18573
+ * this bid prepends and for settling its limit order's budget.
18259
18574
  *
18260
18575
  * Funding calls are not simulated (the V4 PositionManager's flash accounting does
18261
18576
  * not resolve in the bundler's estimation context), so each one adds a fixed
18262
18577
  * allowance for its modifyLiquidities + take. They differ between the bids on one
18263
18578
  * order, since each draws on its own limit order and so on its own funding, which
18264
18579
  * is why this is worked out per bid rather than baked into the cached estimate.
18580
+ * The estimate is of a lone fillOrder, so the budget call is not in it either.
18265
18581
  */
18266
- callGasLimitFor(order, baseCallGasLimit) {
18582
+ callGasLimitFor(order, baseCallGasLimit, budget) {
18267
18583
  const funding = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
18268
- return baseCallGasLimit + FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
18584
+ const fundingGas = FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
18585
+ return baseCallGasLimit + fundingGas + (settles(budget) ? DEBIT_ORDER_GAS : 0n);
18269
18586
  }
18270
18587
  /**
18271
18588
  * Gets the fee token address and decimals for a given chain.
@@ -18337,7 +18654,7 @@ var ContractInteractionService = class {
18337
18654
  this.logger.warn(
18338
18655
  {
18339
18656
  chain,
18340
- walletBalance: (0, import_viem21.formatEther)(solverBalance),
18657
+ walletBalance: (0, import_viem23.formatEther)(solverBalance),
18341
18658
  walletGasUnits: walletGasUnits.toString(),
18342
18659
  gasPrice: gasPrice.toString()
18343
18660
  },
@@ -18352,10 +18669,10 @@ var ContractInteractionService = class {
18352
18669
  this.logger.info(
18353
18670
  {
18354
18671
  chain,
18355
- currentDeposit: (0, import_viem21.formatEther)(currentDeposit),
18672
+ currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
18356
18673
  depositGasUnits: depositGasUnits.toString(),
18357
18674
  targetGasUnits: targetGasUnits.toString(),
18358
- walletBalance: (0, import_viem21.formatEther)(solverBalance)
18675
+ walletBalance: (0, import_viem23.formatEther)(solverBalance)
18359
18676
  },
18360
18677
  "EntryPoint deposit covers target gas units, no top-up needed"
18361
18678
  );
@@ -18366,8 +18683,8 @@ var ContractInteractionService = class {
18366
18683
  this.logger.warn(
18367
18684
  {
18368
18685
  chain,
18369
- deficit: (0, import_viem21.formatEther)(deficit),
18370
- solverBalance: (0, import_viem21.formatEther)(solverBalance),
18686
+ deficit: (0, import_viem23.formatEther)(deficit),
18687
+ solverBalance: (0, import_viem23.formatEther)(solverBalance),
18371
18688
  depositGasUnits: depositGasUnits.toString(),
18372
18689
  targetGasUnits: targetGasUnits.toString()
18373
18690
  },
@@ -18379,10 +18696,10 @@ var ContractInteractionService = class {
18379
18696
  this.logger.info(
18380
18697
  {
18381
18698
  chain,
18382
- currentDeposit: (0, import_viem21.formatEther)(currentDeposit),
18699
+ currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
18383
18700
  depositGasUnits: depositGasUnits.toString(),
18384
18701
  targetGasUnits: targetGasUnits.toString(),
18385
- topUpAmount: (0, import_viem21.formatEther)(deficit)
18702
+ topUpAmount: (0, import_viem23.formatEther)(deficit)
18386
18703
  },
18387
18704
  "Topping up EntryPoint deposit to cover target gas units"
18388
18705
  );
@@ -18405,7 +18722,7 @@ var ContractInteractionService = class {
18405
18722
  const addr = tokenAddress.toLowerCase();
18406
18723
  if (addr !== sourceUsdc && addr !== sourceUsdt) continue;
18407
18724
  const decimals = await this.getTokenDecimals(tokenAddress, order.source);
18408
- const tokenAmount = new import_decimal2.Decimal((0, import_viem21.formatUnits)(input.amount, decimals));
18725
+ const tokenAmount = new import_decimal2.Decimal((0, import_viem23.formatUnits)(input.amount, decimals));
18409
18726
  inputUsdValue = inputUsdValue.plus(tokenAmount);
18410
18727
  }
18411
18728
  return inputUsdValue;
@@ -18447,6 +18764,27 @@ var ContractInteractionService = class {
18447
18764
  { maxRetries: 3, backoffMs: 250, logMessage: "Failed to read token balance" }
18448
18765
  );
18449
18766
  }
18767
+ /**
18768
+ * What the solver's account has tallied against a limit order's budget on `chain`, in
18769
+ * the paid token's own units, or null when it cannot be read.
18770
+ *
18771
+ * Null rather than a throw: an account whose implementation keeps no tally answers
18772
+ * with a revert or with no data, and the caller carries on without the figure either
18773
+ * way. Not retried, since that answer does not change on a second ask.
18774
+ */
18775
+ async limitOrderSpent(chain, budgetId) {
18776
+ try {
18777
+ return await this.clientManager.getPublicClient(chain).readContract({
18778
+ address: this.solverAccountAddress,
18779
+ abi: SOLVER_ACCOUNT_ABI,
18780
+ functionName: "spent",
18781
+ args: [budgetId]
18782
+ });
18783
+ } catch (err) {
18784
+ this.logger.warn({ err, chain, budgetId }, "Could not read the limit order's tally from the solver account");
18785
+ return null;
18786
+ }
18787
+ }
18450
18788
  async getSolverEntryPointBalance(chain) {
18451
18789
  const entryPointAddress = this.configService.getEntryPointAddress(chain);
18452
18790
  if (!entryPointAddress) {
@@ -18475,7 +18813,7 @@ var ContractInteractionService = class {
18475
18813
  const walletClient = this.clientManager.getWalletClient(chain);
18476
18814
  const publicClient = this.clientManager.getPublicClient(chain);
18477
18815
  this.logger.info(
18478
- { chain, solver: this.solverAccountAddress, amount: (0, import_viem21.formatEther)(amount) },
18816
+ { chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(amount) },
18479
18817
  "Depositing to EntryPoint"
18480
18818
  );
18481
18819
  const hash = await walletClient.writeContract({
@@ -18490,7 +18828,7 @@ var ContractInteractionService = class {
18490
18828
  if (receipt.status !== "success") {
18491
18829
  throw new Error(`EntryPoint deposit transaction reverted: ${hash}`);
18492
18830
  }
18493
- this.logger.info({ chain, txHash: hash, amount: (0, import_viem21.formatEther)(amount) }, "EntryPoint deposit confirmed");
18831
+ this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(amount) }, "EntryPoint deposit confirmed");
18494
18832
  return hash;
18495
18833
  }
18496
18834
  /**
@@ -18510,7 +18848,7 @@ var ContractInteractionService = class {
18510
18848
  const walletClient = this.clientManager.getWalletClient(chain);
18511
18849
  const publicClient = this.clientManager.getPublicClient(chain);
18512
18850
  this.logger.info(
18513
- { chain, solver: this.solverAccountAddress, amount: (0, import_viem21.formatEther)(balance) },
18851
+ { chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(balance) },
18514
18852
  "Withdrawing from EntryPoint"
18515
18853
  );
18516
18854
  const hash = await walletClient.writeContract({
@@ -18524,7 +18862,7 @@ var ContractInteractionService = class {
18524
18862
  if (receipt.status !== "success") {
18525
18863
  throw new Error(`EntryPoint withdrawal transaction reverted: ${hash}`);
18526
18864
  }
18527
- this.logger.info({ chain, txHash: hash, amount: (0, import_viem21.formatEther)(balance) }, "EntryPoint withdrawal confirmed");
18865
+ this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(balance) }, "EntryPoint withdrawal confirmed");
18528
18866
  return hash;
18529
18867
  }
18530
18868
  /**
@@ -18587,7 +18925,8 @@ var ContractInteractionService = class {
18587
18925
  if (!cachedEstimate) {
18588
18926
  throw new Error(`No cached gas estimate found for order ${order.id}. Call estimateGasFillPost first.`);
18589
18927
  }
18590
- const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit);
18928
+ const budget = this.cacheService.getBidBudget(order.id);
18929
+ const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit, budget);
18591
18930
  const cachedFillerOutputs = this.cacheService.getFillerOutputs(order.id);
18592
18931
  const cachedFillerInputs = this.cacheService.getFillerInputs(order.id);
18593
18932
  if (!cachedFillerOutputs || !cachedFillerInputs) {
@@ -18614,7 +18953,8 @@ var ContractInteractionService = class {
18614
18953
  order,
18615
18954
  cachedFillerOutputs,
18616
18955
  fillOptions,
18617
- cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount
18956
+ cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount,
18957
+ budget ? { budget, solverAccount: solverAccountAddress } : void 0
18618
18958
  );
18619
18959
  const commitment = (0, import_sdk7.orderCommitment)(order);
18620
18960
  const nonce = await this.clientManager.getPublicClient(order.destination).readContract({
@@ -18760,7 +19100,7 @@ var ContractInteractionService = class {
18760
19100
  {
18761
19101
  target: outputToken,
18762
19102
  value: 0n,
18763
- data: (0, import_viem21.encodeFunctionData)({
19103
+ data: (0, import_viem23.encodeFunctionData)({
18764
19104
  abi: ERC20_ABI,
18765
19105
  functionName: "approve",
18766
19106
  args: [gateway, outputAmount]
@@ -18806,8 +19146,13 @@ var ContractInteractionService = class {
18806
19146
  * Same-chain fills release escrow locally with no Hyperbridge dispatch, so the
18807
19147
  * gateway never pulls the fee token — its approval is skipped. Only cross-chain
18808
19148
  * fills, which dispatch a RedeemEscrow message paid in the fee token, need it.
19149
+ *
19150
+ * A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
19151
+ * as its `orderId`, works out what the fill paid from what is left of the allowance
19152
+ * and reverts the whole batch if the order's payouts would pass its size. It has to
19153
+ * come last, after the gateway has drawn on the allowance.
18809
19154
  */
18810
- async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount) {
19155
+ async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount, settlement) {
18811
19156
  const chain = order.destination;
18812
19157
  const intentGatewayV2Address = this.configService.getIntentGatewayAddress(chain);
18813
19158
  const perTokenRequired = /* @__PURE__ */ new Map();
@@ -18817,9 +19162,10 @@ var ContractInteractionService = class {
18817
19162
  const key = addr.toLowerCase();
18818
19163
  perTokenRequired.set(key, (perTokenRequired.get(key) ?? 0n) + output.amount);
18819
19164
  }
19165
+ let feeKey;
18820
19166
  if (order.source !== order.destination) {
18821
19167
  const feeToken = await this.getFeeTokenWithDecimals(chain);
18822
- const feeKey = feeToken.address.toLowerCase();
19168
+ feeKey = feeToken.address.toLowerCase();
18823
19169
  perTokenRequired.set(feeKey, (perTokenRequired.get(feeKey) ?? 0n) + requiredFeeTokenAmount);
18824
19170
  }
18825
19171
  const fundingPrepends = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
@@ -18831,7 +19177,7 @@ var ContractInteractionService = class {
18831
19177
  calls.push({
18832
19178
  target: tokenAddress,
18833
19179
  value: 0n,
18834
- data: (0, import_viem21.encodeFunctionData)({
19180
+ data: (0, import_viem23.encodeFunctionData)({
18835
19181
  abi: ERC20_ABI,
18836
19182
  functionName: "approve",
18837
19183
  args: [intentGatewayV2Address, amount]
@@ -18845,17 +19191,37 @@ var ContractInteractionService = class {
18845
19191
  value: nativeOutputValue,
18846
19192
  data: (0, import_sdk7.encodeFillOrder)((0, import_sdk7.transformOrderForContract)(order), fillOptions)
18847
19193
  });
19194
+ if (settlement && settles(settlement.budget)) {
19195
+ const { budget, solverAccount } = settlement;
19196
+ const budgetToken = budget.token.toLowerCase();
19197
+ const approved = perTokenRequired.get(budgetToken) ?? 0n;
19198
+ if (approved === 0n) {
19199
+ throw new Error(
19200
+ `Bid on order ${order.id} approves nothing in ${budget.token}, the token budget ${budget.budgetId} is measured in`
19201
+ );
19202
+ }
19203
+ const fee = feeKey === budgetToken ? fillOptions.relayerFee : 0n;
19204
+ calls.push({
19205
+ target: solverAccount,
19206
+ value: 0n,
19207
+ data: (0, import_viem23.encodeFunctionData)({
19208
+ abi: SOLVER_ACCOUNT_ABI,
19209
+ functionName: "debitOrder",
19210
+ args: [budget.budgetId, budget.cap, budgetToken, approved, fee]
19211
+ })
19212
+ });
19213
+ }
18848
19214
  return (0, import_sdk7.encodeERC7821ExecuteBatch)(calls);
18849
19215
  }
18850
19216
  };
18851
19217
 
18852
19218
  // src/services/DelegationService.ts
18853
- var import_viem23 = require("viem");
19219
+ var import_viem25 = require("viem");
18854
19220
  var import_sdk9 = require("@hyperbridge/sdk");
18855
19221
 
18856
19222
  // src/services/UserOpSender.ts
18857
19223
  var import_sdk8 = require("@hyperbridge/sdk");
18858
- var import_viem22 = require("viem");
19224
+ var import_viem24 = require("viem");
18859
19225
  var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
18860
19226
  var FALLBACK_CALL_GAS_LIMIT = 1500000n;
18861
19227
  var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
@@ -18928,11 +19294,11 @@ var UserOpSender = class {
18928
19294
  });
18929
19295
  const formattedAuth = auth ? {
18930
19296
  address: auth.address,
18931
- chainId: (0, import_viem22.toHex)(auth.chainId),
18932
- nonce: (0, import_viem22.toHex)(auth.nonce),
19297
+ chainId: (0, import_viem24.toHex)(auth.chainId),
19298
+ nonce: (0, import_viem24.toHex)(auth.nonce),
18933
19299
  r: auth.r,
18934
19300
  s: auth.s,
18935
- yParity: (0, import_viem22.toHex)(auth.yParity)
19301
+ yParity: (0, import_viem24.toHex)(auth.yParity)
18936
19302
  } : void 0;
18937
19303
  const { verificationGasLimit, callGasLimit, preVerificationGas } = gas ?? await this.estimateGas({
18938
19304
  bundlerUrl,
@@ -19100,10 +19466,10 @@ function bootstrapCallData(approval) {
19100
19466
  {
19101
19467
  target: approval.token,
19102
19468
  value: 0n,
19103
- data: (0, import_viem23.encodeFunctionData)({
19104
- abi: import_viem23.erc20Abi,
19469
+ data: (0, import_viem25.encodeFunctionData)({
19470
+ abi: import_viem25.erc20Abi,
19105
19471
  functionName: "approve",
19106
- args: [approval.spender, import_viem23.maxUint256]
19472
+ args: [approval.spender, import_viem25.maxUint256]
19107
19473
  })
19108
19474
  }
19109
19475
  ]);
@@ -19154,10 +19520,10 @@ var DelegationService = class {
19154
19520
  return await walletClient.sendTransaction({
19155
19521
  to: approval.token,
19156
19522
  value: 0n,
19157
- data: (0, import_viem23.encodeFunctionData)({
19158
- abi: import_viem23.erc20Abi,
19523
+ data: (0, import_viem25.encodeFunctionData)({
19524
+ abi: import_viem25.erc20Abi,
19159
19525
  functionName: "approve",
19160
- args: [approval.spender, import_viem23.maxUint256]
19526
+ args: [approval.spender, import_viem25.maxUint256]
19161
19527
  }),
19162
19528
  authorizationList: [authorization],
19163
19529
  chain: walletClient.chain,
@@ -19391,8 +19757,8 @@ var DelegationService = class {
19391
19757
  {
19392
19758
  chain,
19393
19759
  authority,
19394
- nativeBalance: (0, import_viem23.formatEther)(native.nativeBalance),
19395
- requiredNative: (0, import_viem23.formatEther)(native.requiredNative)
19760
+ nativeBalance: (0, import_viem25.formatEther)(native.nativeBalance),
19761
+ requiredNative: (0, import_viem25.formatEther)(native.requiredNative)
19396
19762
  },
19397
19763
  "Delegation failed: insufficient native balance for direct EIP-7702 tx and paymaster path unavailable"
19398
19764
  );
@@ -19467,7 +19833,7 @@ var DelegationService = class {
19467
19833
  { chain, authority: this.signer.address, mode: this.signer.mode ?? "custom" },
19468
19834
  "Revoking EIP-7702 delegation"
19469
19835
  );
19470
- const authorization = await this.buildAuthorization(chain, import_viem23.zeroAddress);
19836
+ const authorization = await this.buildAuthorization(chain, import_viem25.zeroAddress);
19471
19837
  const hash = await this.sendDelegationTransaction(chain, authorization);
19472
19838
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
19473
19839
  if (receipt.status === "success") {
@@ -19483,13 +19849,13 @@ var DelegationService = class {
19483
19849
  };
19484
19850
 
19485
19851
  // src/services/RebalancingService.ts
19486
- var import_viem26 = require("viem");
19852
+ var import_viem28 = require("viem");
19487
19853
  var import_sdk13 = require("@hyperbridge/sdk");
19488
19854
  var import_decimal3 = require("decimal.js");
19489
19855
 
19490
19856
  // src/services/rebalancers/binance.ts
19491
19857
  var import_wallet2 = require("@binance/wallet");
19492
- var import_viem24 = require("viem");
19858
+ var import_viem26 = require("viem");
19493
19859
  var import_sdk10 = require("@hyperbridge/sdk");
19494
19860
  var DEPOSIT_STATUS_SUCCESS = 1;
19495
19861
  var WITHDRAW_STATUS_COMPLETED = 6;
@@ -19801,7 +20167,7 @@ var BinanceRebalancer = class {
19801
20167
  abi: ERC20_ABI,
19802
20168
  functionName: "decimals"
19803
20169
  });
19804
- const amountWei = (0, import_viem24.parseUnits)(amount, Number(tokenDecimals));
20170
+ const amountWei = (0, import_viem26.parseUnits)(amount, Number(tokenDecimals));
19805
20171
  const balance = await publicClient.readContract({
19806
20172
  address: tokenAddress,
19807
20173
  abi: ERC20_ABI,
@@ -20025,7 +20391,7 @@ var CctpRebalancer = class {
20025
20391
  };
20026
20392
 
20027
20393
  // src/services/rebalancers/usdt0.ts
20028
- var import_viem25 = require("viem");
20394
+ var import_viem27 = require("viem");
20029
20395
  var import_sdk12 = require("@hyperbridge/sdk");
20030
20396
 
20031
20397
  // src/config/abis/Oft.ts
@@ -20188,7 +20554,7 @@ var Usdt0Rebalancer = class {
20188
20554
  const publicClient = this.chainClientManager.getPublicClient(source);
20189
20555
  const walletClient = this.chainClientManager.getWalletClient(source);
20190
20556
  const recipient = recipientAddress || this.account.address;
20191
- const amountWei = (0, import_viem25.parseUnits)(amount, 6);
20557
+ const amountWei = (0, import_viem27.parseUnits)(amount, 6);
20192
20558
  if (sourceChainId === 1) {
20193
20559
  const allowance = await publicClient.readContract({
20194
20560
  address: tokenAddress,
@@ -20201,7 +20567,7 @@ var Usdt0Rebalancer = class {
20201
20567
  address: tokenAddress,
20202
20568
  abi: ERC20_ABI,
20203
20569
  functionName: "approve",
20204
- args: [oftAddress, import_viem25.maxUint256],
20570
+ args: [oftAddress, import_viem27.maxUint256],
20205
20571
  chain: walletClient.chain
20206
20572
  });
20207
20573
  await publicClient.waitForTransactionReceipt({ hash: approveTx, confirmations: 1 });
@@ -20283,8 +20649,8 @@ var Usdt0Rebalancer = class {
20283
20649
  if (!oftAddress) throw new Error(`Chain ${source} not supported by USDT0`);
20284
20650
  const publicClient = this.chainClientManager.getPublicClient(source);
20285
20651
  const recipient = recipientAddress || this.account.address;
20286
- const amountWei = (0, import_viem25.parseUnits)(amount, 6);
20287
- const recipientBytes32 = (0, import_viem25.padHex)(recipient, { size: 32 });
20652
+ const amountWei = (0, import_viem27.parseUnits)(amount, 6);
20653
+ const recipientBytes32 = (0, import_viem27.padHex)(recipient, { size: 32 });
20288
20654
  const sendParam = {
20289
20655
  dstEid: destEid,
20290
20656
  to: recipientBytes32,
@@ -20636,7 +21002,7 @@ var RebalancingService = class {
20636
21002
  functionName: "balanceOf",
20637
21003
  args: [this.walletAddress]
20638
21004
  });
20639
- const balanceDecimal = new import_decimal3.Decimal((0, import_viem26.formatUnits)(balanceWei, Number(decimals)));
21005
+ const balanceDecimal = new import_decimal3.Decimal((0, import_viem28.formatUnits)(balanceWei, Number(decimals)));
20640
21006
  return balanceDecimal.toNumber();
20641
21007
  } catch (error) {
20642
21008
  this.logger.error({ error, chain, asset }, "Failed to get current balance");
@@ -20684,11 +21050,11 @@ function byBestRate(a, b) {
20684
21050
  return left === right ? 0 : left > right ? -1 : 1;
20685
21051
  }
20686
21052
  function chargedFor(hold, delivered, released) {
20687
- if (hold.take === void 0 || released === 0n) return delivered;
20688
- const take = BigInt(hold.take);
20689
- if (take === 0n) return delivered;
20690
- const atRate = (BigInt(hold.amount) * released + take - 1n) / take;
20691
- return atRate > delivered ? atRate : delivered;
21053
+ const take = hold.take === void 0 ? 0n : BigInt(hold.take);
21054
+ if (take === 0n || released === 0n) return toScaledUp(delivered.raw, delivered.decimals);
21055
+ const offered = toRaw(BigInt(hold.amount), delivered.decimals);
21056
+ const atRate = (offered * released + take - 1n) / take;
21057
+ return toScaledUp(atRate > delivered.raw ? atRate : delivered.raw, delivered.decimals);
20692
21058
  }
20693
21059
  var IntentFiller = class {
20694
21060
  constructor(chainConfigs, strategies, config, configService, chainClientManager, contractService, signer, scanners, rebalancingService, bidStorage, limitOrders, assetRegistry) {
@@ -20765,6 +21131,17 @@ var IntentFiller = class {
20765
21131
  setLimitOrderService(service) {
20766
21132
  this.limitOrderService = service;
20767
21133
  }
21134
+ /**
21135
+ * Brings the limit orders' `remaining` in line with their on-chain tallies.
21136
+ *
21137
+ * On the settlement queue, so the pass and a fill never resize the same order at
21138
+ * once. Resolves when the pass has run.
21139
+ */
21140
+ async reconcileLimitOrderTallies() {
21141
+ await this.settlementQueue.add(async () => {
21142
+ await this.limitOrderService?.reconcileTallies();
21143
+ });
21144
+ }
20768
21145
  /** Whether the given chain id is configured for watch-only (monitor, never fill). */
20769
21146
  isChainWatchOnly(chainId) {
20770
21147
  const watchOnly = this.config.watchOnly;
@@ -21342,6 +21719,23 @@ var IntentFiller = class {
21342
21719
  this.monitor.emit("orderSkipped", { orderId: order.id, reason: "watch-only" });
21343
21720
  return null;
21344
21721
  }
21722
+ const destinationHead = await this.destinationHead(order);
21723
+ if (destinationHead !== null && order.deadline <= destinationHead) {
21724
+ this.logger.info(
21725
+ {
21726
+ orderId: order.id,
21727
+ destChain: order.destination,
21728
+ deadline: order.deadline.toString(),
21729
+ head: destinationHead.toString()
21730
+ },
21731
+ "Skipping order: its deadline has passed on the destination chain"
21732
+ );
21733
+ this.monitor.emit("orderSkipped", {
21734
+ orderId: order.id,
21735
+ reason: "Order deadline has passed on the destination chain"
21736
+ });
21737
+ return null;
21738
+ }
21345
21739
  const evalStartMs = Date.now();
21346
21740
  const eligibleStrategies = await Promise.all(
21347
21741
  this.strategies.map(async (strategy) => {
@@ -21351,7 +21745,10 @@ var IntentFiller = class {
21351
21745
  })
21352
21746
  );
21353
21747
  const fillsPartially = this.fillsPartially(order);
21354
- const validStrategies = eligibleStrategies.filter((s) => s !== null && (s.profitability > 0 || fillsPartially)).sort((a, b) => b.profitability - a.profitability);
21748
+ const feeCheckWaived = this.feeCheckWaived(order);
21749
+ const validStrategies = eligibleStrategies.filter(
21750
+ (s) => s !== null && (s.profitability > 0 || fillsPartially || feeCheckWaived)
21751
+ ).sort((a, b) => b.profitability - a.profitability);
21355
21752
  const evalDurationSec = (Date.now() - evalStartMs) / 1e3;
21356
21753
  this.monitor.emit("orderTiming", { orderId: order.id, phase: "evaluation", durationSec: evalDurationSec });
21357
21754
  if (validStrategies.length === 0) {
@@ -21376,6 +21773,23 @@ var IntentFiller = class {
21376
21773
  );
21377
21774
  return validStrategies[0];
21378
21775
  }
21776
+ /**
21777
+ * The destination chain's latest block number, the clock `order.deadline` is read on.
21778
+ *
21779
+ * Null when the read fails. That is not an answer about the order, so evaluation goes
21780
+ * on: the gas estimate simulates the same deadline check and refuses an expired order.
21781
+ */
21782
+ async destinationHead(order) {
21783
+ try {
21784
+ return await this.chainClientManager.getPublicClient(order.destination).getBlockNumber();
21785
+ } catch (err) {
21786
+ this.logger.warn(
21787
+ { orderId: order.id, destChain: order.destination, err },
21788
+ "Could not read the destination head to check the order's deadline"
21789
+ );
21790
+ return null;
21791
+ }
21792
+ }
21379
21793
  /**
21380
21794
  * Whether the strategy's evaluation concluded in a deliberate partial fill.
21381
21795
  *
@@ -21389,6 +21803,15 @@ var IntentFiller = class {
21389
21803
  if (!order.id) return false;
21390
21804
  return this.contractService.cacheService.isPartialFill(order.id);
21391
21805
  }
21806
+ /**
21807
+ * Whether the strategy's evaluation let a bid through without the fee check, because
21808
+ * the order is at or above the operator's minimum size. Read from a flag the strategy
21809
+ * sets only once it has an answer, as {@link fillsPartially} is.
21810
+ */
21811
+ feeCheckWaived(order) {
21812
+ if (!order.id) return false;
21813
+ return this.contractService.cacheService.isFeeCheckWaived(order.id);
21814
+ }
21392
21815
  executeOrder(order, bestStrategy, solverSelectionActive, inputUsdValue, profitUsd) {
21393
21816
  const chainQueue = this.chainQueues.get((0, import_sdk14.getChainId)(order.destination));
21394
21817
  if (!chainQueue) {
@@ -21410,7 +21833,7 @@ var IntentFiller = class {
21410
21833
  let reservation = [];
21411
21834
  if (plan) {
21412
21835
  if (order.id) {
21413
- cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs);
21836
+ cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs, plan.budget);
21414
21837
  cache.setPartialFill(order.id, plan.partialFill);
21415
21838
  if (plan.fundingCalls.length > 0) cache.setFundingPrepends(order.id, plan.fundingCalls);
21416
21839
  else cache.clearFundingPrepends(order.id);
@@ -21611,7 +22034,7 @@ var IntentFiller = class {
21611
22034
  await this.releaseAll(claimed);
21612
22035
  return;
21613
22036
  }
21614
- const settled = executedHold(claimed, delivered, released);
22037
+ const settled = executedHold(claimed, delivered.scaled, released);
21615
22038
  const charged = chargedFor(settled, delivered, released);
21616
22039
  const settledBid = rows.find(
21617
22040
  (row) => row.reservations.some(
@@ -21664,15 +22087,15 @@ var IntentFiller = class {
21664
22087
  if (delivered === null) return [];
21665
22088
  const settled = executedHold(
21666
22089
  holds.map((entry) => entry.hold),
21667
- delivered,
22090
+ delivered.scaled,
21668
22091
  released
21669
22092
  );
21670
22093
  const row = holds.find((entry) => entry.hold === settled).row;
21671
22094
  return this.bidStorage.claimReservation(commitment, row.bid ?? void 0);
21672
22095
  }
21673
22096
  /**
21674
- * What this fill delivered of the token the limit order pays, at 1e18, or null
21675
- * when the fill names none of it.
22097
+ * What this fill delivered of the token the limit order pays, or null when the
22098
+ * fill names none of it.
21676
22099
  */
21677
22100
  async deliveredAgainst(limitOrderId, chainId, outputs) {
21678
22101
  const order = await this.limitOrders?.get(limitOrderId);
@@ -21688,7 +22111,7 @@ var IntentFiller = class {
21688
22111
  }
21689
22112
  if (total === 0n) return null;
21690
22113
  const decimals = await this.contractService.getTokenDecimals(address, chain);
21691
- return toScaled(total, decimals);
22114
+ return { scaled: toScaled(total, decimals), raw: total, decimals };
21692
22115
  }
21693
22116
  /**
21694
22117
  * Gives back whatever this bid still holds against its limit order.
@@ -21778,90 +22201,9 @@ var IntentFiller = class {
21778
22201
 
21779
22202
  // src/strategies/fx.ts
21780
22203
  var import_sdk15 = require("@hyperbridge/sdk");
21781
- var import_viem27 = require("viem");
22204
+ var import_viem29 = require("viem");
21782
22205
  var import_decimal6 = require("decimal.js");
21783
22206
 
21784
- // src/orderbook/matching.ts
21785
- function limitOrderLegs(order) {
21786
- return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
21787
- }
21788
- function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
21789
- const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
21790
- const offer = offerFor({
21791
- side: order.side,
21792
- inputAmount: incoming.inputNet,
21793
- price: BigInt(order.price),
21794
- outputDecimals: incoming.outputDecimals
21795
- });
21796
- const available = BigInt(order.remaining);
21797
- return { order, offer, available, payout: offer < available ? offer : available };
21798
- }).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
21799
- return qualifying;
21800
- }
21801
- function byBestOfferFirst(a, b) {
21802
- if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
21803
- if (a.available !== b.available) return a.available > b.available ? -1 : 1;
21804
- return a.order.id < b.order.id ? -1 : 1;
21805
- }
21806
- function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
21807
- let furthest = -1;
21808
- for (const order of orders) {
21809
- const failed = firstFailed(order, incoming, resolve, now);
21810
- furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
21811
- }
21812
- const { source, destination, inputSymbol } = incoming;
21813
- if (furthest < CHECKS.length) {
21814
- switch (CHECKS[Math.max(furthest, 0)]) {
21815
- case "open":
21816
- return "no limit order is open";
21817
- case "chain":
21818
- return `no open limit order fills on ${destination}`;
21819
- case "input":
21820
- return `no limit order on ${destination} takes ${inputSymbol} in`;
21821
- case "output":
21822
- return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
21823
- case "source":
21824
- return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
21825
- }
21826
- }
21827
- const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
21828
- order,
21829
- offer: offerFor({
21830
- side: order.side,
21831
- inputAmount: incoming.inputNet,
21832
- price: BigInt(order.price),
21833
- outputDecimals: incoming.outputDecimals
21834
- })
21835
- }));
21836
- if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
21837
- return "the limit orders that meet its rate have nothing left to pay out";
21838
- }
21839
- const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
21840
- const symbol = limitOrderLegs(best.order).output;
21841
- return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
21842
- }
21843
- var CHECKS = ["open", "chain", "input", "output", "source"];
21844
- function serves(order, incoming, resolve, now) {
21845
- return firstFailed(order, incoming, resolve, now) === null;
21846
- }
21847
- function firstFailed(order, incoming, resolve, now) {
21848
- if (order.status !== "open") return "open";
21849
- if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
21850
- if (order.fillChain !== incoming.destination) return "chain";
21851
- const legs = limitOrderLegs(order);
21852
- if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
21853
- const outputToken = resolve(legs.output, order.fillChain);
21854
- if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
21855
- if (incoming.source === incoming.destination) return null;
21856
- return order.acceptedSources.includes(incoming.source) ? null : "source";
21857
- }
21858
- function readable(amount) {
21859
- const [whole, fraction] = toHuman(amount).split(".");
21860
- const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
21861
- const cut = fraction?.slice(0, 6).replace(/0+$/, "");
21862
- return cut ? `${grouped}.${cut}` : grouped;
21863
- }
21864
-
21865
22207
  // src/orderbook/usd.ts
21866
22208
  var import_decimal5 = require("decimal.js");
21867
22209
  function limitOrderUsdEdges(orders) {
@@ -21916,9 +22258,13 @@ var FXFiller = class _FXFiller {
21916
22258
  */
21917
22259
  constructor(signer, configService, clientManager, contractService, pairs, registry, options) {
21918
22260
  this.name = "FXFiller";
21919
- /** Consecutive orders where overfill clamp activated. */
22261
+ /**
22262
+ * Consecutive orders where the overfill clamp activated. Dormant: the clamp
22263
+ * existed for venue-priced legs, which went with Uniswap V4 pricing, so every
22264
+ * outcome is now recorded unclamped and this never climbs. See `recordOrderOutcome`.
22265
+ */
21920
22266
  this.consecutiveClamps = 0;
21921
- /** Once set, the filler refuses all orders until restart — systemic pricing error suspected. */
22267
+ /** Once set, the filler refuses all orders until reset. Unreachable while the clamp is dormant. */
21922
22268
  this.halted = false;
21923
22269
  this.logger = moduleLogger(configService.loggers, "fx-simplex");
21924
22270
  const { confirmationPolicy, fundingVenues = [], limitOrders } = options ?? {};
@@ -21967,10 +22313,9 @@ var FXFiller = class _FXFiller {
21967
22313
  this.pairs.push(pair);
21968
22314
  }
21969
22315
  /**
21970
- * Removes a market from the running engine. All-or-nothing: at least one
21971
- * market must remain and no other pair may lose its USD anchor (removing a
21972
- * reference feed that anchors a dependent market is rejected). In-flight
21973
- * rechecks of orders on the removed pair simply stop matching.
22316
+ * Removes a market from the running engine. At least one market must remain.
22317
+ * Matching reads the limit orders rather than the pairs, so removing a market
22318
+ * changes what the dashboard lists, not what the filler fills.
21974
22319
  */
21975
22320
  removePair(pair) {
21976
22321
  const index = this.pairs.indexOf(pair);
@@ -22026,25 +22371,25 @@ var FXFiller = class _FXFiller {
22026
22371
  }
22027
22372
  }
22028
22373
  /**
22029
- * Evaluates whether an order is profitable to fill under the per-pair
22030
- * `maxOrderSize` caps (where set) and the filler's current token balances.
22374
+ * Evaluates whether an order is profitable to fill against the operator's
22375
+ * limit orders and the filler's current token balances.
22031
22376
  *
22032
22377
  * High-level flow:
22033
- * - Resolve each (input, output) leg to a configured pair and direction.
22034
- * - Estimate each pair's total token0 notional in the order and cap it at
22035
- * the pair's `maxOrderSize` when one is set; pair curves are evaluated at
22036
- * that (possibly uncapped) notional.
22037
- * - Walk the legs, allocating from each pair's capped token0 budget and
22038
- * pricing outputs at the pair's rate.
22039
- * - Further cap each leg by the filler's current token balance plus
22040
- * funding-venue withdrawals.
22041
- * - Cache the resulting outputs for later use in `executeOrder`.
22042
- *
22043
- * Note: we may intentionally overfill relative to the user's requested
22044
- * outputs if the pair pricing makes that attractive. This is how we stay competitive.
22378
+ * - Match each (input, output) leg against the open limit orders.
22379
+ * - Build one bid per matching order, best offer first, each paying the
22380
+ * order's whole offer for the input at its own rate, capped by what the
22381
+ * order has left. The gateway credits the swapper the ask and splits
22382
+ * anything above it between the swapper and the protocol.
22383
+ * - Treat a payout below the ask as a partial fill, which only orders
22384
+ * without output calldata and without an earlier partial fill allow.
22385
+ * - Cap each payout by the filler's balance plus funding-venue withdrawals,
22386
+ * then gate it on fees (full fills of orders below `minOrderSizeUsd`) and the
22387
+ * same-asset spread.
22388
+ * - Cache the resulting bids for later use in `executeOrder`.
22045
22389
  */
22046
22390
  async calculateProfitability(order) {
22047
22391
  if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
22392
+ if (order.id) this.contractService.cacheService.clearFeeCheckWaived(order.id);
22048
22393
  if (this.halted) {
22049
22394
  this.logger.warn({ orderId: order.id }, "FXFiller halted \u2014 rejecting order");
22050
22395
  return 0;
@@ -22059,6 +22404,16 @@ var FXFiller = class _FXFiller {
22059
22404
  const multiLeg = order.inputs.length > 1;
22060
22405
  const plans = [];
22061
22406
  const plannedOn = /* @__PURE__ */ new Map();
22407
+ const spentOn = /* @__PURE__ */ new Map();
22408
+ let atOrAboveMinSize;
22409
+ const feeCheckWaived = async () => {
22410
+ if (atOrAboveMinSize === void 0) {
22411
+ const usd = await this.getOrderUsdValue(order);
22412
+ atOrAboveMinSize = usd !== null && usd.inputUsd.gte(this.configService.getMinOrderSizeUsd());
22413
+ }
22414
+ return atOrAboveMinSize;
22415
+ };
22416
+ let feesWaived = false;
22062
22417
  for (let leg = 0; leg < order.inputs.length; leg++) {
22063
22418
  const input = order.inputs[leg];
22064
22419
  const output = order.output.assets[leg];
@@ -22080,7 +22435,30 @@ var FXFiller = class _FXFiller {
22080
22435
  for (const candidate of matches) {
22081
22436
  let partialFill = false;
22082
22437
  const fundingCalls = [];
22083
- const left = candidate.available - (plannedOn.get(candidate.order.id) ?? 0n);
22438
+ const budget = budgetFor(candidate.order, outputToken, outputDecimals);
22439
+ if (!spentOn.has(candidate.order.id)) {
22440
+ spentOn.set(
22441
+ candidate.order.id,
22442
+ await this.contractService.limitOrderSpent(destChain, budget.budgetId)
22443
+ );
22444
+ }
22445
+ const spent = spentOn.get(candidate.order.id) ?? null;
22446
+ const room = spent === null ? candidate.available : budgetRoom(budget, spent, outputDecimals);
22447
+ if (room === 0n) {
22448
+ this.logger.info(
22449
+ {
22450
+ orderId: order.id,
22451
+ leg,
22452
+ limitOrder: candidate.order.id,
22453
+ spent: spent?.toString(),
22454
+ cap: budget.cap.toString()
22455
+ },
22456
+ "Skipping a bid: the limit order has nothing left on chain"
22457
+ );
22458
+ continue;
22459
+ }
22460
+ const available = room < candidate.available ? room : candidate.available;
22461
+ const left = available - (plannedOn.get(candidate.order.id) ?? 0n);
22084
22462
  if (left <= 0n) {
22085
22463
  this.logger.info(
22086
22464
  { orderId: order.id, leg, limitOrder: candidate.order.id },
@@ -22115,10 +22493,7 @@ var FXFiller = class _FXFiller {
22115
22493
  {
22116
22494
  orderId: order.id,
22117
22495
  limitOrder: candidate.order.id,
22118
- available: (0, import_viem27.formatUnits)(
22119
- candidate.available,
22120
- 18
22121
- ),
22496
+ available: (0, import_viem29.formatUnits)(available, 18),
22122
22497
  userRequested: output.amount.toString(),
22123
22498
  payout: targetOutput.toString(),
22124
22499
  crossChain: sourceChain !== destChain
@@ -22163,7 +22538,10 @@ var FXFiller = class _FXFiller {
22163
22538
  }
22164
22539
  }
22165
22540
  const effectiveBalance = walletContribution + credited;
22166
- const finalOutputAmount = effectiveBalance > targetOutput ? targetOutput : effectiveBalance;
22541
+ const finalOutputAmount = toRaw(
22542
+ toScaled(effectiveBalance > targetOutput ? targetOutput : effectiveBalance, outputDecimals),
22543
+ outputDecimals
22544
+ );
22167
22545
  if (finalOutputAmount === 0n) {
22168
22546
  this.logger.info(
22169
22547
  {
@@ -22252,7 +22630,7 @@ var FXFiller = class _FXFiller {
22252
22630
  const spread = convertedInput - finalOutputAmount;
22253
22631
  if (spread <= 0n) sameAssetProfitable = false;
22254
22632
  realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
22255
- const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0, import_viem27.formatUnits)(spread, outputDecimals)));
22633
+ const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(spread, outputDecimals)));
22256
22634
  if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
22257
22635
  }
22258
22636
  const payoutSurplusUsd = new import_decimal6.Decimal(0);
@@ -22267,9 +22645,9 @@ var FXFiller = class _FXFiller {
22267
22645
  {
22268
22646
  orderId: order.id,
22269
22647
  feeToken: feeTokenLower,
22270
- residual: (0, import_viem27.formatUnits)(residual, feeToken.decimals),
22271
- dispatchFee: (0, import_viem27.formatUnits)(dispatchFee, feeToken.decimals),
22272
- required: (0, import_viem27.formatUnits)(required, feeToken.decimals)
22648
+ residual: (0, import_viem29.formatUnits)(residual, feeToken.decimals),
22649
+ dispatchFee: (0, import_viem29.formatUnits)(dispatchFee, feeToken.decimals),
22650
+ required: (0, import_viem29.formatUnits)(required, feeToken.decimals)
22273
22651
  },
22274
22652
  "Skipping order: fill leaves too little of the fee token to dispatch the escrow release"
22275
22653
  );
@@ -22279,27 +22657,31 @@ var FXFiller = class _FXFiller {
22279
22657
  const executionCost = totalCostInSourceFeeToken + relayerFeeInSourceFeeToken;
22280
22658
  const partialEdgeUsd = sameAssetEdgeUsd.plus(payoutSurplusUsd);
22281
22659
  if (!partialFill && order.fees < executionCost) {
22282
- this.logger.info(
22283
- {
22284
- orderId: order.id,
22285
- orderFees: (0, import_viem27.formatUnits)(order.fees, feeTokenDecimals),
22286
- fillGas: (0, import_viem27.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22287
- relayerFee: (0, import_viem27.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22288
- executionCost: (0, import_viem27.formatUnits)(executionCost, feeTokenDecimals)
22289
- },
22290
- "Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
22291
- );
22292
- continue;
22660
+ if (!await feeCheckWaived()) {
22661
+ this.logger.info(
22662
+ {
22663
+ orderId: order.id,
22664
+ orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
22665
+ fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22666
+ relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22667
+ executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
22668
+ minOrderSizeUsd: this.configService.getMinOrderSizeUsd()
22669
+ },
22670
+ "Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
22671
+ );
22672
+ continue;
22673
+ }
22674
+ feesWaived = true;
22293
22675
  }
22294
22676
  if (sameAsset && !sameAssetProfitable) {
22295
22677
  this.logger.info(
22296
- { orderId: order.id, realizedSpreadProfit: (0, import_viem27.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22678
+ { orderId: order.id, realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22297
22679
  "Skipping order: a same-asset fill does not net a positive spread"
22298
22680
  );
22299
22681
  continue;
22300
22682
  }
22301
22683
  const feeProfit = order.fees - executionCost;
22302
- const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0, import_viem27.formatUnits)(feeProfit + realizedSpreadProfit, feeTokenDecimals));
22684
+ const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0, import_viem29.formatUnits)(feeProfit + realizedSpreadProfit, feeTokenDecimals));
22303
22685
  this.logger.info(
22304
22686
  {
22305
22687
  orderId: order.id,
@@ -22315,15 +22697,16 @@ var FXFiller = class _FXFiller {
22315
22697
  limitOrders: matches.length,
22316
22698
  available: candidate.available.toString(),
22317
22699
  payout: targetOutput.toString(),
22318
- orderFees: (0, import_viem27.formatUnits)(order.fees, feeTokenDecimals),
22319
- fillGas: (0, import_viem27.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22320
- relayerFee: (0, import_viem27.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22321
- executionCost: (0, import_viem27.formatUnits)(executionCost, feeTokenDecimals),
22322
- feeProfit: (0, import_viem27.formatUnits)(feeProfit, feeTokenDecimals),
22323
- realizedSpreadProfit: (0, import_viem27.formatUnits)(realizedSpreadProfit, feeTokenDecimals),
22700
+ orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
22701
+ fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22702
+ relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22703
+ executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
22704
+ feeProfit: (0, import_viem29.formatUnits)(feeProfit, feeTokenDecimals),
22705
+ realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals),
22324
22706
  payoutSurplusUsd: payoutSurplusUsd.toString(),
22325
22707
  totalProfit,
22326
- profitable: totalProfit > 0
22708
+ profitable: totalProfit > 0,
22709
+ feeCheckWaived: !partialFill && order.fees < executionCost
22327
22710
  },
22328
22711
  "FX swap profitability evaluation"
22329
22712
  );
@@ -22339,7 +22722,8 @@ var FXFiller = class _FXFiller {
22339
22722
  fillerInputs,
22340
22723
  fundingCalls: [...fundingCalls],
22341
22724
  partialFill,
22342
- profit: totalProfit
22725
+ profit: totalProfit,
22726
+ budget
22343
22727
  });
22344
22728
  if (!partialEligibleCheap) break;
22345
22729
  }
@@ -22347,8 +22731,14 @@ var FXFiller = class _FXFiller {
22347
22731
  if (plans.length === 0) return 0;
22348
22732
  if (order.id) {
22349
22733
  this.contractService.cacheService.setBidPlans(order.id, plans);
22350
- this.contractService.cacheService.setFillerOutputs(order.id, plans[0].fillerOutputs, plans[0].fillerInputs);
22734
+ this.contractService.cacheService.setFillerOutputs(
22735
+ order.id,
22736
+ plans[0].fillerOutputs,
22737
+ plans[0].fillerInputs,
22738
+ plans[0].budget
22739
+ );
22351
22740
  this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
22741
+ this.contractService.cacheService.setFeeCheckWaived(order.id, feesWaived);
22352
22742
  this.contractService.cacheService.setMatchedLimitOrder(order.id, [
22353
22743
  { limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
22354
22744
  ]);
@@ -22442,10 +22832,14 @@ var FXFiller = class _FXFiller {
22442
22832
  // =========================================================================
22443
22833
  /**
22444
22834
  * Update consecutive-clamp counter after a successful order evaluation.
22445
- * Only venue-priced legs feed this counter (see clamp site) — a streak of those
22446
- * is the signal that a live market source has gone off (stale pool, manipulated
22447
- * venue) and the filler should stop until an operator investigates. Offline
22448
- * price-curve clamps warn but never reach here.
22835
+ *
22836
+ * Dormant. Only venue-priced legs ever fed a clamped outcome here: a streak
22837
+ * of those meant a live market source had gone off (stale pool, manipulated
22838
+ * venue). Venue pricing went with Uniswap V4, and limit orders are prices the
22839
+ * operator set, so the only caller records `false` and the filler never
22840
+ * halts. `isHalted`, `resetHalt` and `[simplex.overfillProtection]
22841
+ * maxConsecutiveClamps` are kept so existing configs and the halt API still
22842
+ * load; they can go once nothing reads them.
22449
22843
  */
22450
22844
  recordOrderOutcome(clamped, orderId) {
22451
22845
  if (clamped) {
@@ -22471,18 +22865,6 @@ var FXFiller = class _FXFiller {
22471
22865
  this.consecutiveClamps = 0;
22472
22866
  this.logger.warn("FXFiller halt reset by operator \u2014 resuming order evaluation");
22473
22867
  }
22474
- /**
22475
- * Given a single (input, output) leg and the remaining token0 budget of its
22476
- * pair, computes how much token0 notional to allocate to this leg and the
22477
- * corresponding maximum output amount at the pair's rate.
22478
- *
22479
- * `rate` is **token1 per 1 token0**:
22480
- * - token0 input → token1 output: token0 × rate → token1 amount.
22481
- * - token1 input → token0 output: token1 ÷ rate → token0 amount.
22482
- *
22483
- * Returns `null` when this leg cannot consume any of the pair's remaining
22484
- * budget (e.g. the cap has already been exhausted).
22485
- */
22486
22868
  /**
22487
22869
  * Whether any solver has already delivered output against this order.
22488
22870
  *
@@ -22622,7 +23004,7 @@ var FXFiller = class _FXFiller {
22622
23004
  const symbol = this.registry.symbolFor(inputToken, order.source);
22623
23005
  if (!symbol) return null;
22624
23006
  const decimals = await this.contractService.getTokenDecimals(inputToken, order.source);
22625
- const legUsd = usdValueOf(factors, symbol, new import_decimal6.Decimal((0, import_viem27.formatUnits)(input.amount, decimals)));
23007
+ const legUsd = usdValueOf(factors, symbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(input.amount, decimals)));
22626
23008
  if (!legUsd) return null;
22627
23009
  inputUsd = inputUsd.plus(legUsd);
22628
23010
  }
@@ -22635,7 +23017,7 @@ function minBigInt(first, ...rest) {
22635
23017
 
22636
23018
  // src/services/TokenSender.ts
22637
23019
  var import_sdk16 = require("@hyperbridge/sdk");
22638
- var import_viem28 = require("viem");
23020
+ var import_viem30 = require("viem");
22639
23021
  var TokenSender = class {
22640
23022
  constructor(clientManager, solver, getVaults, userOpSender, configService) {
22641
23023
  this.clientManager = clientManager;
@@ -22647,18 +23029,18 @@ var TokenSender = class {
22647
23029
  }
22648
23030
  async send(params) {
22649
23031
  const { chain, token, amount, to } = params;
22650
- if (!(0, import_viem28.isAddress)(to)) throw new Error(`Invalid recipient address: ${to}`);
23032
+ if (!(0, import_viem30.isAddress)(to)) throw new Error(`Invalid recipient address: ${to}`);
22651
23033
  const publicClient = this.clientManager.getPublicClient(chain);
22652
23034
  const calls = [];
22653
23035
  let redeemed = false;
22654
23036
  if (token === "native") {
22655
- const value = (0, import_viem28.parseUnits)(amount, 18);
23037
+ const value = (0, import_viem30.parseUnits)(amount, 18);
22656
23038
  if (value <= 0n) throw new Error("Amount must be greater than 0");
22657
23039
  const balance = await publicClient.getBalance({ address: this.solver });
22658
23040
  if (balance < value) throw new Error(`Insufficient native balance: have ${balance}, need ${value}`);
22659
23041
  calls.push({ target: to, value, data: "0x" });
22660
23042
  } else {
22661
- if (!(0, import_viem28.isAddress)(token)) throw new Error(`Invalid token address: ${token}`);
23043
+ if (!(0, import_viem30.isAddress)(token)) throw new Error(`Invalid token address: ${token}`);
22662
23044
  const tokenLower = token.toLowerCase();
22663
23045
  const [decimals, balance] = await Promise.all([
22664
23046
  publicClient.readContract({ abi: ERC20_ABI, address: token, functionName: "decimals" }),
@@ -22669,7 +23051,7 @@ var TokenSender = class {
22669
23051
  args: [this.solver]
22670
23052
  })
22671
23053
  ]);
22672
- const units = (0, import_viem28.parseUnits)(amount, decimals);
23054
+ const units = (0, import_viem30.parseUnits)(amount, decimals);
22673
23055
  if (units <= 0n) throw new Error("Amount must be greater than 0");
22674
23056
  const isVaultShare = this.getVaults().some(
22675
23057
  (v) => v.chain === chain && v.vault.toLowerCase() === tokenLower
@@ -22680,7 +23062,7 @@ var TokenSender = class {
22680
23062
  calls.push({
22681
23063
  target: token,
22682
23064
  value: 0n,
22683
- data: (0, import_viem28.encodeFunctionData)({ abi: ERC4626_ABI, functionName: "redeem", args: [units, to, this.solver] })
23065
+ data: (0, import_viem30.encodeFunctionData)({ abi: ERC4626_ABI, functionName: "redeem", args: [units, to, this.solver] })
22684
23066
  });
22685
23067
  } else {
22686
23068
  const reserve = this.userOpSender?.canSponsor(chain) && this.configService ? paymasterReserveForToken(chain, tokenLower, this.configService) : 0n;
@@ -22703,7 +23085,7 @@ var TokenSender = class {
22703
23085
  calls.push({
22704
23086
  target: token,
22705
23087
  value: 0n,
22706
- data: (0, import_viem28.encodeFunctionData)({ abi: ERC20_ABI, functionName: "transfer", args: [to, units] })
23088
+ data: (0, import_viem30.encodeFunctionData)({ abi: ERC20_ABI, functionName: "transfer", args: [to, units] })
22707
23089
  });
22708
23090
  }
22709
23091
  }
@@ -22746,7 +23128,7 @@ var TokenSender = class {
22746
23128
  });
22747
23129
  const required = shortfall > 0n ? shortfall : 0n;
22748
23130
  if (available < required) continue;
22749
- const floor = vault.minBalance === void 0 ? 0n : (0, import_viem28.parseUnits)(vault.minBalance, decimals);
23131
+ const floor = vault.minBalance === void 0 ? 0n : (0, import_viem30.parseUnits)(vault.minBalance, decimals);
22750
23132
  const headroom = reserve > floor ? reserve : floor;
22751
23133
  const wanted = shortfall + headroom;
22752
23134
  const amount = available >= wanted ? wanted : required;
@@ -22754,7 +23136,7 @@ var TokenSender = class {
22754
23136
  return {
22755
23137
  target: vault.vault,
22756
23138
  value: 0n,
22757
- data: (0, import_viem28.encodeFunctionData)({
23139
+ data: (0, import_viem30.encodeFunctionData)({
22758
23140
  abi: ERC4626_ABI,
22759
23141
  functionName: "withdraw",
22760
23142
  args: [amount, this.solver, this.solver]
@@ -22820,9 +23202,9 @@ var LimitOrderLifecycle = class {
22820
23202
  const reconcileMs = this.options.reconcileIntervalSecs * 1e3;
22821
23203
  this.every(heartbeatMs, "heartbeat", () => this.service.heartbeat());
22822
23204
  this.every(EXPIRY_SWEEP_MS, "expiry", () => this.expire());
22823
- this.every(reconcileMs, "reconciliation", () => this.reconcile());
23205
+ this.every(reconcileMs, "reconciliation", () => this.upkeep());
22824
23206
  this.logger.info({ heartbeatMs, expiryMs: EXPIRY_SWEEP_MS, reconcileMs }, "Orderbook lifecycle started");
22825
- void this.run("reconciliation", () => this.reconcile());
23207
+ void this.upkeep();
22826
23208
  }
22827
23209
  stop() {
22828
23210
  this.timers.forEach((timer) => clearInterval(timer));
@@ -22833,6 +23215,14 @@ var LimitOrderLifecycle = class {
22833
23215
  const expired = await this.service.expireStale();
22834
23216
  if (expired > 0) this.logger.info({ expired }, "Withdrew limit orders that had outlived their expiry");
22835
23217
  }
23218
+ /**
23219
+ * The tally pass first, so reconciliation against the orderbook reads the sizes
23220
+ * it left. Each fails on its own.
23221
+ */
23222
+ async upkeep() {
23223
+ await this.run("tally reconciliation", async () => this.options.reconcileTallies());
23224
+ await this.run("reconciliation", () => this.reconcile());
23225
+ }
22836
23226
  async reconcile() {
22837
23227
  const report = await this.service.reconcile();
22838
23228
  if (report.cancelled || report.reposted || report.underFunded) {
@@ -22868,7 +23258,7 @@ var LimitOrderLifecycle = class {
22868
23258
 
22869
23259
  // src/services/BalanceProvider.ts
22870
23260
  var import_node_events = require("node:events");
22871
- var import_viem29 = require("viem");
23261
+ var import_viem31 = require("viem");
22872
23262
  var BalanceProvider = class extends import_node_events.EventEmitter {
22873
23263
  constructor(options) {
22874
23264
  super();
@@ -22961,7 +23351,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
22961
23351
  try {
22962
23352
  const native = await client.getBalance({ address: fillerAddr });
22963
23353
  const symbol = nativeTokenSymbol(chainId);
22964
- row.native = { symbol, amount: Number.parseFloat((0, import_viem29.formatUnits)(native, 18)) };
23354
+ row.native = { symbol, amount: Number.parseFloat((0, import_viem31.formatUnits)(native, 18)) };
22965
23355
  } catch (err) {
22966
23356
  issues.push({ chainId, source: "native", message: errorMessage(err) });
22967
23357
  }
@@ -23032,7 +23422,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23032
23422
  functionName: "balanceOf",
23033
23423
  args: [fillerAddr]
23034
23424
  });
23035
- wallet = Number.parseFloat((0, import_viem29.formatUnits)(balance, definition.decimals));
23425
+ wallet = Number.parseFloat((0, import_viem31.formatUnits)(balance, definition.decimals));
23036
23426
  } catch (err) {
23037
23427
  issues.push({
23038
23428
  chainId,
@@ -23060,8 +23450,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23060
23450
  available: wallet !== null && vaultDataAvailable ? Math.max(wallet - walletReserve, 0) + vaultAvailable : null,
23061
23451
  vaults: positions.map((position) => ({
23062
23452
  address: position.vault,
23063
- position: Number.parseFloat((0, import_viem29.formatUnits)(position.positionAssets, position.decimals)),
23064
- available: Number.parseFloat((0, import_viem29.formatUnits)(position.availableAssets, position.decimals)),
23453
+ position: Number.parseFloat((0, import_viem31.formatUnits)(position.positionAssets, position.decimals)),
23454
+ available: Number.parseFloat((0, import_viem31.formatUnits)(position.availableAssets, position.decimals)),
23065
23455
  acceptsDeposits: position.acceptsDeposits
23066
23456
  })),
23067
23457
  status: complete ? "fresh" : wallet !== null || positions.length > 0 ? "partial" : "unavailable"
@@ -23104,8 +23494,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23104
23494
  const api = await coprocessor.queryApi();
23105
23495
  const account = await api.query.system.account(address);
23106
23496
  const decimals = api.registry.chainDecimals[0] ?? 12;
23107
- const free = Number.parseFloat((0, import_viem29.formatUnits)(BigInt(account.data.free.toString()), decimals));
23108
- const reserved = Number.parseFloat((0, import_viem29.formatUnits)(BigInt(account.data.reserved.toString()), decimals));
23497
+ const free = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.free.toString()), decimals));
23498
+ const reserved = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.reserved.toString()), decimals));
23109
23499
  this.snapshot = { ...this.snapshot, hyperbridge: { address, free, reserved } };
23110
23500
  } catch (err) {
23111
23501
  this.logger.warn({ err }, "Failed to fetch Hyperbridge balance");
@@ -23125,7 +23515,7 @@ function isConfiguredAddress(address) {
23125
23515
  }
23126
23516
  function sumFormatted(positions, field) {
23127
23517
  return positions.reduce(
23128
- (total, position) => total + Number.parseFloat((0, import_viem29.formatUnits)(position[field], position.decimals)),
23518
+ (total, position) => total + Number.parseFloat((0, import_viem31.formatUnits)(position[field], position.decimals)),
23129
23519
  0
23130
23520
  );
23131
23521
  }
@@ -23590,6 +23980,7 @@ async function bootFiller(config, options) {
23590
23980
  rebalancing: config.rebalancing,
23591
23981
  targetGasUnits: config.simplex.targetGasUnits,
23592
23982
  bidValiditySeconds: config.simplex.bidValiditySeconds,
23983
+ minOrderSizeUsd: config.simplex.minOrderSizeUsd,
23593
23984
  blockScanIntervalSeconds: config.simplex.blockScanIntervalSeconds,
23594
23985
  gasFeeBump: config.simplex.gasFeeBump,
23595
23986
  overfillProtection: config.simplex.overfillProtection,
@@ -23633,7 +24024,7 @@ async function bootFiller(config, options) {
23633
24024
  const sharedCacheService = new CacheService(options.loggers);
23634
24025
  if (!options.signer && !globalWatchOnly && !allChainsWatchOnly(watchOnlyConfig, resolvedChains)) {
23635
24026
  throw new Error(
23636
- "A signer is required unless every chain is watch-only: pass `signer` to Simplex.start (privateKeySigner, turnkeySigner, mpcVaultSigner, viemSigner, or your own Signer implementation). Running the binary? Configure it under [simplex.signer]."
24027
+ "A signer is required unless every chain is watch-only: pass `signer` to Simplex.start (privateKeySigner, secretPhraseSigner, turnkeySigner, mpcVaultSigner, viemSigner, or your own Signer implementation). Running the binary? Configure it under [simplex.signer]."
23637
24028
  );
23638
24029
  }
23639
24030
  const chainClientManager = new ChainClientManager(configService, options.signer);
@@ -23772,7 +24163,8 @@ async function bootFiller(config, options) {
23772
24163
  const lifecycle = new LimitOrderLifecycle(
23773
24164
  limitOrderService,
23774
24165
  {
23775
- reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS
24166
+ reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS,
24167
+ reconcileTallies: () => intentFiller.reconcileLimitOrderTallies()
23776
24168
  },
23777
24169
  options.loggers
23778
24170
  );
@@ -23810,8 +24202,8 @@ async function bootFiller(config, options) {
23810
24202
  if (vaultVenue) {
23811
24203
  vaultVenue.onTx = ({ chain, kind, txHash, sponsored, movements }) => {
23812
24204
  const rows = movements.length > 0 ? movements.map((move) => {
23813
- const underlying = { token: move.symbol, amount: (0, import_viem30.formatUnits)(move.amount, move.decimals) };
23814
- const shares = move.shares === void 0 ? { token: null, amount: null } : { token: move.shareSymbol, amount: (0, import_viem30.formatUnits)(move.shares, move.shareDecimals) };
24205
+ const underlying = { token: move.symbol, amount: (0, import_viem32.formatUnits)(move.amount, move.decimals) };
24206
+ const shares = move.shares === void 0 ? { token: null, amount: null } : { token: move.shareSymbol, amount: (0, import_viem32.formatUnits)(move.shares, move.shareDecimals) };
23815
24207
  const out = kind === "sweep" ? underlying : shares;
23816
24208
  const back = kind === "sweep" ? shares : underlying;
23817
24209
  return { token: out.token, amount: out.amount, to: move.vault, tokenIn: back.token, amountIn: back.amount };
@@ -24243,6 +24635,14 @@ var MemoryLimitOrderStore = class {
24243
24635
  const remaining = BigInt(order.remaining) - BigInt(amount);
24244
24636
  return this.patch(id, { remaining: (remaining > 0n ? remaining : 0n).toString() });
24245
24637
  }
24638
+ async clampRemaining(id, room) {
24639
+ const order = this.orders.get(id);
24640
+ if (!order || order.status !== "open") return null;
24641
+ const sum = BigInt(room) + BigInt(order.reserved);
24642
+ const target = sum > 0n ? sum : 0n;
24643
+ if (target >= BigInt(order.remaining)) return null;
24644
+ return this.patch(id, { remaining: target.toString() });
24645
+ }
24246
24646
  async release(id, amount) {
24247
24647
  const order = this.orders.get(id);
24248
24648
  if (!order) return;
@@ -24751,7 +25151,8 @@ var OrderScanner = class _OrderScanner {
24751
25151
  * back from its own endpoints, which also proves they answer for one chain
24752
25152
  * before a filler ever depends on them.
24753
25153
  *
24754
- * @throws if an endpoint set is invalid or unreachable, if two entries resolve
25154
+ * @throws if an endpoint set is invalid, if none of a chain's endpoints answer,
25155
+ * if its endpoints disagree on the chain, if two entries resolve
24755
25156
  * to the same chain, or if `scanIntervalSecs` is below the minimum.
24756
25157
  */
24757
25158
  static async create(options) {
@@ -24811,7 +25212,10 @@ var OrderScanner = class _OrderScanner {
24811
25212
  async addChainNow(chain) {
24812
25213
  if (this.closed) throw new Error("This OrderScanner is closed");
24813
25214
  const rpcUrls = validateRpcUrls(chain.rpcUrls);
24814
- const chainId = chain.chainId ?? await fetchChainId(rpcUrls[0]);
25215
+ const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls, bundlerUrl: chain.bundlerUrl ?? "" }], {
25216
+ loggers: this.loggers,
25217
+ tolerateUnreachable: true
25218
+ }))[0].chainId;
24815
25219
  if (this.closed) throw new Error("This OrderScanner is closed");
24816
25220
  if (this.scanners.has(chainId)) {
24817
25221
  throw new Error(`Chain ${chainId} is already in this scanner`);
@@ -24911,7 +25315,7 @@ var LimitOrderController = class {
24911
25315
  get service() {
24912
25316
  const service = this.runtime.limitOrders;
24913
25317
  if (!service) {
24914
- throw new Error("No orderbook is configured \u2014 set [orderbook] enabled and url to use limit orders");
25318
+ throw new Error("No orderbook is configured \u2014 set [orderbook] url to use limit orders");
24915
25319
  }
24916
25320
  return service;
24917
25321
  }
@@ -25376,7 +25780,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25376
25780
  const tomlSigner = options.config.simplex?.signer;
25377
25781
  if (tomlSigner && !options.signer) {
25378
25782
  throw new Error(
25379
- "config.simplex.signer describes a signer but none was passed. Simplex.start takes a Signer instance: `signer: await createSigner(config.simplex.signer)`, or build one directly with privateKeySigner/turnkeySigner/mpcVaultSigner/viemSigner."
25783
+ "config.simplex.signer describes a signer but none was passed. Simplex.start takes a Signer instance: `signer: await createSigner(config.simplex.signer)`, or build one directly with privateKeySigner/secretPhraseSigner/turnkeySigner/mpcVaultSigner/viemSigner."
25380
25784
  );
25381
25785
  }
25382
25786
  const loggers = new LoggerContext({
@@ -25580,6 +25984,8 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25580
25984
  0 && (module.exports = {
25581
25985
  AssetController,
25582
25986
  ChainController,
25987
+ LimitOrderController,
25988
+ LimitOrderValidationError,
25583
25989
  LoggerContext,
25584
25990
  MemoryDataStore,
25585
25991
  OrderScanner,
@@ -25592,6 +25998,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25592
25998
  WalletController,
25593
25999
  addLogSink,
25594
26000
  assertConfirmationCoverage,
26001
+ canDerive,
25595
26002
  configureLogger,
25596
26003
  createSigner,
25597
26004
  digestSigner,
@@ -25604,10 +26011,12 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25604
26011
  pickAnchorStable,
25605
26012
  privateKeySigner,
25606
26013
  registrySymbols,
26014
+ secretPhraseSigner,
25607
26015
  turnkeySigner,
25608
26016
  validateAssetDefinitions,
25609
26017
  validateConfig,
25610
26018
  validatePairConfigs,
26019
+ validateSecretPhrase,
25611
26020
  validateSignerConfig,
25612
26021
  validateVaultToml,
25613
26022
  viemSigner