@hyperbridge/simplex 0.16.8 → 0.16.11

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");
@@ -2838,11 +2843,21 @@ var import_node_crypto = require("node:crypto");
2838
2843
  var import_sdk4 = require("@hyperbridge/sdk");
2839
2844
 
2840
2845
  // src/orderbook/amounts.ts
2846
+ var import_viem6 = require("viem");
2841
2847
  var ORDERBOOK_SCALE = 10n ** 18n;
2842
2848
  function toRaw(amount, decimals) {
2843
2849
  const shift = 18 - decimals;
2844
2850
  return shift >= 0 ? amount / 10n ** BigInt(shift) : amount * 10n ** BigInt(-shift);
2845
2851
  }
2852
+ function budgetIdFor(limitOrderId) {
2853
+ return (0, import_viem6.keccak256)((0, import_viem6.stringToBytes)(limitOrderId));
2854
+ }
2855
+ function budgetFor(order, token, decimals) {
2856
+ return { budgetId: budgetIdFor(order.id), cap: toRaw(BigInt(order.size), decimals), token };
2857
+ }
2858
+ function budgetRoom(budget, spent, decimals) {
2859
+ return spent >= budget.cap ? 0n : toScaled(budget.cap - spent, decimals);
2860
+ }
2846
2861
  function fromHuman(amount) {
2847
2862
  const text = amount.trim();
2848
2863
  if (!/^[0-9]+(\.[0-9]+)?$/.test(text)) {
@@ -2863,6 +2878,10 @@ function toScaled(amount, decimals) {
2863
2878
  const shift = 18 - decimals;
2864
2879
  return shift >= 0 ? amount * 10n ** BigInt(shift) : amount / 10n ** BigInt(-shift);
2865
2880
  }
2881
+ function toScaledUp(amount, decimals) {
2882
+ const shift = 18 - decimals;
2883
+ return shift >= 0 ? amount * 10n ** BigInt(shift) : divCeil(amount, 10n ** BigInt(-shift));
2884
+ }
2866
2885
  function divCeil(numerator, denominator) {
2867
2886
  return (numerator + denominator - 1n) / denominator;
2868
2887
  }
@@ -2904,6 +2923,87 @@ function inputFor(params) {
2904
2923
  return divCeil(scaled, unit) * unit;
2905
2924
  }
2906
2925
 
2926
+ // src/orderbook/matching.ts
2927
+ function limitOrderLegs(order) {
2928
+ return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
2929
+ }
2930
+ function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
2931
+ const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
2932
+ const offer = offerFor({
2933
+ side: order.side,
2934
+ inputAmount: incoming.inputNet,
2935
+ price: BigInt(order.price),
2936
+ outputDecimals: incoming.outputDecimals
2937
+ });
2938
+ const available = BigInt(order.remaining);
2939
+ return { order, offer, available, payout: offer < available ? offer : available };
2940
+ }).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
2941
+ return qualifying;
2942
+ }
2943
+ function byBestOfferFirst(a, b) {
2944
+ if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
2945
+ if (a.available !== b.available) return a.available > b.available ? -1 : 1;
2946
+ return a.order.id < b.order.id ? -1 : 1;
2947
+ }
2948
+ function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
2949
+ let furthest = -1;
2950
+ for (const order of orders) {
2951
+ const failed = firstFailed(order, incoming, resolve, now);
2952
+ furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
2953
+ }
2954
+ const { source, destination, inputSymbol } = incoming;
2955
+ if (furthest < CHECKS.length) {
2956
+ switch (CHECKS[Math.max(furthest, 0)]) {
2957
+ case "open":
2958
+ return "no limit order is open";
2959
+ case "chain":
2960
+ return `no open limit order fills on ${destination}`;
2961
+ case "input":
2962
+ return `no limit order on ${destination} takes ${inputSymbol} in`;
2963
+ case "output":
2964
+ return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
2965
+ case "source":
2966
+ return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
2967
+ }
2968
+ }
2969
+ const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
2970
+ order,
2971
+ offer: offerFor({
2972
+ side: order.side,
2973
+ inputAmount: incoming.inputNet,
2974
+ price: BigInt(order.price),
2975
+ outputDecimals: incoming.outputDecimals
2976
+ })
2977
+ }));
2978
+ if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
2979
+ return "the limit orders that meet its rate have nothing left to pay out";
2980
+ }
2981
+ const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
2982
+ const symbol = limitOrderLegs(best.order).output;
2983
+ return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
2984
+ }
2985
+ var CHECKS = ["open", "chain", "input", "output", "source"];
2986
+ function serves(order, incoming, resolve, now) {
2987
+ return firstFailed(order, incoming, resolve, now) === null;
2988
+ }
2989
+ function firstFailed(order, incoming, resolve, now) {
2990
+ if (order.status !== "open") return "open";
2991
+ if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
2992
+ if (order.fillChain !== incoming.destination) return "chain";
2993
+ const legs = limitOrderLegs(order);
2994
+ if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
2995
+ const outputToken = resolve(legs.output, order.fillChain);
2996
+ if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
2997
+ if (incoming.source === incoming.destination) return null;
2998
+ return order.acceptedSources.includes(incoming.source) ? null : "source";
2999
+ }
3000
+ function readable(amount) {
3001
+ const [whole, fraction] = toHuman(amount).split(".");
3002
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3003
+ const cut = fraction?.slice(0, 6).replace(/0+$/, "");
3004
+ return cut ? `${grouped}.${cut}` : grouped;
3005
+ }
3006
+
2907
3007
  // src/orderbook/limit-orders.ts
2908
3008
  var LIMITS_TTL_MS = 5 * 60 * 1e3;
2909
3009
  var POSTABLE = ["open", "resizing"];
@@ -3418,6 +3518,58 @@ var LimitOrderService = class {
3418
3518
  }
3419
3519
  return report;
3420
3520
  }
3521
+ /**
3522
+ * Lowers every open order's `remaining` to what the solver's account will still
3523
+ * pay for it, and puts the order back on the book at that size.
3524
+ *
3525
+ * The account tallies each order's payouts on the fill chain and refuses a fill
3526
+ * that would take the tally past the order's size. A fill that never settled
3527
+ * here, or a store restored from an older copy, leaves `remaining` above that
3528
+ * room and the order posted at a size the account will refuse. A tally behind
3529
+ * the store changes nothing: `remaining` is never raised.
3530
+ *
3531
+ * Answers how many orders were lowered.
3532
+ */
3533
+ async reconcileTallies() {
3534
+ let lowered = 0;
3535
+ for (const order of await this.store.list({ status: "open" })) {
3536
+ try {
3537
+ const clamped = await this.clampToTally(order);
3538
+ if (!clamped) continue;
3539
+ lowered++;
3540
+ await this.resize(clamped, BigInt(order.remaining) - BigInt(clamped.remaining));
3541
+ } catch (err) {
3542
+ this.logger.error({ id: order.id, err }, "Could not bring the limit order in line with its on-chain tally");
3543
+ }
3544
+ }
3545
+ return lowered;
3546
+ }
3547
+ /** The order as the store now holds it when its tally lowered it, null when it was left alone. */
3548
+ async clampToTally(order) {
3549
+ const token = this.assetRegistry.getAddress(limitOrderLegs(order).output, order.fillChain);
3550
+ if (!token) return null;
3551
+ const decimals = await this.contractService.getTokenDecimals(token, order.fillChain);
3552
+ const budget = budgetFor(order, token, decimals);
3553
+ const spent = await this.contractService.limitOrderSpent(order.fillChain, budget.budgetId);
3554
+ if (spent === null) return null;
3555
+ const room = budgetRoom(budget, spent, decimals);
3556
+ const over = BigInt(order.remaining) - (room + BigInt(order.reserved));
3557
+ if (over < toScaled(1n, decimals)) return null;
3558
+ const clamped = await this.store.clampRemaining(order.id, room.toString());
3559
+ if (!clamped) return null;
3560
+ this.logger.warn(
3561
+ {
3562
+ id: order.id,
3563
+ before: order.remaining,
3564
+ after: clamped.remaining,
3565
+ spent: spent.toString(),
3566
+ cap: budget.cap.toString(),
3567
+ reserved: clamped.reserved
3568
+ },
3569
+ "Limit order had more left than its on-chain tally allows; lowering it"
3570
+ );
3571
+ return clamped;
3572
+ }
3421
3573
  /**
3422
3574
  * Withdraws every order that has outlived the operator's own `expiresAt`.
3423
3575
  *
@@ -3679,7 +3831,7 @@ async function patchRuntimeState(store, patch) {
3679
3831
  }
3680
3832
 
3681
3833
  // src/services/wallet/mpcvault.ts
3682
- var import_viem6 = require("viem");
3834
+ var import_viem7 = require("viem");
3683
3835
  var grpc = __toESM(require("@grpc/grpc-js"), 1);
3684
3836
 
3685
3837
  // src/proto/mpcvault/platform/v1/api.ts
@@ -12243,18 +12395,18 @@ var MpcVaultService = class {
12243
12395
  // Signature assembly
12244
12396
  // -----------------------------------------------------------------------
12245
12397
  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 });
12398
+ const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
12399
+ const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
12248
12400
  const vInt = this.parseFlexibleBigInt(parts.V || "0");
12249
- let v = (0, import_viem6.padHex)((0, import_viem6.toHex)(vInt), { size: 1 });
12401
+ let v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt), { size: 1 });
12250
12402
  if (normalizeV) {
12251
- v = (0, import_viem6.padHex)((0, import_viem6.toHex)(vInt < 27n ? vInt + 27n : vInt), { size: 1 });
12403
+ v = (0, import_viem7.padHex)((0, import_viem7.toHex)(vInt < 27n ? vInt + 27n : vInt), { size: 1 });
12252
12404
  }
12253
- return (0, import_viem6.concatHex)([r, s, v]);
12405
+ return (0, import_viem7.concatHex)([r, s, v]);
12254
12406
  }
12255
12407
  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 });
12408
+ const r = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.R || "0")), { size: 32 });
12409
+ const s = (0, import_viem7.padHex)((0, import_viem7.toHex)(BigInt(parts.S || "0")), { size: 32 });
12258
12410
  const vInt = this.parseFlexibleBigInt(parts.V || "0");
12259
12411
  const yParity = vInt >= 27n ? Number(vInt - 27n) : Number(vInt);
12260
12412
  if (yParity !== 0 && yParity !== 1) {
@@ -12290,7 +12442,7 @@ var MpcVaultService = class {
12290
12442
  const messageBytes = Buffer.from(messageHash.slice(2), "hex");
12291
12443
  const prefix = Buffer.from(`Ethereum Signed Message:
12292
12444
  ${messageBytes.length}`);
12293
- const ethSignedMessageHash = (0, import_viem6.keccak256)((0, import_viem6.concatHex)([(0, import_viem6.toHex)(prefix), messageHash]));
12445
+ const ethSignedMessageHash = (0, import_viem7.keccak256)((0, import_viem7.concatHex)([(0, import_viem7.toHex)(prefix), messageHash]));
12294
12446
  const ethSignedBytes = Buffer.from(ethSignedMessageHash.slice(2), "hex");
12295
12447
  const uuid = await this.createSigningRequest({
12296
12448
  vaultUuid: this.vaultUuid,
@@ -12378,11 +12530,12 @@ var SignerType = /* @__PURE__ */ ((SignerType2) => {
12378
12530
  SignerType2["PrivateKey"] = "privateKey";
12379
12531
  SignerType2["MpcVault"] = "mpcVault";
12380
12532
  SignerType2["Turnkey"] = "turnkey";
12533
+ SignerType2["SecretPhrase"] = "secretPhrase";
12381
12534
  return SignerType2;
12382
12535
  })(SignerType || {});
12383
12536
 
12384
12537
  // src/services/wallet/accounts/mpc.ts
12385
- var import_viem7 = require("viem");
12538
+ var import_viem8 = require("viem");
12386
12539
  var import_utils = require("viem/utils");
12387
12540
  function mpcVaultSigner(config) {
12388
12541
  const service = new MpcVaultService({
@@ -12417,9 +12570,9 @@ function mpcVaultSigner(config) {
12417
12570
  if (tx.authorizationList?.length) {
12418
12571
  const serializable = { ...tx, type: "eip7702" };
12419
12572
  const signature = await service.signRawHashComponents(
12420
- (0, import_viem7.keccak256)((0, import_viem7.serializeTransaction)(serializable))
12573
+ (0, import_viem8.keccak256)((0, import_viem8.serializeTransaction)(serializable))
12421
12574
  );
12422
- return (0, import_viem7.serializeTransaction)(serializable, signature);
12575
+ return (0, import_viem8.serializeTransaction)(serializable, signature);
12423
12576
  }
12424
12577
  return service.signTransaction({
12425
12578
  chainId: tx.chainId,
@@ -12444,10 +12597,10 @@ function requireChainId(value) {
12444
12597
  var import_accounts = require("viem/accounts");
12445
12598
 
12446
12599
  // src/services/wallet/accounts/viem.ts
12447
- var import_viem8 = require("viem");
12600
+ var import_viem9 = require("viem");
12448
12601
  var import_utils2 = require("viem/utils");
12449
12602
  function splitSignature(signature, source) {
12450
- const sig = (0, import_viem8.parseSignature)(signature);
12603
+ const sig = (0, import_viem9.parseSignature)(signature);
12451
12604
  const yParity = sig.yParity ?? (sig.v !== void 0 ? Number(sig.v >= 27n ? sig.v - 27n : sig.v) : void 0);
12452
12605
  if (yParity !== 0 && yParity !== 1) {
12453
12606
  throw new Error(`Failed to derive yParity from ${source} signature`);
@@ -12499,9 +12652,54 @@ function privateKeySigner(privateKey) {
12499
12652
  return viemSigner((0, import_accounts.privateKeyToAccount)(privateKey));
12500
12653
  }
12501
12654
 
12655
+ // src/services/wallet/accounts/secretphrase.ts
12656
+ var import_bip39 = require("@scure/bip39");
12657
+ var import_accounts2 = require("viem/accounts");
12658
+ var WORD_COUNTS = [12, 15, 18, 21, 24];
12659
+ var MAX_DERIVATION_INDEX = 2 ** 31 - 1;
12660
+ function normaliseSecretPhrase(phrase) {
12661
+ return phrase.trim().split(/\s+/).join(" ").toLowerCase();
12662
+ }
12663
+ function validateSecretPhrase(phrase) {
12664
+ if (typeof phrase !== "string") throw new Error("Secret phrase must be a string");
12665
+ const normalised = normaliseSecretPhrase(phrase);
12666
+ const words = normalised === "" ? [] : normalised.split(" ");
12667
+ if (!WORD_COUNTS.includes(words.length)) {
12668
+ throw new Error(`Secret phrase must have ${WORD_COUNTS.join(", ")} words; got ${words.length}`);
12669
+ }
12670
+ if (words.some((word) => !import_accounts2.english.includes(word))) {
12671
+ throw new Error("Secret phrase contains a word that is not in the BIP-39 English wordlist");
12672
+ }
12673
+ if (!(0, import_bip39.validateMnemonic)(normalised, import_accounts2.english)) {
12674
+ throw new Error("Secret phrase checksum does not match; check the words and their order");
12675
+ }
12676
+ }
12677
+ function assertDerivationIndex(index, name = "Derivation index") {
12678
+ const expected = `${name} must be an integer between 0 and ${MAX_DERIVATION_INDEX}`;
12679
+ if (typeof index !== "number") throw new Error(expected);
12680
+ if (!Number.isInteger(index) || index < 0 || index > MAX_DERIVATION_INDEX) {
12681
+ throw new Error(`${expected}; got ${index}`);
12682
+ }
12683
+ }
12684
+ function secretPhraseSigner(config) {
12685
+ validateSecretPhrase(config.phrase);
12686
+ const phrase = normaliseSecretPhrase(config.phrase);
12687
+ const accountIndex = config.accountIndex ?? 0;
12688
+ assertDerivationIndex(accountIndex, "accountIndex");
12689
+ const derive = (index) => {
12690
+ assertDerivationIndex(index);
12691
+ return { ...viemSigner((0, import_accounts2.mnemonicToAccount)(phrase, { addressIndex: index })), mode: "secretPhrase" };
12692
+ };
12693
+ return { ...derive(accountIndex), accountIndex, derive };
12694
+ }
12695
+ function canDerive(signer) {
12696
+ const candidate = signer;
12697
+ return typeof candidate.derive === "function" && typeof candidate.accountIndex === "number";
12698
+ }
12699
+
12502
12700
  // src/services/wallet/accounts/turnkey.ts
12503
12701
  var import_sdk_server = require("@turnkey/sdk-server");
12504
- var import_viem10 = require("@turnkey/viem");
12702
+ var import_viem12 = require("@turnkey/viem");
12505
12703
  async function turnkeySigner(config) {
12506
12704
  const turnkey = new import_sdk_server.Turnkey({
12507
12705
  defaultOrganizationId: config.organizationId,
@@ -12509,7 +12707,7 @@ async function turnkeySigner(config) {
12509
12707
  apiPrivateKey: config.apiPrivateKey,
12510
12708
  apiPublicKey: config.apiPublicKey
12511
12709
  });
12512
- const account = await (0, import_viem10.createAccount)({
12710
+ const account = await (0, import_viem12.createAccount)({
12513
12711
  client: turnkey.apiClient(),
12514
12712
  organizationId: config.organizationId,
12515
12713
  signWith: config.signWith
@@ -12522,6 +12720,7 @@ async function createSigner(config) {
12522
12720
  if (config.type === "privateKey" /* PrivateKey */) return privateKeySigner(config.key);
12523
12721
  if (config.type === "mpcVault" /* MpcVault */) return mpcVaultSigner(config);
12524
12722
  if (config.type === "turnkey" /* Turnkey */) return turnkeySigner(config);
12723
+ if (config.type === "secretPhrase" /* SecretPhrase */) return secretPhraseSigner(config);
12525
12724
  throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
12526
12725
  }
12527
12726
  function validateSignerConfig(config) {
@@ -12547,15 +12746,23 @@ function validateSignerConfig(config) {
12547
12746
  if (!config.signWith) throw new Error("simplex.signer.signWith is required");
12548
12747
  return;
12549
12748
  }
12749
+ if (config.type === "secretPhrase" /* SecretPhrase */) {
12750
+ if (!config.phrase) {
12751
+ throw new Error("simplex.signer.phrase is required when simplex.signer.type=secretPhrase");
12752
+ }
12753
+ validateSecretPhrase(config.phrase);
12754
+ if (config.accountIndex !== void 0) assertDerivationIndex(config.accountIndex, "simplex.signer.accountIndex");
12755
+ return;
12756
+ }
12550
12757
  throw new Error(`Unsupported signer mode: ${config.type ?? "unknown"}`);
12551
12758
  }
12552
12759
 
12553
12760
  // src/services/wallet/account.ts
12554
- var import_viem12 = require("viem");
12761
+ var import_viem14 = require("viem");
12555
12762
  var import_utils3 = require("viem/utils");
12556
- var import_accounts2 = require("viem/accounts");
12763
+ var import_accounts3 = require("viem/accounts");
12557
12764
  function accountFor(signer) {
12558
- return (0, import_accounts2.toAccount)({
12765
+ return (0, import_accounts3.toAccount)({
12559
12766
  address: signer.address,
12560
12767
  signTypedData: (typedData) => signer.signTypedData(typedData),
12561
12768
  signTransaction: async (transaction) => await signer.signTransaction(toSignerTransaction(transaction)),
@@ -12595,7 +12802,7 @@ function digestSigner(opts) {
12595
12802
  return {
12596
12803
  address: opts.address,
12597
12804
  mode: opts.mode,
12598
- signTypedData: async (typedData) => (0, import_viem12.serializeSignature)(await sign((0, import_viem12.hashTypedData)(typedData))),
12805
+ signTypedData: async (typedData) => (0, import_viem14.serializeSignature)(await sign((0, import_viem14.hashTypedData)(typedData))),
12599
12806
  signAuthorization: (auth) => sign(
12600
12807
  (0, import_utils3.hashAuthorization)({
12601
12808
  address: auth.contractAddress,
@@ -12605,8 +12812,8 @@ function digestSigner(opts) {
12605
12812
  ),
12606
12813
  signTransaction: async (tx) => {
12607
12814
  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);
12815
+ const signature = await sign((0, import_viem14.keccak256)((0, import_viem14.serializeTransaction)(serializable)));
12816
+ return (0, import_viem14.serializeTransaction)(serializable, signature);
12610
12817
  }
12611
12818
  };
12612
12819
  }
@@ -12635,6 +12842,12 @@ async function deriveSubstrateKeyPair(key) {
12635
12842
  }
12636
12843
 
12637
12844
  // src/services/CacheService.ts
12845
+ function budgetToCache(budget) {
12846
+ return { budgetId: budget.budgetId, cap: budget.cap.toString(), token: budget.token };
12847
+ }
12848
+ function budgetFromCache(budget) {
12849
+ return { budgetId: budget.budgetId, cap: BigInt(budget.cap), token: budget.token };
12850
+ }
12638
12851
  var CacheService = class {
12639
12852
  constructor(loggers = defaultLoggerContext()) {
12640
12853
  this.CACHE_EXPIRY_MS = 1 * 60 * 1e3;
@@ -12792,7 +13005,24 @@ var CacheService = class {
12792
13005
  return null;
12793
13006
  }
12794
13007
  }
12795
- setFillerOutputs(orderId, outputs, inputs = []) {
13008
+ /**
13009
+ * The budget of the limit order that priced the outputs cached for this order, or
13010
+ * null when no limit order did.
13011
+ *
13012
+ * Kept on the same entry as the outputs, so the two are replaced and expire
13013
+ * together: a bid can never sign one limit order's payout beside another's budget.
13014
+ */
13015
+ getBidBudget(orderId) {
13016
+ try {
13017
+ const cache = this.cacheData.fillerOutputs[orderId];
13018
+ if (cache?.budget && this.isCacheValid(cache.timestamp)) return budgetFromCache(cache.budget);
13019
+ return null;
13020
+ } catch (error) {
13021
+ this.logger.error({ err: error }, "Error getting bid budget");
13022
+ return null;
13023
+ }
13024
+ }
13025
+ setFillerOutputs(orderId, outputs, inputs = [], budget) {
12796
13026
  try {
12797
13027
  this.cleanupStaleData();
12798
13028
  this.cacheData.fillerOutputs[orderId] = {
@@ -12804,6 +13034,7 @@ var CacheService = class {
12804
13034
  token: o.token,
12805
13035
  amount: o.amount.toString()
12806
13036
  })),
13037
+ ...budget ? { budget: budgetToCache(budget) } : {},
12807
13038
  timestamp: Date.now()
12808
13039
  };
12809
13040
  } catch (error) {
@@ -12849,7 +13080,8 @@ var CacheService = class {
12849
13080
  data: call.data
12850
13081
  })),
12851
13082
  partialFill: plan.partialFill,
12852
- profit: plan.profit
13083
+ profit: plan.profit,
13084
+ ...plan.budget ? { budget: budgetFromCache(plan.budget) } : {}
12853
13085
  }));
12854
13086
  } catch (error) {
12855
13087
  this.logger.error({ err: error }, "Error getting bid plans");
@@ -12878,7 +13110,8 @@ var CacheService = class {
12878
13110
  data: call.data
12879
13111
  })),
12880
13112
  partialFill: plan.partialFill,
12881
- profit: plan.profit
13113
+ profit: plan.profit,
13114
+ ...plan.budget ? { budget: budgetToCache(plan.budget) } : {}
12882
13115
  })),
12883
13116
  timestamp: Date.now()
12884
13117
  };
@@ -13036,12 +13269,12 @@ var CacheService = class {
13036
13269
  };
13037
13270
 
13038
13271
  // src/services/ChainClientManager.ts
13039
- var import_viem15 = require("viem");
13040
- var import_accounts3 = require("viem/accounts");
13272
+ var import_viem17 = require("viem");
13273
+ var import_accounts4 = require("viem/accounts");
13041
13274
  var import_sdk6 = require("@hyperbridge/sdk");
13042
13275
 
13043
13276
  // src/services/QuorumPublicClient.ts
13044
- var import_viem14 = require("viem");
13277
+ var import_viem16 = require("viem");
13045
13278
  var import_sdk5 = require("@hyperbridge/sdk");
13046
13279
  function quorumThreshold(numProviders) {
13047
13280
  return Math.floor(2 * numProviders / 3) + 1;
@@ -13141,9 +13374,9 @@ var _QuorumPublicClient = class _QuorumPublicClient {
13141
13374
  this.deadlineMs = budget.deadlineMs;
13142
13375
  const chain = (0, import_sdk5.getViemChain)(chainId);
13143
13376
  this.clients = this.rpcUrls.map(
13144
- (url) => (0, import_viem14.createPublicClient)({
13377
+ (url) => (0, import_viem16.createPublicClient)({
13145
13378
  chain,
13146
- transport: (0, import_viem14.http)(url, {
13379
+ transport: (0, import_viem16.http)(url, {
13147
13380
  timeout: budget.timeoutMs,
13148
13381
  // No transport-level retry. viem's retry sleeps for the provider's
13149
13382
  // `Retry-After` verbatim and uncapped, which reaches the socket
@@ -13559,9 +13792,9 @@ var HTTP_TRANSPORT_OPTS = {
13559
13792
  };
13560
13793
  function buildTransport(rpcUrls) {
13561
13794
  if (rpcUrls.length === 1) {
13562
- return (0, import_viem15.http)(rpcUrls[0], HTTP_TRANSPORT_OPTS);
13795
+ return (0, import_viem17.http)(rpcUrls[0], HTTP_TRANSPORT_OPTS);
13563
13796
  }
13564
- return (0, import_viem15.fallback)(rpcUrls.map((url) => (0, import_viem15.http)(url, HTTP_TRANSPORT_OPTS)));
13797
+ return (0, import_viem17.fallback)(rpcUrls.map((url) => (0, import_viem17.http)(url, HTTP_TRANSPORT_OPTS)));
13565
13798
  }
13566
13799
  function walletClientCacheKey(chainId, accountAddress) {
13567
13800
  return `${chainId}:${accountAddress.toLowerCase()}`;
@@ -13587,7 +13820,7 @@ var ViemClientFactoryImpl = class {
13587
13820
  if (!this.publicClients.has(chainConfig.chainId)) {
13588
13821
  const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
13589
13822
  const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
13590
- const publicClient = (0, import_viem15.createPublicClient)({
13823
+ const publicClient = (0, import_viem17.createPublicClient)({
13591
13824
  chain,
13592
13825
  transport: buildTransport(urls)
13593
13826
  });
@@ -13600,7 +13833,7 @@ var ViemClientFactoryImpl = class {
13600
13833
  if (!this.walletClients.has(key)) {
13601
13834
  const chain = (0, import_sdk6.getViemChain)(chainConfig.chainId);
13602
13835
  const urls = rpcUrls && rpcUrls.length > 0 ? rpcUrls : [chainConfig.rpcUrl];
13603
- const walletClient = (0, import_viem15.createWalletClient)({
13836
+ const walletClient = (0, import_viem17.createWalletClient)({
13604
13837
  chain,
13605
13838
  account,
13606
13839
  transport: buildTransport(urls)
@@ -13615,7 +13848,7 @@ var ChainClientManager = class {
13615
13848
  this.clientFactory = new ViemClientFactoryImpl();
13616
13849
  this.quorumClients = /* @__PURE__ */ new Map();
13617
13850
  this.configService = configService;
13618
- this.signer = signer ?? privateKeySigner((0, import_accounts3.generatePrivateKey)());
13851
+ this.signer = signer ?? privateKeySigner((0, import_accounts4.generatePrivateKey)());
13619
13852
  this.account = accountFor(this.signer);
13620
13853
  }
13621
13854
  /** This filler's logging destination, relayed from the config service. */
@@ -13684,9 +13917,47 @@ var ChainClientManager = class {
13684
13917
  };
13685
13918
 
13686
13919
  // src/services/ContractInteractionService.ts
13687
- var import_viem21 = require("viem");
13920
+ var import_viem23 = require("viem");
13688
13921
  var import_sdk7 = require("@hyperbridge/sdk");
13689
13922
 
13923
+ // src/config/abis/SolverAccount.ts
13924
+ var SOLVER_ACCOUNT_ABI = [
13925
+ {
13926
+ inputs: [
13927
+ { name: "orderId", type: "bytes32" },
13928
+ { name: "cap", type: "uint256" },
13929
+ { name: "token", type: "address" },
13930
+ { name: "approved", type: "uint256" },
13931
+ { name: "fee", type: "uint256" }
13932
+ ],
13933
+ name: "debitOrder",
13934
+ outputs: [],
13935
+ stateMutability: "nonpayable",
13936
+ type: "function"
13937
+ },
13938
+ {
13939
+ inputs: [{ name: "orderId", type: "bytes32" }],
13940
+ name: "spent",
13941
+ outputs: [{ name: "", type: "uint256" }],
13942
+ stateMutability: "view",
13943
+ type: "function"
13944
+ },
13945
+ {
13946
+ inputs: [{ name: "sender", type: "address" }],
13947
+ name: "AccountUnauthorized",
13948
+ type: "error"
13949
+ },
13950
+ {
13951
+ inputs: [
13952
+ { name: "orderId", type: "bytes32" },
13953
+ { name: "total", type: "uint256" },
13954
+ { name: "cap", type: "uint256" }
13955
+ ],
13956
+ name: "LimitOrderExceeded",
13957
+ type: "error"
13958
+ }
13959
+ ];
13960
+
13690
13961
  // src/config/abis/EvmHost.ts
13691
13962
  var EVM_HOST = [
13692
13963
  {
@@ -17539,10 +17810,10 @@ var ENTRYPOINT_ABI = [
17539
17810
  ];
17540
17811
 
17541
17812
  // src/services/paymaster/provider/simplex.ts
17542
- var import_viem20 = require("viem");
17813
+ var import_viem22 = require("viem");
17543
17814
 
17544
17815
  // src/services/paymaster/types.ts
17545
- var import_viem16 = require("viem");
17816
+ var import_viem18 = require("viem");
17546
17817
  var RECOMMENDED_AMOUNT_USD = 5n;
17547
17818
  var VERIFICATION_GAS_LIMIT_PERMIT2 = 200000n;
17548
17819
  var VERIFICATION_GAS_LIMIT_PERMIT = 250000n;
@@ -17550,19 +17821,19 @@ var PERMIT2_DEADLINE_SECONDS = 3600n;
17550
17821
  var POST_OP_GAS_LIMIT_SIMPLEX = 40000n;
17551
17822
  var DEPOSIT_HEADROOM_PERCENT = 150n;
17552
17823
  function packPaymasterAndData(pm) {
17553
- return (0, import_viem16.encodePacked)(
17824
+ return (0, import_viem18.encodePacked)(
17554
17825
  ["address", "uint128", "uint128", "bytes"],
17555
17826
  [pm.paymaster, pm.paymasterVerificationGasLimit, pm.paymasterPostOpGasLimit, pm.paymasterData]
17556
17827
  );
17557
17828
  }
17558
17829
 
17559
17830
  // src/services/paymaster/permit.ts
17560
- var import_viem19 = require("viem");
17831
+ var import_viem21 = require("viem");
17561
17832
 
17562
17833
  // src/config/abis/EIP2612.ts
17563
- var import_viem17 = require("viem");
17834
+ var import_viem19 = require("viem");
17564
17835
  var EIP2612_ABI = [
17565
- ...import_viem17.erc20Abi,
17836
+ ...import_viem19.erc20Abi,
17566
17837
  {
17567
17838
  inputs: [{ internalType: "address", name: "owner", type: "address" }],
17568
17839
  stateMutability: "view",
@@ -17580,7 +17851,7 @@ var EIP2612_ABI = [
17580
17851
  ];
17581
17852
 
17582
17853
  // src/services/paymaster/permit2.ts
17583
- var import_viem18 = require("viem");
17854
+ var import_viem20 = require("viem");
17584
17855
  function permit2TransferTypedData(p) {
17585
17856
  return {
17586
17857
  types: {
@@ -17615,16 +17886,16 @@ function permit2TransferTypedData(p) {
17615
17886
  };
17616
17887
  }
17617
17888
  function randomPermit2Nonce() {
17618
- return (0, import_viem18.bytesToBigInt)(crypto.getRandomValues(new Uint8Array(32)));
17889
+ return (0, import_viem20.bytesToBigInt)(crypto.getRandomValues(new Uint8Array(32)));
17619
17890
  }
17620
17891
  async function signPermit2Transfer(signer, p) {
17621
17892
  const signature = await signer.signTypedData(permit2TransferTypedData(p));
17622
17893
  return normalizeSignature65(signature);
17623
17894
  }
17624
17895
  function normalizeSignature65(signature) {
17625
- const length = (0, import_viem18.size)(signature);
17896
+ const length = (0, import_viem20.size)(signature);
17626
17897
  if (length === 64) {
17627
- return (0, import_viem18.serializeSignature)((0, import_viem18.compactSignatureToSignature)((0, import_viem18.parseCompactSignature)(signature)));
17898
+ return (0, import_viem20.serializeSignature)((0, import_viem20.compactSignatureToSignature)((0, import_viem20.parseCompactSignature)(signature)));
17628
17899
  }
17629
17900
  if (length !== 65) {
17630
17901
  throw new Error(`Permit2 signature must be 64 or 65 bytes, got ${length}`);
@@ -17638,7 +17909,7 @@ function normalizeSignature65(signature) {
17638
17909
 
17639
17910
  // src/services/paymaster/permit.ts
17640
17911
  async function signEip2612Permit(client, signer, owner, spender, tokenAddress, value, chainId) {
17641
- const token = (0, import_viem19.getContract)({
17912
+ const token = (0, import_viem21.getContract)({
17642
17913
  client,
17643
17914
  address: tokenAddress,
17644
17915
  abi: EIP2612_ABI
@@ -17676,7 +17947,7 @@ async function signEip2612Permit(client, signer, owner, spender, tokenAddress, v
17676
17947
  spender,
17677
17948
  value,
17678
17949
  nonce,
17679
- deadline: import_viem19.maxUint256
17950
+ deadline: import_viem21.maxUint256
17680
17951
  }
17681
17952
  };
17682
17953
  return normalizeSignature65(await signer.signTypedData(typedData));
@@ -17804,7 +18075,7 @@ async function selectToken(client, solverAccount, tokens, permit2) {
17804
18075
  for (const token of tokens) {
17805
18076
  const balance = await client.readContract({
17806
18077
  address: token.address,
17807
- abi: import_viem20.erc20Abi,
18078
+ abi: import_viem22.erc20Abi,
17808
18079
  functionName: "balanceOf",
17809
18080
  args: [solverAccount]
17810
18081
  });
@@ -17834,7 +18105,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
17834
18105
  permit2Support.set(key, { supported: true, checkedAt: Date.now() });
17835
18106
  return true;
17836
18107
  } catch (error) {
17837
- const isRevert = error instanceof import_viem20.BaseError && error.walk((e) => e instanceof import_viem20.ContractFunctionRevertedError || e instanceof import_viem20.ContractFunctionZeroDataError);
18108
+ const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
17838
18109
  if (isRevert) {
17839
18110
  permit2Support.set(key, { supported: false, checkedAt: Date.now() });
17840
18111
  return false;
@@ -17845,7 +18116,7 @@ async function paymasterSupportsPermit2(client, chainId, paymasterAddress) {
17845
18116
  async function readAllowance(client, token, owner, spender) {
17846
18117
  return await client.readContract({
17847
18118
  address: token,
17848
- abi: import_viem20.erc20Abi,
18119
+ abi: import_viem22.erc20Abi,
17849
18120
  functionName: "allowance",
17850
18121
  args: [owner, spender]
17851
18122
  });
@@ -17865,7 +18136,7 @@ async function buildPermit2Mode(signer, p) {
17865
18136
  const r = `0x${signature.slice(2, 66)}`;
17866
18137
  const s = `0x${signature.slice(66, 130)}`;
17867
18138
  const v = Number.parseInt(signature.slice(130, 132), 16);
17868
- const paymasterData = (0, import_viem20.encodePacked)(
18139
+ const paymasterData = (0, import_viem22.encodePacked)(
17869
18140
  ["uint8", "address", "uint256", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
17870
18141
  [2, p.token, p.amount, nonce, deadline, v, r, s]
17871
18142
  );
@@ -17889,9 +18160,9 @@ async function buildPermitMode(client, signer, solverAccount, paymasterAddress,
17889
18160
  const r = `0x${permitSignature.slice(2, 66)}`;
17890
18161
  const s = `0x${permitSignature.slice(66, 130)}`;
17891
18162
  const v = Number.parseInt(permitSignature.slice(130, 132), 16);
17892
- const paymasterData = (0, import_viem20.encodePacked)(
18163
+ const paymasterData = (0, import_viem22.encodePacked)(
17893
18164
  ["uint8", "address", "uint256", "uint256", "uint8", "bytes32", "bytes32"],
17894
- [0, tokenAddress, permitAmount, import_viem20.maxUint256, v, r, s]
18165
+ [0, tokenAddress, permitAmount, import_viem22.maxUint256, v, r, s]
17895
18166
  );
17896
18167
  return {
17897
18168
  paymaster: paymasterAddress,
@@ -17905,7 +18176,7 @@ async function ensureFundedApprove(client, walletClient, chainId, solverAccount,
17905
18176
  const key = `${chainId}:${tokenAddress.toLowerCase()}:${solverAccount.toLowerCase()}`;
17906
18177
  const inFlight = approvalsInFlight.get(key);
17907
18178
  if (inFlight) return inFlight;
17908
- const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender, import_viem20.maxUint256).finally(
18179
+ const pending = sendFundedApprove(client, walletClient, solverAccount, tokenAddress, spender, import_viem22.maxUint256).finally(
17909
18180
  () => {
17910
18181
  approvalsInFlight.delete(key);
17911
18182
  }
@@ -17923,7 +18194,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
17923
18194
  const requiredNative = APPROVE_TX_GAS * gasPrice * (needsReset ? 2n : 1n);
17924
18195
  if (nativeBalance < requiredNative) {
17925
18196
  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}`
18197
+ `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
18198
  );
17928
18199
  }
17929
18200
  if (needsReset) {
@@ -17934,7 +18205,7 @@ async function sendFundedApprove(client, walletClient, solverAccount, tokenAddre
17934
18205
  async function sendApproveTx(client, walletClient, tokenAddress, spender, amount) {
17935
18206
  const hash = await walletClient.writeContract({
17936
18207
  address: tokenAddress,
17937
- abi: import_viem20.erc20Abi,
18208
+ abi: import_viem22.erc20Abi,
17938
18209
  functionName: "approve",
17939
18210
  args: [spender, amount],
17940
18211
  chain: walletClient.chain,
@@ -17959,7 +18230,7 @@ async function tokenSupportsPermit(client, tokenAddress) {
17959
18230
  });
17960
18231
  return true;
17961
18232
  } catch (error) {
17962
- const isRevert = error instanceof import_viem20.BaseError && error.walk((e) => e instanceof import_viem20.ContractFunctionRevertedError || e instanceof import_viem20.ContractFunctionZeroDataError);
18233
+ const isRevert = error instanceof import_viem22.BaseError && error.walk((e) => e instanceof import_viem22.ContractFunctionRevertedError || e instanceof import_viem22.ContractFunctionZeroDataError);
17963
18234
  if (isRevert) return false;
17964
18235
  throw error;
17965
18236
  }
@@ -18066,6 +18337,10 @@ function describeShortfall(balances) {
18066
18337
  // src/services/ContractInteractionService.ts
18067
18338
  import_decimal2.Decimal.config({ precision: 28, rounding: 4 });
18068
18339
  var FUNDING_GAS_PER_CALL = 400000n;
18340
+ var DEBIT_ORDER_GAS = 60000n;
18341
+ function settles(budget) {
18342
+ return !!budget && budget.token.toLowerCase() !== import_sdk7.ADDRESS_ZERO;
18343
+ }
18069
18344
  var LIMIT_ORDER_CALL_GAS_LIMIT = 500000n;
18070
18345
  var LIMIT_ORDER_VERIFICATION_GAS_LIMIT = 150000n;
18071
18346
  var LIMIT_ORDER_PRE_VERIFICATION_GAS = 50000n;
@@ -18255,17 +18530,19 @@ var ContractInteractionService = class {
18255
18530
  }
18256
18531
  /**
18257
18532
  * The call gas a bid needs: the shared estimate plus room for the funding calls
18258
- * this bid prepends.
18533
+ * this bid prepends and for settling its limit order's budget.
18259
18534
  *
18260
18535
  * Funding calls are not simulated (the V4 PositionManager's flash accounting does
18261
18536
  * not resolve in the bundler's estimation context), so each one adds a fixed
18262
18537
  * allowance for its modifyLiquidities + take. They differ between the bids on one
18263
18538
  * order, since each draws on its own limit order and so on its own funding, which
18264
18539
  * is why this is worked out per bid rather than baked into the cached estimate.
18540
+ * The estimate is of a lone fillOrder, so the budget call is not in it either.
18265
18541
  */
18266
- callGasLimitFor(order, baseCallGasLimit) {
18542
+ callGasLimitFor(order, baseCallGasLimit, budget) {
18267
18543
  const funding = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
18268
- return baseCallGasLimit + FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
18544
+ const fundingGas = FUNDING_GAS_PER_CALL * BigInt(funding?.calls?.length ?? 0);
18545
+ return baseCallGasLimit + fundingGas + (settles(budget) ? DEBIT_ORDER_GAS : 0n);
18269
18546
  }
18270
18547
  /**
18271
18548
  * Gets the fee token address and decimals for a given chain.
@@ -18337,7 +18614,7 @@ var ContractInteractionService = class {
18337
18614
  this.logger.warn(
18338
18615
  {
18339
18616
  chain,
18340
- walletBalance: (0, import_viem21.formatEther)(solverBalance),
18617
+ walletBalance: (0, import_viem23.formatEther)(solverBalance),
18341
18618
  walletGasUnits: walletGasUnits.toString(),
18342
18619
  gasPrice: gasPrice.toString()
18343
18620
  },
@@ -18352,10 +18629,10 @@ var ContractInteractionService = class {
18352
18629
  this.logger.info(
18353
18630
  {
18354
18631
  chain,
18355
- currentDeposit: (0, import_viem21.formatEther)(currentDeposit),
18632
+ currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
18356
18633
  depositGasUnits: depositGasUnits.toString(),
18357
18634
  targetGasUnits: targetGasUnits.toString(),
18358
- walletBalance: (0, import_viem21.formatEther)(solverBalance)
18635
+ walletBalance: (0, import_viem23.formatEther)(solverBalance)
18359
18636
  },
18360
18637
  "EntryPoint deposit covers target gas units, no top-up needed"
18361
18638
  );
@@ -18366,8 +18643,8 @@ var ContractInteractionService = class {
18366
18643
  this.logger.warn(
18367
18644
  {
18368
18645
  chain,
18369
- deficit: (0, import_viem21.formatEther)(deficit),
18370
- solverBalance: (0, import_viem21.formatEther)(solverBalance),
18646
+ deficit: (0, import_viem23.formatEther)(deficit),
18647
+ solverBalance: (0, import_viem23.formatEther)(solverBalance),
18371
18648
  depositGasUnits: depositGasUnits.toString(),
18372
18649
  targetGasUnits: targetGasUnits.toString()
18373
18650
  },
@@ -18379,10 +18656,10 @@ var ContractInteractionService = class {
18379
18656
  this.logger.info(
18380
18657
  {
18381
18658
  chain,
18382
- currentDeposit: (0, import_viem21.formatEther)(currentDeposit),
18659
+ currentDeposit: (0, import_viem23.formatEther)(currentDeposit),
18383
18660
  depositGasUnits: depositGasUnits.toString(),
18384
18661
  targetGasUnits: targetGasUnits.toString(),
18385
- topUpAmount: (0, import_viem21.formatEther)(deficit)
18662
+ topUpAmount: (0, import_viem23.formatEther)(deficit)
18386
18663
  },
18387
18664
  "Topping up EntryPoint deposit to cover target gas units"
18388
18665
  );
@@ -18405,7 +18682,7 @@ var ContractInteractionService = class {
18405
18682
  const addr = tokenAddress.toLowerCase();
18406
18683
  if (addr !== sourceUsdc && addr !== sourceUsdt) continue;
18407
18684
  const decimals = await this.getTokenDecimals(tokenAddress, order.source);
18408
- const tokenAmount = new import_decimal2.Decimal((0, import_viem21.formatUnits)(input.amount, decimals));
18685
+ const tokenAmount = new import_decimal2.Decimal((0, import_viem23.formatUnits)(input.amount, decimals));
18409
18686
  inputUsdValue = inputUsdValue.plus(tokenAmount);
18410
18687
  }
18411
18688
  return inputUsdValue;
@@ -18447,6 +18724,27 @@ var ContractInteractionService = class {
18447
18724
  { maxRetries: 3, backoffMs: 250, logMessage: "Failed to read token balance" }
18448
18725
  );
18449
18726
  }
18727
+ /**
18728
+ * What the solver's account has tallied against a limit order's budget on `chain`, in
18729
+ * the paid token's own units, or null when it cannot be read.
18730
+ *
18731
+ * Null rather than a throw: an account whose implementation keeps no tally answers
18732
+ * with a revert or with no data, and the caller carries on without the figure either
18733
+ * way. Not retried, since that answer does not change on a second ask.
18734
+ */
18735
+ async limitOrderSpent(chain, budgetId) {
18736
+ try {
18737
+ return await this.clientManager.getPublicClient(chain).readContract({
18738
+ address: this.solverAccountAddress,
18739
+ abi: SOLVER_ACCOUNT_ABI,
18740
+ functionName: "spent",
18741
+ args: [budgetId]
18742
+ });
18743
+ } catch (err) {
18744
+ this.logger.warn({ err, chain, budgetId }, "Could not read the limit order's tally from the solver account");
18745
+ return null;
18746
+ }
18747
+ }
18450
18748
  async getSolverEntryPointBalance(chain) {
18451
18749
  const entryPointAddress = this.configService.getEntryPointAddress(chain);
18452
18750
  if (!entryPointAddress) {
@@ -18475,7 +18773,7 @@ var ContractInteractionService = class {
18475
18773
  const walletClient = this.clientManager.getWalletClient(chain);
18476
18774
  const publicClient = this.clientManager.getPublicClient(chain);
18477
18775
  this.logger.info(
18478
- { chain, solver: this.solverAccountAddress, amount: (0, import_viem21.formatEther)(amount) },
18776
+ { chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(amount) },
18479
18777
  "Depositing to EntryPoint"
18480
18778
  );
18481
18779
  const hash = await walletClient.writeContract({
@@ -18490,7 +18788,7 @@ var ContractInteractionService = class {
18490
18788
  if (receipt.status !== "success") {
18491
18789
  throw new Error(`EntryPoint deposit transaction reverted: ${hash}`);
18492
18790
  }
18493
- this.logger.info({ chain, txHash: hash, amount: (0, import_viem21.formatEther)(amount) }, "EntryPoint deposit confirmed");
18791
+ this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(amount) }, "EntryPoint deposit confirmed");
18494
18792
  return hash;
18495
18793
  }
18496
18794
  /**
@@ -18510,7 +18808,7 @@ var ContractInteractionService = class {
18510
18808
  const walletClient = this.clientManager.getWalletClient(chain);
18511
18809
  const publicClient = this.clientManager.getPublicClient(chain);
18512
18810
  this.logger.info(
18513
- { chain, solver: this.solverAccountAddress, amount: (0, import_viem21.formatEther)(balance) },
18811
+ { chain, solver: this.solverAccountAddress, amount: (0, import_viem23.formatEther)(balance) },
18514
18812
  "Withdrawing from EntryPoint"
18515
18813
  );
18516
18814
  const hash = await walletClient.writeContract({
@@ -18524,7 +18822,7 @@ var ContractInteractionService = class {
18524
18822
  if (receipt.status !== "success") {
18525
18823
  throw new Error(`EntryPoint withdrawal transaction reverted: ${hash}`);
18526
18824
  }
18527
- this.logger.info({ chain, txHash: hash, amount: (0, import_viem21.formatEther)(balance) }, "EntryPoint withdrawal confirmed");
18825
+ this.logger.info({ chain, txHash: hash, amount: (0, import_viem23.formatEther)(balance) }, "EntryPoint withdrawal confirmed");
18528
18826
  return hash;
18529
18827
  }
18530
18828
  /**
@@ -18587,7 +18885,8 @@ var ContractInteractionService = class {
18587
18885
  if (!cachedEstimate) {
18588
18886
  throw new Error(`No cached gas estimate found for order ${order.id}. Call estimateGasFillPost first.`);
18589
18887
  }
18590
- const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit);
18888
+ const budget = this.cacheService.getBidBudget(order.id);
18889
+ const callGasLimit = this.callGasLimitFor(order, cachedEstimate.callGasLimit, budget);
18591
18890
  const cachedFillerOutputs = this.cacheService.getFillerOutputs(order.id);
18592
18891
  const cachedFillerInputs = this.cacheService.getFillerInputs(order.id);
18593
18892
  if (!cachedFillerOutputs || !cachedFillerInputs) {
@@ -18614,7 +18913,8 @@ var ContractInteractionService = class {
18614
18913
  order,
18615
18914
  cachedFillerOutputs,
18616
18915
  fillOptions,
18617
- cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount
18916
+ cachedEstimate.totalCostInSourceFeeToken + dispatchFeeTokenAmount,
18917
+ budget ? { budget, solverAccount: solverAccountAddress } : void 0
18618
18918
  );
18619
18919
  const commitment = (0, import_sdk7.orderCommitment)(order);
18620
18920
  const nonce = await this.clientManager.getPublicClient(order.destination).readContract({
@@ -18760,7 +19060,7 @@ var ContractInteractionService = class {
18760
19060
  {
18761
19061
  target: outputToken,
18762
19062
  value: 0n,
18763
- data: (0, import_viem21.encodeFunctionData)({
19063
+ data: (0, import_viem23.encodeFunctionData)({
18764
19064
  abi: ERC20_ABI,
18765
19065
  functionName: "approve",
18766
19066
  args: [gateway, outputAmount]
@@ -18806,8 +19106,13 @@ var ContractInteractionService = class {
18806
19106
  * Same-chain fills release escrow locally with no Hyperbridge dispatch, so the
18807
19107
  * gateway never pulls the fee token — its approval is skipped. Only cross-chain
18808
19108
  * fills, which dispatch a RedeemEscrow message paid in the fee token, need it.
19109
+ *
19110
+ * A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
19111
+ * as its `orderId`, works out what the fill paid from what is left of the allowance
19112
+ * and reverts the whole batch if the order's payouts would pass its size. It has to
19113
+ * come last, after the gateway has drawn on the allowance.
18809
19114
  */
18810
- async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount) {
19115
+ async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount, settlement) {
18811
19116
  const chain = order.destination;
18812
19117
  const intentGatewayV2Address = this.configService.getIntentGatewayAddress(chain);
18813
19118
  const perTokenRequired = /* @__PURE__ */ new Map();
@@ -18817,9 +19122,10 @@ var ContractInteractionService = class {
18817
19122
  const key = addr.toLowerCase();
18818
19123
  perTokenRequired.set(key, (perTokenRequired.get(key) ?? 0n) + output.amount);
18819
19124
  }
19125
+ let feeKey;
18820
19126
  if (order.source !== order.destination) {
18821
19127
  const feeToken = await this.getFeeTokenWithDecimals(chain);
18822
- const feeKey = feeToken.address.toLowerCase();
19128
+ feeKey = feeToken.address.toLowerCase();
18823
19129
  perTokenRequired.set(feeKey, (perTokenRequired.get(feeKey) ?? 0n) + requiredFeeTokenAmount);
18824
19130
  }
18825
19131
  const fundingPrepends = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
@@ -18831,7 +19137,7 @@ var ContractInteractionService = class {
18831
19137
  calls.push({
18832
19138
  target: tokenAddress,
18833
19139
  value: 0n,
18834
- data: (0, import_viem21.encodeFunctionData)({
19140
+ data: (0, import_viem23.encodeFunctionData)({
18835
19141
  abi: ERC20_ABI,
18836
19142
  functionName: "approve",
18837
19143
  args: [intentGatewayV2Address, amount]
@@ -18845,17 +19151,37 @@ var ContractInteractionService = class {
18845
19151
  value: nativeOutputValue,
18846
19152
  data: (0, import_sdk7.encodeFillOrder)((0, import_sdk7.transformOrderForContract)(order), fillOptions)
18847
19153
  });
19154
+ if (settlement && settles(settlement.budget)) {
19155
+ const { budget, solverAccount } = settlement;
19156
+ const budgetToken = budget.token.toLowerCase();
19157
+ const approved = perTokenRequired.get(budgetToken) ?? 0n;
19158
+ if (approved === 0n) {
19159
+ throw new Error(
19160
+ `Bid on order ${order.id} approves nothing in ${budget.token}, the token budget ${budget.budgetId} is measured in`
19161
+ );
19162
+ }
19163
+ const fee = feeKey === budgetToken ? fillOptions.relayerFee : 0n;
19164
+ calls.push({
19165
+ target: solverAccount,
19166
+ value: 0n,
19167
+ data: (0, import_viem23.encodeFunctionData)({
19168
+ abi: SOLVER_ACCOUNT_ABI,
19169
+ functionName: "debitOrder",
19170
+ args: [budget.budgetId, budget.cap, budgetToken, approved, fee]
19171
+ })
19172
+ });
19173
+ }
18848
19174
  return (0, import_sdk7.encodeERC7821ExecuteBatch)(calls);
18849
19175
  }
18850
19176
  };
18851
19177
 
18852
19178
  // src/services/DelegationService.ts
18853
- var import_viem23 = require("viem");
19179
+ var import_viem25 = require("viem");
18854
19180
  var import_sdk9 = require("@hyperbridge/sdk");
18855
19181
 
18856
19182
  // src/services/UserOpSender.ts
18857
19183
  var import_sdk8 = require("@hyperbridge/sdk");
18858
- var import_viem22 = require("viem");
19184
+ var import_viem24 = require("viem");
18859
19185
  var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
18860
19186
  var FALLBACK_CALL_GAS_LIMIT = 1500000n;
18861
19187
  var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
@@ -18928,11 +19254,11 @@ var UserOpSender = class {
18928
19254
  });
18929
19255
  const formattedAuth = auth ? {
18930
19256
  address: auth.address,
18931
- chainId: (0, import_viem22.toHex)(auth.chainId),
18932
- nonce: (0, import_viem22.toHex)(auth.nonce),
19257
+ chainId: (0, import_viem24.toHex)(auth.chainId),
19258
+ nonce: (0, import_viem24.toHex)(auth.nonce),
18933
19259
  r: auth.r,
18934
19260
  s: auth.s,
18935
- yParity: (0, import_viem22.toHex)(auth.yParity)
19261
+ yParity: (0, import_viem24.toHex)(auth.yParity)
18936
19262
  } : void 0;
18937
19263
  const { verificationGasLimit, callGasLimit, preVerificationGas } = gas ?? await this.estimateGas({
18938
19264
  bundlerUrl,
@@ -19100,10 +19426,10 @@ function bootstrapCallData(approval) {
19100
19426
  {
19101
19427
  target: approval.token,
19102
19428
  value: 0n,
19103
- data: (0, import_viem23.encodeFunctionData)({
19104
- abi: import_viem23.erc20Abi,
19429
+ data: (0, import_viem25.encodeFunctionData)({
19430
+ abi: import_viem25.erc20Abi,
19105
19431
  functionName: "approve",
19106
- args: [approval.spender, import_viem23.maxUint256]
19432
+ args: [approval.spender, import_viem25.maxUint256]
19107
19433
  })
19108
19434
  }
19109
19435
  ]);
@@ -19154,10 +19480,10 @@ var DelegationService = class {
19154
19480
  return await walletClient.sendTransaction({
19155
19481
  to: approval.token,
19156
19482
  value: 0n,
19157
- data: (0, import_viem23.encodeFunctionData)({
19158
- abi: import_viem23.erc20Abi,
19483
+ data: (0, import_viem25.encodeFunctionData)({
19484
+ abi: import_viem25.erc20Abi,
19159
19485
  functionName: "approve",
19160
- args: [approval.spender, import_viem23.maxUint256]
19486
+ args: [approval.spender, import_viem25.maxUint256]
19161
19487
  }),
19162
19488
  authorizationList: [authorization],
19163
19489
  chain: walletClient.chain,
@@ -19391,8 +19717,8 @@ var DelegationService = class {
19391
19717
  {
19392
19718
  chain,
19393
19719
  authority,
19394
- nativeBalance: (0, import_viem23.formatEther)(native.nativeBalance),
19395
- requiredNative: (0, import_viem23.formatEther)(native.requiredNative)
19720
+ nativeBalance: (0, import_viem25.formatEther)(native.nativeBalance),
19721
+ requiredNative: (0, import_viem25.formatEther)(native.requiredNative)
19396
19722
  },
19397
19723
  "Delegation failed: insufficient native balance for direct EIP-7702 tx and paymaster path unavailable"
19398
19724
  );
@@ -19467,7 +19793,7 @@ var DelegationService = class {
19467
19793
  { chain, authority: this.signer.address, mode: this.signer.mode ?? "custom" },
19468
19794
  "Revoking EIP-7702 delegation"
19469
19795
  );
19470
- const authorization = await this.buildAuthorization(chain, import_viem23.zeroAddress);
19796
+ const authorization = await this.buildAuthorization(chain, import_viem25.zeroAddress);
19471
19797
  const hash = await this.sendDelegationTransaction(chain, authorization);
19472
19798
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
19473
19799
  if (receipt.status === "success") {
@@ -19483,13 +19809,13 @@ var DelegationService = class {
19483
19809
  };
19484
19810
 
19485
19811
  // src/services/RebalancingService.ts
19486
- var import_viem26 = require("viem");
19812
+ var import_viem28 = require("viem");
19487
19813
  var import_sdk13 = require("@hyperbridge/sdk");
19488
19814
  var import_decimal3 = require("decimal.js");
19489
19815
 
19490
19816
  // src/services/rebalancers/binance.ts
19491
19817
  var import_wallet2 = require("@binance/wallet");
19492
- var import_viem24 = require("viem");
19818
+ var import_viem26 = require("viem");
19493
19819
  var import_sdk10 = require("@hyperbridge/sdk");
19494
19820
  var DEPOSIT_STATUS_SUCCESS = 1;
19495
19821
  var WITHDRAW_STATUS_COMPLETED = 6;
@@ -19801,7 +20127,7 @@ var BinanceRebalancer = class {
19801
20127
  abi: ERC20_ABI,
19802
20128
  functionName: "decimals"
19803
20129
  });
19804
- const amountWei = (0, import_viem24.parseUnits)(amount, Number(tokenDecimals));
20130
+ const amountWei = (0, import_viem26.parseUnits)(amount, Number(tokenDecimals));
19805
20131
  const balance = await publicClient.readContract({
19806
20132
  address: tokenAddress,
19807
20133
  abi: ERC20_ABI,
@@ -20025,7 +20351,7 @@ var CctpRebalancer = class {
20025
20351
  };
20026
20352
 
20027
20353
  // src/services/rebalancers/usdt0.ts
20028
- var import_viem25 = require("viem");
20354
+ var import_viem27 = require("viem");
20029
20355
  var import_sdk12 = require("@hyperbridge/sdk");
20030
20356
 
20031
20357
  // src/config/abis/Oft.ts
@@ -20188,7 +20514,7 @@ var Usdt0Rebalancer = class {
20188
20514
  const publicClient = this.chainClientManager.getPublicClient(source);
20189
20515
  const walletClient = this.chainClientManager.getWalletClient(source);
20190
20516
  const recipient = recipientAddress || this.account.address;
20191
- const amountWei = (0, import_viem25.parseUnits)(amount, 6);
20517
+ const amountWei = (0, import_viem27.parseUnits)(amount, 6);
20192
20518
  if (sourceChainId === 1) {
20193
20519
  const allowance = await publicClient.readContract({
20194
20520
  address: tokenAddress,
@@ -20201,7 +20527,7 @@ var Usdt0Rebalancer = class {
20201
20527
  address: tokenAddress,
20202
20528
  abi: ERC20_ABI,
20203
20529
  functionName: "approve",
20204
- args: [oftAddress, import_viem25.maxUint256],
20530
+ args: [oftAddress, import_viem27.maxUint256],
20205
20531
  chain: walletClient.chain
20206
20532
  });
20207
20533
  await publicClient.waitForTransactionReceipt({ hash: approveTx, confirmations: 1 });
@@ -20283,8 +20609,8 @@ var Usdt0Rebalancer = class {
20283
20609
  if (!oftAddress) throw new Error(`Chain ${source} not supported by USDT0`);
20284
20610
  const publicClient = this.chainClientManager.getPublicClient(source);
20285
20611
  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 });
20612
+ const amountWei = (0, import_viem27.parseUnits)(amount, 6);
20613
+ const recipientBytes32 = (0, import_viem27.padHex)(recipient, { size: 32 });
20288
20614
  const sendParam = {
20289
20615
  dstEid: destEid,
20290
20616
  to: recipientBytes32,
@@ -20636,7 +20962,7 @@ var RebalancingService = class {
20636
20962
  functionName: "balanceOf",
20637
20963
  args: [this.walletAddress]
20638
20964
  });
20639
- const balanceDecimal = new import_decimal3.Decimal((0, import_viem26.formatUnits)(balanceWei, Number(decimals)));
20965
+ const balanceDecimal = new import_decimal3.Decimal((0, import_viem28.formatUnits)(balanceWei, Number(decimals)));
20640
20966
  return balanceDecimal.toNumber();
20641
20967
  } catch (error) {
20642
20968
  this.logger.error({ error, chain, asset }, "Failed to get current balance");
@@ -20684,11 +21010,11 @@ function byBestRate(a, b) {
20684
21010
  return left === right ? 0 : left > right ? -1 : 1;
20685
21011
  }
20686
21012
  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;
21013
+ const take = hold.take === void 0 ? 0n : BigInt(hold.take);
21014
+ if (take === 0n || released === 0n) return toScaledUp(delivered.raw, delivered.decimals);
21015
+ const offered = toRaw(BigInt(hold.amount), delivered.decimals);
21016
+ const atRate = (offered * released + take - 1n) / take;
21017
+ return toScaledUp(atRate > delivered.raw ? atRate : delivered.raw, delivered.decimals);
20692
21018
  }
20693
21019
  var IntentFiller = class {
20694
21020
  constructor(chainConfigs, strategies, config, configService, chainClientManager, contractService, signer, scanners, rebalancingService, bidStorage, limitOrders, assetRegistry) {
@@ -20765,6 +21091,17 @@ var IntentFiller = class {
20765
21091
  setLimitOrderService(service) {
20766
21092
  this.limitOrderService = service;
20767
21093
  }
21094
+ /**
21095
+ * Brings the limit orders' `remaining` in line with their on-chain tallies.
21096
+ *
21097
+ * On the settlement queue, so the pass and a fill never resize the same order at
21098
+ * once. Resolves when the pass has run.
21099
+ */
21100
+ async reconcileLimitOrderTallies() {
21101
+ await this.settlementQueue.add(async () => {
21102
+ await this.limitOrderService?.reconcileTallies();
21103
+ });
21104
+ }
20768
21105
  /** Whether the given chain id is configured for watch-only (monitor, never fill). */
20769
21106
  isChainWatchOnly(chainId) {
20770
21107
  const watchOnly = this.config.watchOnly;
@@ -21410,7 +21747,7 @@ var IntentFiller = class {
21410
21747
  let reservation = [];
21411
21748
  if (plan) {
21412
21749
  if (order.id) {
21413
- cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs);
21750
+ cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs, plan.budget);
21414
21751
  cache.setPartialFill(order.id, plan.partialFill);
21415
21752
  if (plan.fundingCalls.length > 0) cache.setFundingPrepends(order.id, plan.fundingCalls);
21416
21753
  else cache.clearFundingPrepends(order.id);
@@ -21611,7 +21948,7 @@ var IntentFiller = class {
21611
21948
  await this.releaseAll(claimed);
21612
21949
  return;
21613
21950
  }
21614
- const settled = executedHold(claimed, delivered, released);
21951
+ const settled = executedHold(claimed, delivered.scaled, released);
21615
21952
  const charged = chargedFor(settled, delivered, released);
21616
21953
  const settledBid = rows.find(
21617
21954
  (row) => row.reservations.some(
@@ -21664,15 +22001,15 @@ var IntentFiller = class {
21664
22001
  if (delivered === null) return [];
21665
22002
  const settled = executedHold(
21666
22003
  holds.map((entry) => entry.hold),
21667
- delivered,
22004
+ delivered.scaled,
21668
22005
  released
21669
22006
  );
21670
22007
  const row = holds.find((entry) => entry.hold === settled).row;
21671
22008
  return this.bidStorage.claimReservation(commitment, row.bid ?? void 0);
21672
22009
  }
21673
22010
  /**
21674
- * What this fill delivered of the token the limit order pays, at 1e18, or null
21675
- * when the fill names none of it.
22011
+ * What this fill delivered of the token the limit order pays, or null when the
22012
+ * fill names none of it.
21676
22013
  */
21677
22014
  async deliveredAgainst(limitOrderId, chainId, outputs) {
21678
22015
  const order = await this.limitOrders?.get(limitOrderId);
@@ -21688,7 +22025,7 @@ var IntentFiller = class {
21688
22025
  }
21689
22026
  if (total === 0n) return null;
21690
22027
  const decimals = await this.contractService.getTokenDecimals(address, chain);
21691
- return toScaled(total, decimals);
22028
+ return { scaled: toScaled(total, decimals), raw: total, decimals };
21692
22029
  }
21693
22030
  /**
21694
22031
  * Gives back whatever this bid still holds against its limit order.
@@ -21778,90 +22115,9 @@ var IntentFiller = class {
21778
22115
 
21779
22116
  // src/strategies/fx.ts
21780
22117
  var import_sdk15 = require("@hyperbridge/sdk");
21781
- var import_viem27 = require("viem");
22118
+ var import_viem29 = require("viem");
21782
22119
  var import_decimal6 = require("decimal.js");
21783
22120
 
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
22121
  // src/orderbook/usd.ts
21866
22122
  var import_decimal5 = require("decimal.js");
21867
22123
  function limitOrderUsdEdges(orders) {
@@ -21916,9 +22172,13 @@ var FXFiller = class _FXFiller {
21916
22172
  */
21917
22173
  constructor(signer, configService, clientManager, contractService, pairs, registry, options) {
21918
22174
  this.name = "FXFiller";
21919
- /** Consecutive orders where overfill clamp activated. */
22175
+ /**
22176
+ * Consecutive orders where the overfill clamp activated. Dormant: the clamp
22177
+ * existed for venue-priced legs, which went with Uniswap V4 pricing, so every
22178
+ * outcome is now recorded unclamped and this never climbs. See `recordOrderOutcome`.
22179
+ */
21920
22180
  this.consecutiveClamps = 0;
21921
- /** Once set, the filler refuses all orders until restart — systemic pricing error suspected. */
22181
+ /** Once set, the filler refuses all orders until reset. Unreachable while the clamp is dormant. */
21922
22182
  this.halted = false;
21923
22183
  this.logger = moduleLogger(configService.loggers, "fx-simplex");
21924
22184
  const { confirmationPolicy, fundingVenues = [], limitOrders } = options ?? {};
@@ -21967,10 +22227,9 @@ var FXFiller = class _FXFiller {
21967
22227
  this.pairs.push(pair);
21968
22228
  }
21969
22229
  /**
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.
22230
+ * Removes a market from the running engine. At least one market must remain.
22231
+ * Matching reads the limit orders rather than the pairs, so removing a market
22232
+ * changes what the dashboard lists, not what the filler fills.
21974
22233
  */
21975
22234
  removePair(pair) {
21976
22235
  const index = this.pairs.indexOf(pair);
@@ -22026,22 +22285,20 @@ var FXFiller = class _FXFiller {
22026
22285
  }
22027
22286
  }
22028
22287
  /**
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.
22288
+ * Evaluates whether an order is profitable to fill against the operator's
22289
+ * limit orders and the filler's current token balances.
22031
22290
  *
22032
22291
  * 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.
22292
+ * - Match each (input, output) leg against the open limit orders.
22293
+ * - Build one bid per matching order, best offer first, each paying the
22294
+ * order's whole offer for the input at its own rate, capped by what the
22295
+ * order has left. The gateway credits the swapper the ask and splits
22296
+ * anything above it between the swapper and the protocol.
22297
+ * - Treat a payout below the ask as a partial fill, which only orders
22298
+ * without output calldata and without an earlier partial fill allow.
22299
+ * - Cap each payout by the filler's balance plus funding-venue withdrawals,
22300
+ * then gate it on fees (full fills) and the same-asset spread.
22301
+ * - Cache the resulting bids for later use in `executeOrder`.
22045
22302
  */
22046
22303
  async calculateProfitability(order) {
22047
22304
  if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
@@ -22059,6 +22316,7 @@ var FXFiller = class _FXFiller {
22059
22316
  const multiLeg = order.inputs.length > 1;
22060
22317
  const plans = [];
22061
22318
  const plannedOn = /* @__PURE__ */ new Map();
22319
+ const spentOn = /* @__PURE__ */ new Map();
22062
22320
  for (let leg = 0; leg < order.inputs.length; leg++) {
22063
22321
  const input = order.inputs[leg];
22064
22322
  const output = order.output.assets[leg];
@@ -22080,7 +22338,30 @@ var FXFiller = class _FXFiller {
22080
22338
  for (const candidate of matches) {
22081
22339
  let partialFill = false;
22082
22340
  const fundingCalls = [];
22083
- const left = candidate.available - (plannedOn.get(candidate.order.id) ?? 0n);
22341
+ const budget = budgetFor(candidate.order, outputToken, outputDecimals);
22342
+ if (!spentOn.has(candidate.order.id)) {
22343
+ spentOn.set(
22344
+ candidate.order.id,
22345
+ await this.contractService.limitOrderSpent(destChain, budget.budgetId)
22346
+ );
22347
+ }
22348
+ const spent = spentOn.get(candidate.order.id) ?? null;
22349
+ const room = spent === null ? candidate.available : budgetRoom(budget, spent, outputDecimals);
22350
+ if (room === 0n) {
22351
+ this.logger.info(
22352
+ {
22353
+ orderId: order.id,
22354
+ leg,
22355
+ limitOrder: candidate.order.id,
22356
+ spent: spent?.toString(),
22357
+ cap: budget.cap.toString()
22358
+ },
22359
+ "Skipping a bid: the limit order has nothing left on chain"
22360
+ );
22361
+ continue;
22362
+ }
22363
+ const available = room < candidate.available ? room : candidate.available;
22364
+ const left = available - (plannedOn.get(candidate.order.id) ?? 0n);
22084
22365
  if (left <= 0n) {
22085
22366
  this.logger.info(
22086
22367
  { orderId: order.id, leg, limitOrder: candidate.order.id },
@@ -22115,10 +22396,7 @@ var FXFiller = class _FXFiller {
22115
22396
  {
22116
22397
  orderId: order.id,
22117
22398
  limitOrder: candidate.order.id,
22118
- available: (0, import_viem27.formatUnits)(
22119
- candidate.available,
22120
- 18
22121
- ),
22399
+ available: (0, import_viem29.formatUnits)(available, 18),
22122
22400
  userRequested: output.amount.toString(),
22123
22401
  payout: targetOutput.toString(),
22124
22402
  crossChain: sourceChain !== destChain
@@ -22163,7 +22441,10 @@ var FXFiller = class _FXFiller {
22163
22441
  }
22164
22442
  }
22165
22443
  const effectiveBalance = walletContribution + credited;
22166
- const finalOutputAmount = effectiveBalance > targetOutput ? targetOutput : effectiveBalance;
22444
+ const finalOutputAmount = toRaw(
22445
+ toScaled(effectiveBalance > targetOutput ? targetOutput : effectiveBalance, outputDecimals),
22446
+ outputDecimals
22447
+ );
22167
22448
  if (finalOutputAmount === 0n) {
22168
22449
  this.logger.info(
22169
22450
  {
@@ -22252,7 +22533,7 @@ var FXFiller = class _FXFiller {
22252
22533
  const spread = convertedInput - finalOutputAmount;
22253
22534
  if (spread <= 0n) sameAssetProfitable = false;
22254
22535
  realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
22255
- const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0, import_viem27.formatUnits)(spread, outputDecimals)));
22536
+ const spreadUsd = outputSymbol && usdValueOf(usdFactors, outputSymbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(spread, outputDecimals)));
22256
22537
  if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
22257
22538
  }
22258
22539
  const payoutSurplusUsd = new import_decimal6.Decimal(0);
@@ -22267,9 +22548,9 @@ var FXFiller = class _FXFiller {
22267
22548
  {
22268
22549
  orderId: order.id,
22269
22550
  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)
22551
+ residual: (0, import_viem29.formatUnits)(residual, feeToken.decimals),
22552
+ dispatchFee: (0, import_viem29.formatUnits)(dispatchFee, feeToken.decimals),
22553
+ required: (0, import_viem29.formatUnits)(required, feeToken.decimals)
22273
22554
  },
22274
22555
  "Skipping order: fill leaves too little of the fee token to dispatch the escrow release"
22275
22556
  );
@@ -22282,10 +22563,10 @@ var FXFiller = class _FXFiller {
22282
22563
  this.logger.info(
22283
22564
  {
22284
22565
  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)
22566
+ orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
22567
+ fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22568
+ relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22569
+ executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals)
22289
22570
  },
22290
22571
  "Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
22291
22572
  );
@@ -22293,13 +22574,13 @@ var FXFiller = class _FXFiller {
22293
22574
  }
22294
22575
  if (sameAsset && !sameAssetProfitable) {
22295
22576
  this.logger.info(
22296
- { orderId: order.id, realizedSpreadProfit: (0, import_viem27.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22577
+ { orderId: order.id, realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22297
22578
  "Skipping order: a same-asset fill does not net a positive spread"
22298
22579
  );
22299
22580
  continue;
22300
22581
  }
22301
22582
  const feeProfit = order.fees - executionCost;
22302
- const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0, import_viem27.formatUnits)(feeProfit + realizedSpreadProfit, feeTokenDecimals));
22583
+ const totalProfit = partialFill ? partialEdgeUsd.toNumber() : Number.parseFloat((0, import_viem29.formatUnits)(feeProfit + realizedSpreadProfit, feeTokenDecimals));
22303
22584
  this.logger.info(
22304
22585
  {
22305
22586
  orderId: order.id,
@@ -22315,12 +22596,12 @@ var FXFiller = class _FXFiller {
22315
22596
  limitOrders: matches.length,
22316
22597
  available: candidate.available.toString(),
22317
22598
  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),
22599
+ orderFees: (0, import_viem29.formatUnits)(order.fees, feeTokenDecimals),
22600
+ fillGas: (0, import_viem29.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22601
+ relayerFee: (0, import_viem29.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22602
+ executionCost: (0, import_viem29.formatUnits)(executionCost, feeTokenDecimals),
22603
+ feeProfit: (0, import_viem29.formatUnits)(feeProfit, feeTokenDecimals),
22604
+ realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals),
22324
22605
  payoutSurplusUsd: payoutSurplusUsd.toString(),
22325
22606
  totalProfit,
22326
22607
  profitable: totalProfit > 0
@@ -22339,7 +22620,8 @@ var FXFiller = class _FXFiller {
22339
22620
  fillerInputs,
22340
22621
  fundingCalls: [...fundingCalls],
22341
22622
  partialFill,
22342
- profit: totalProfit
22623
+ profit: totalProfit,
22624
+ budget
22343
22625
  });
22344
22626
  if (!partialEligibleCheap) break;
22345
22627
  }
@@ -22347,7 +22629,12 @@ var FXFiller = class _FXFiller {
22347
22629
  if (plans.length === 0) return 0;
22348
22630
  if (order.id) {
22349
22631
  this.contractService.cacheService.setBidPlans(order.id, plans);
22350
- this.contractService.cacheService.setFillerOutputs(order.id, plans[0].fillerOutputs, plans[0].fillerInputs);
22632
+ this.contractService.cacheService.setFillerOutputs(
22633
+ order.id,
22634
+ plans[0].fillerOutputs,
22635
+ plans[0].fillerInputs,
22636
+ plans[0].budget
22637
+ );
22351
22638
  this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
22352
22639
  this.contractService.cacheService.setMatchedLimitOrder(order.id, [
22353
22640
  { limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
@@ -22442,10 +22729,14 @@ var FXFiller = class _FXFiller {
22442
22729
  // =========================================================================
22443
22730
  /**
22444
22731
  * 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.
22732
+ *
22733
+ * Dormant. Only venue-priced legs ever fed a clamped outcome here: a streak
22734
+ * of those meant a live market source had gone off (stale pool, manipulated
22735
+ * venue). Venue pricing went with Uniswap V4, and limit orders are prices the
22736
+ * operator set, so the only caller records `false` and the filler never
22737
+ * halts. `isHalted`, `resetHalt` and `[simplex.overfillProtection]
22738
+ * maxConsecutiveClamps` are kept so existing configs and the halt API still
22739
+ * load; they can go once nothing reads them.
22449
22740
  */
22450
22741
  recordOrderOutcome(clamped, orderId) {
22451
22742
  if (clamped) {
@@ -22471,18 +22762,6 @@ var FXFiller = class _FXFiller {
22471
22762
  this.consecutiveClamps = 0;
22472
22763
  this.logger.warn("FXFiller halt reset by operator \u2014 resuming order evaluation");
22473
22764
  }
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
22765
  /**
22487
22766
  * Whether any solver has already delivered output against this order.
22488
22767
  *
@@ -22622,7 +22901,7 @@ var FXFiller = class _FXFiller {
22622
22901
  const symbol = this.registry.symbolFor(inputToken, order.source);
22623
22902
  if (!symbol) return null;
22624
22903
  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)));
22904
+ const legUsd = usdValueOf(factors, symbol, new import_decimal6.Decimal((0, import_viem29.formatUnits)(input.amount, decimals)));
22626
22905
  if (!legUsd) return null;
22627
22906
  inputUsd = inputUsd.plus(legUsd);
22628
22907
  }
@@ -22635,7 +22914,7 @@ function minBigInt(first, ...rest) {
22635
22914
 
22636
22915
  // src/services/TokenSender.ts
22637
22916
  var import_sdk16 = require("@hyperbridge/sdk");
22638
- var import_viem28 = require("viem");
22917
+ var import_viem30 = require("viem");
22639
22918
  var TokenSender = class {
22640
22919
  constructor(clientManager, solver, getVaults, userOpSender, configService) {
22641
22920
  this.clientManager = clientManager;
@@ -22647,18 +22926,18 @@ var TokenSender = class {
22647
22926
  }
22648
22927
  async send(params) {
22649
22928
  const { chain, token, amount, to } = params;
22650
- if (!(0, import_viem28.isAddress)(to)) throw new Error(`Invalid recipient address: ${to}`);
22929
+ if (!(0, import_viem30.isAddress)(to)) throw new Error(`Invalid recipient address: ${to}`);
22651
22930
  const publicClient = this.clientManager.getPublicClient(chain);
22652
22931
  const calls = [];
22653
22932
  let redeemed = false;
22654
22933
  if (token === "native") {
22655
- const value = (0, import_viem28.parseUnits)(amount, 18);
22934
+ const value = (0, import_viem30.parseUnits)(amount, 18);
22656
22935
  if (value <= 0n) throw new Error("Amount must be greater than 0");
22657
22936
  const balance = await publicClient.getBalance({ address: this.solver });
22658
22937
  if (balance < value) throw new Error(`Insufficient native balance: have ${balance}, need ${value}`);
22659
22938
  calls.push({ target: to, value, data: "0x" });
22660
22939
  } else {
22661
- if (!(0, import_viem28.isAddress)(token)) throw new Error(`Invalid token address: ${token}`);
22940
+ if (!(0, import_viem30.isAddress)(token)) throw new Error(`Invalid token address: ${token}`);
22662
22941
  const tokenLower = token.toLowerCase();
22663
22942
  const [decimals, balance] = await Promise.all([
22664
22943
  publicClient.readContract({ abi: ERC20_ABI, address: token, functionName: "decimals" }),
@@ -22669,7 +22948,7 @@ var TokenSender = class {
22669
22948
  args: [this.solver]
22670
22949
  })
22671
22950
  ]);
22672
- const units = (0, import_viem28.parseUnits)(amount, decimals);
22951
+ const units = (0, import_viem30.parseUnits)(amount, decimals);
22673
22952
  if (units <= 0n) throw new Error("Amount must be greater than 0");
22674
22953
  const isVaultShare = this.getVaults().some(
22675
22954
  (v) => v.chain === chain && v.vault.toLowerCase() === tokenLower
@@ -22680,7 +22959,7 @@ var TokenSender = class {
22680
22959
  calls.push({
22681
22960
  target: token,
22682
22961
  value: 0n,
22683
- data: (0, import_viem28.encodeFunctionData)({ abi: ERC4626_ABI, functionName: "redeem", args: [units, to, this.solver] })
22962
+ data: (0, import_viem30.encodeFunctionData)({ abi: ERC4626_ABI, functionName: "redeem", args: [units, to, this.solver] })
22684
22963
  });
22685
22964
  } else {
22686
22965
  const reserve = this.userOpSender?.canSponsor(chain) && this.configService ? paymasterReserveForToken(chain, tokenLower, this.configService) : 0n;
@@ -22703,7 +22982,7 @@ var TokenSender = class {
22703
22982
  calls.push({
22704
22983
  target: token,
22705
22984
  value: 0n,
22706
- data: (0, import_viem28.encodeFunctionData)({ abi: ERC20_ABI, functionName: "transfer", args: [to, units] })
22985
+ data: (0, import_viem30.encodeFunctionData)({ abi: ERC20_ABI, functionName: "transfer", args: [to, units] })
22707
22986
  });
22708
22987
  }
22709
22988
  }
@@ -22746,7 +23025,7 @@ var TokenSender = class {
22746
23025
  });
22747
23026
  const required = shortfall > 0n ? shortfall : 0n;
22748
23027
  if (available < required) continue;
22749
- const floor = vault.minBalance === void 0 ? 0n : (0, import_viem28.parseUnits)(vault.minBalance, decimals);
23028
+ const floor = vault.minBalance === void 0 ? 0n : (0, import_viem30.parseUnits)(vault.minBalance, decimals);
22750
23029
  const headroom = reserve > floor ? reserve : floor;
22751
23030
  const wanted = shortfall + headroom;
22752
23031
  const amount = available >= wanted ? wanted : required;
@@ -22754,7 +23033,7 @@ var TokenSender = class {
22754
23033
  return {
22755
23034
  target: vault.vault,
22756
23035
  value: 0n,
22757
- data: (0, import_viem28.encodeFunctionData)({
23036
+ data: (0, import_viem30.encodeFunctionData)({
22758
23037
  abi: ERC4626_ABI,
22759
23038
  functionName: "withdraw",
22760
23039
  args: [amount, this.solver, this.solver]
@@ -22820,9 +23099,9 @@ var LimitOrderLifecycle = class {
22820
23099
  const reconcileMs = this.options.reconcileIntervalSecs * 1e3;
22821
23100
  this.every(heartbeatMs, "heartbeat", () => this.service.heartbeat());
22822
23101
  this.every(EXPIRY_SWEEP_MS, "expiry", () => this.expire());
22823
- this.every(reconcileMs, "reconciliation", () => this.reconcile());
23102
+ this.every(reconcileMs, "reconciliation", () => this.upkeep());
22824
23103
  this.logger.info({ heartbeatMs, expiryMs: EXPIRY_SWEEP_MS, reconcileMs }, "Orderbook lifecycle started");
22825
- void this.run("reconciliation", () => this.reconcile());
23104
+ void this.upkeep();
22826
23105
  }
22827
23106
  stop() {
22828
23107
  this.timers.forEach((timer) => clearInterval(timer));
@@ -22833,6 +23112,14 @@ var LimitOrderLifecycle = class {
22833
23112
  const expired = await this.service.expireStale();
22834
23113
  if (expired > 0) this.logger.info({ expired }, "Withdrew limit orders that had outlived their expiry");
22835
23114
  }
23115
+ /**
23116
+ * The tally pass first, so reconciliation against the orderbook reads the sizes
23117
+ * it left. Each fails on its own.
23118
+ */
23119
+ async upkeep() {
23120
+ await this.run("tally reconciliation", async () => this.options.reconcileTallies());
23121
+ await this.run("reconciliation", () => this.reconcile());
23122
+ }
22836
23123
  async reconcile() {
22837
23124
  const report = await this.service.reconcile();
22838
23125
  if (report.cancelled || report.reposted || report.underFunded) {
@@ -22868,7 +23155,7 @@ var LimitOrderLifecycle = class {
22868
23155
 
22869
23156
  // src/services/BalanceProvider.ts
22870
23157
  var import_node_events = require("node:events");
22871
- var import_viem29 = require("viem");
23158
+ var import_viem31 = require("viem");
22872
23159
  var BalanceProvider = class extends import_node_events.EventEmitter {
22873
23160
  constructor(options) {
22874
23161
  super();
@@ -22961,7 +23248,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
22961
23248
  try {
22962
23249
  const native = await client.getBalance({ address: fillerAddr });
22963
23250
  const symbol = nativeTokenSymbol(chainId);
22964
- row.native = { symbol, amount: Number.parseFloat((0, import_viem29.formatUnits)(native, 18)) };
23251
+ row.native = { symbol, amount: Number.parseFloat((0, import_viem31.formatUnits)(native, 18)) };
22965
23252
  } catch (err) {
22966
23253
  issues.push({ chainId, source: "native", message: errorMessage(err) });
22967
23254
  }
@@ -23032,7 +23319,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23032
23319
  functionName: "balanceOf",
23033
23320
  args: [fillerAddr]
23034
23321
  });
23035
- wallet = Number.parseFloat((0, import_viem29.formatUnits)(balance, definition.decimals));
23322
+ wallet = Number.parseFloat((0, import_viem31.formatUnits)(balance, definition.decimals));
23036
23323
  } catch (err) {
23037
23324
  issues.push({
23038
23325
  chainId,
@@ -23060,8 +23347,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23060
23347
  available: wallet !== null && vaultDataAvailable ? Math.max(wallet - walletReserve, 0) + vaultAvailable : null,
23061
23348
  vaults: positions.map((position) => ({
23062
23349
  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)),
23350
+ position: Number.parseFloat((0, import_viem31.formatUnits)(position.positionAssets, position.decimals)),
23351
+ available: Number.parseFloat((0, import_viem31.formatUnits)(position.availableAssets, position.decimals)),
23065
23352
  acceptsDeposits: position.acceptsDeposits
23066
23353
  })),
23067
23354
  status: complete ? "fresh" : wallet !== null || positions.length > 0 ? "partial" : "unavailable"
@@ -23104,8 +23391,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23104
23391
  const api = await coprocessor.queryApi();
23105
23392
  const account = await api.query.system.account(address);
23106
23393
  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));
23394
+ const free = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.free.toString()), decimals));
23395
+ const reserved = Number.parseFloat((0, import_viem31.formatUnits)(BigInt(account.data.reserved.toString()), decimals));
23109
23396
  this.snapshot = { ...this.snapshot, hyperbridge: { address, free, reserved } };
23110
23397
  } catch (err) {
23111
23398
  this.logger.warn({ err }, "Failed to fetch Hyperbridge balance");
@@ -23125,7 +23412,7 @@ function isConfiguredAddress(address) {
23125
23412
  }
23126
23413
  function sumFormatted(positions, field) {
23127
23414
  return positions.reduce(
23128
- (total, position) => total + Number.parseFloat((0, import_viem29.formatUnits)(position[field], position.decimals)),
23415
+ (total, position) => total + Number.parseFloat((0, import_viem31.formatUnits)(position[field], position.decimals)),
23129
23416
  0
23130
23417
  );
23131
23418
  }
@@ -23633,7 +23920,7 @@ async function bootFiller(config, options) {
23633
23920
  const sharedCacheService = new CacheService(options.loggers);
23634
23921
  if (!options.signer && !globalWatchOnly && !allChainsWatchOnly(watchOnlyConfig, resolvedChains)) {
23635
23922
  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]."
23923
+ "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
23924
  );
23638
23925
  }
23639
23926
  const chainClientManager = new ChainClientManager(configService, options.signer);
@@ -23772,7 +24059,8 @@ async function bootFiller(config, options) {
23772
24059
  const lifecycle = new LimitOrderLifecycle(
23773
24060
  limitOrderService,
23774
24061
  {
23775
- reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS
24062
+ reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS,
24063
+ reconcileTallies: () => intentFiller.reconcileLimitOrderTallies()
23776
24064
  },
23777
24065
  options.loggers
23778
24066
  );
@@ -23810,8 +24098,8 @@ async function bootFiller(config, options) {
23810
24098
  if (vaultVenue) {
23811
24099
  vaultVenue.onTx = ({ chain, kind, txHash, sponsored, movements }) => {
23812
24100
  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) };
24101
+ const underlying = { token: move.symbol, amount: (0, import_viem32.formatUnits)(move.amount, move.decimals) };
24102
+ const shares = move.shares === void 0 ? { token: null, amount: null } : { token: move.shareSymbol, amount: (0, import_viem32.formatUnits)(move.shares, move.shareDecimals) };
23815
24103
  const out = kind === "sweep" ? underlying : shares;
23816
24104
  const back = kind === "sweep" ? shares : underlying;
23817
24105
  return { token: out.token, amount: out.amount, to: move.vault, tokenIn: back.token, amountIn: back.amount };
@@ -24243,6 +24531,14 @@ var MemoryLimitOrderStore = class {
24243
24531
  const remaining = BigInt(order.remaining) - BigInt(amount);
24244
24532
  return this.patch(id, { remaining: (remaining > 0n ? remaining : 0n).toString() });
24245
24533
  }
24534
+ async clampRemaining(id, room) {
24535
+ const order = this.orders.get(id);
24536
+ if (!order || order.status !== "open") return null;
24537
+ const sum = BigInt(room) + BigInt(order.reserved);
24538
+ const target = sum > 0n ? sum : 0n;
24539
+ if (target >= BigInt(order.remaining)) return null;
24540
+ return this.patch(id, { remaining: target.toString() });
24541
+ }
24246
24542
  async release(id, amount) {
24247
24543
  const order = this.orders.get(id);
24248
24544
  if (!order) return;
@@ -24751,7 +25047,8 @@ var OrderScanner = class _OrderScanner {
24751
25047
  * back from its own endpoints, which also proves they answer for one chain
24752
25048
  * before a filler ever depends on them.
24753
25049
  *
24754
- * @throws if an endpoint set is invalid or unreachable, if two entries resolve
25050
+ * @throws if an endpoint set is invalid, if none of a chain's endpoints answer,
25051
+ * if its endpoints disagree on the chain, if two entries resolve
24755
25052
  * to the same chain, or if `scanIntervalSecs` is below the minimum.
24756
25053
  */
24757
25054
  static async create(options) {
@@ -24811,7 +25108,10 @@ var OrderScanner = class _OrderScanner {
24811
25108
  async addChainNow(chain) {
24812
25109
  if (this.closed) throw new Error("This OrderScanner is closed");
24813
25110
  const rpcUrls = validateRpcUrls(chain.rpcUrls);
24814
- const chainId = chain.chainId ?? await fetchChainId(rpcUrls[0]);
25111
+ const chainId = chain.chainId ?? (await resolveChainConfigs([{ rpcUrls, bundlerUrl: chain.bundlerUrl ?? "" }], {
25112
+ loggers: this.loggers,
25113
+ tolerateUnreachable: true
25114
+ }))[0].chainId;
24815
25115
  if (this.closed) throw new Error("This OrderScanner is closed");
24816
25116
  if (this.scanners.has(chainId)) {
24817
25117
  throw new Error(`Chain ${chainId} is already in this scanner`);
@@ -24911,7 +25211,7 @@ var LimitOrderController = class {
24911
25211
  get service() {
24912
25212
  const service = this.runtime.limitOrders;
24913
25213
  if (!service) {
24914
- throw new Error("No orderbook is configured \u2014 set [orderbook] enabled and url to use limit orders");
25214
+ throw new Error("No orderbook is configured \u2014 set [orderbook] url to use limit orders");
24915
25215
  }
24916
25216
  return service;
24917
25217
  }
@@ -25376,7 +25676,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25376
25676
  const tomlSigner = options.config.simplex?.signer;
25377
25677
  if (tomlSigner && !options.signer) {
25378
25678
  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."
25679
+ "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
25680
  );
25381
25681
  }
25382
25682
  const loggers = new LoggerContext({
@@ -25580,6 +25880,8 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25580
25880
  0 && (module.exports = {
25581
25881
  AssetController,
25582
25882
  ChainController,
25883
+ LimitOrderController,
25884
+ LimitOrderValidationError,
25583
25885
  LoggerContext,
25584
25886
  MemoryDataStore,
25585
25887
  OrderScanner,
@@ -25592,6 +25894,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25592
25894
  WalletController,
25593
25895
  addLogSink,
25594
25896
  assertConfirmationCoverage,
25897
+ canDerive,
25595
25898
  configureLogger,
25596
25899
  createSigner,
25597
25900
  digestSigner,
@@ -25604,10 +25907,12 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25604
25907
  pickAnchorStable,
25605
25908
  privateKeySigner,
25606
25909
  registrySymbols,
25910
+ secretPhraseSigner,
25607
25911
  turnkeySigner,
25608
25912
  validateAssetDefinitions,
25609
25913
  validateConfig,
25610
25914
  validatePairConfigs,
25915
+ validateSecretPhrase,
25611
25916
  validateSignerConfig,
25612
25917
  validateVaultToml,
25613
25918
  viemSigner