@hyperbridge/simplex 0.16.7 → 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({
@@ -18642,20 +18942,32 @@ var ContractInteractionService = class {
18642
18942
  } else {
18643
18943
  this.logger.warn({ reason: pmResult.reason }, "No paymaster for bid UserOp; relying on EntryPoint deposit");
18644
18944
  }
18645
- const userOp = await sdkHelper.prepareSubmitBid({
18646
- order,
18647
- fillOptions,
18945
+ const bidGas = {
18648
18946
  solverAccount: solverAccountAddress,
18649
- solverSigner: sdkSigningAccount(this.signer),
18650
18947
  nonce,
18651
18948
  entryPointAddress,
18652
18949
  callGasLimit,
18653
18950
  verificationGasLimit: cachedEstimate.verificationGasLimit,
18654
- preVerificationGas: cachedEstimate.preVerificationGas,
18655
18951
  maxFeePerGas: cachedEstimate.maxFeePerGas,
18656
18952
  maxPriorityFeePerGas: cachedEstimate.maxPriorityFeePerGas,
18657
18953
  callData,
18658
18954
  paymasterAndData
18955
+ };
18956
+ let preVerificationGas = cachedEstimate.preVerificationGas;
18957
+ try {
18958
+ preVerificationGas = await sdkHelper.estimateBidPreVerificationGas(bidGas);
18959
+ } catch (err) {
18960
+ this.logger.warn(
18961
+ { orderId: order.id, err, preVerificationGas: preVerificationGas.toString() },
18962
+ "Could not estimate the bid's preVerificationGas; using the fill estimate's"
18963
+ );
18964
+ }
18965
+ const userOp = await sdkHelper.prepareSubmitBid({
18966
+ ...bidGas,
18967
+ order,
18968
+ fillOptions,
18969
+ solverSigner: sdkSigningAccount(this.signer),
18970
+ preVerificationGas
18659
18971
  });
18660
18972
  const encodedUserOp = (0, import_sdk7.encodeUserOpScale)(userOp);
18661
18973
  this.logger.info(
@@ -18664,6 +18976,7 @@ var ContractInteractionService = class {
18664
18976
  solverAccount: solverAccountAddress,
18665
18977
  nonce: nonce.toString(),
18666
18978
  callGasLimit: callGasLimit.toString(),
18979
+ preVerificationGas: preVerificationGas.toString(),
18667
18980
  maxFeePerGas: cachedEstimate.maxFeePerGas.toString()
18668
18981
  },
18669
18982
  "Prepared bid UserOp"
@@ -18747,7 +19060,7 @@ var ContractInteractionService = class {
18747
19060
  {
18748
19061
  target: outputToken,
18749
19062
  value: 0n,
18750
- data: (0, import_viem21.encodeFunctionData)({
19063
+ data: (0, import_viem23.encodeFunctionData)({
18751
19064
  abi: ERC20_ABI,
18752
19065
  functionName: "approve",
18753
19066
  args: [gateway, outputAmount]
@@ -18793,8 +19106,13 @@ var ContractInteractionService = class {
18793
19106
  * Same-chain fills release escrow locally with no Hyperbridge dispatch, so the
18794
19107
  * gateway never pulls the fee token — its approval is skipped. Only cross-chain
18795
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.
18796
19114
  */
18797
- async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount) {
19115
+ async buildApprovalAndFillCalldata(order, fillerOutputs, fillOptions, requiredFeeTokenAmount, settlement) {
18798
19116
  const chain = order.destination;
18799
19117
  const intentGatewayV2Address = this.configService.getIntentGatewayAddress(chain);
18800
19118
  const perTokenRequired = /* @__PURE__ */ new Map();
@@ -18804,9 +19122,10 @@ var ContractInteractionService = class {
18804
19122
  const key = addr.toLowerCase();
18805
19123
  perTokenRequired.set(key, (perTokenRequired.get(key) ?? 0n) + output.amount);
18806
19124
  }
19125
+ let feeKey;
18807
19126
  if (order.source !== order.destination) {
18808
19127
  const feeToken = await this.getFeeTokenWithDecimals(chain);
18809
- const feeKey = feeToken.address.toLowerCase();
19128
+ feeKey = feeToken.address.toLowerCase();
18810
19129
  perTokenRequired.set(feeKey, (perTokenRequired.get(feeKey) ?? 0n) + requiredFeeTokenAmount);
18811
19130
  }
18812
19131
  const fundingPrepends = order.id ? this.cacheService.getFundingPrepends(order.id) : null;
@@ -18818,7 +19137,7 @@ var ContractInteractionService = class {
18818
19137
  calls.push({
18819
19138
  target: tokenAddress,
18820
19139
  value: 0n,
18821
- data: (0, import_viem21.encodeFunctionData)({
19140
+ data: (0, import_viem23.encodeFunctionData)({
18822
19141
  abi: ERC20_ABI,
18823
19142
  functionName: "approve",
18824
19143
  args: [intentGatewayV2Address, amount]
@@ -18832,17 +19151,37 @@ var ContractInteractionService = class {
18832
19151
  value: nativeOutputValue,
18833
19152
  data: (0, import_sdk7.encodeFillOrder)((0, import_sdk7.transformOrderForContract)(order), fillOptions)
18834
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
+ }
18835
19174
  return (0, import_sdk7.encodeERC7821ExecuteBatch)(calls);
18836
19175
  }
18837
19176
  };
18838
19177
 
18839
19178
  // src/services/DelegationService.ts
18840
- var import_viem23 = require("viem");
19179
+ var import_viem25 = require("viem");
18841
19180
  var import_sdk9 = require("@hyperbridge/sdk");
18842
19181
 
18843
19182
  // src/services/UserOpSender.ts
18844
19183
  var import_sdk8 = require("@hyperbridge/sdk");
18845
- var import_viem22 = require("viem");
19184
+ var import_viem24 = require("viem");
18846
19185
  var FALLBACK_VERIFICATION_GAS_LIMIT = 250000n;
18847
19186
  var FALLBACK_CALL_GAS_LIMIT = 1500000n;
18848
19187
  var FALLBACK_PRE_VERIFICATION_GAS = 150000n;
@@ -18915,11 +19254,11 @@ var UserOpSender = class {
18915
19254
  });
18916
19255
  const formattedAuth = auth ? {
18917
19256
  address: auth.address,
18918
- chainId: (0, import_viem22.toHex)(auth.chainId),
18919
- nonce: (0, import_viem22.toHex)(auth.nonce),
19257
+ chainId: (0, import_viem24.toHex)(auth.chainId),
19258
+ nonce: (0, import_viem24.toHex)(auth.nonce),
18920
19259
  r: auth.r,
18921
19260
  s: auth.s,
18922
- yParity: (0, import_viem22.toHex)(auth.yParity)
19261
+ yParity: (0, import_viem24.toHex)(auth.yParity)
18923
19262
  } : void 0;
18924
19263
  const { verificationGasLimit, callGasLimit, preVerificationGas } = gas ?? await this.estimateGas({
18925
19264
  bundlerUrl,
@@ -19087,10 +19426,10 @@ function bootstrapCallData(approval) {
19087
19426
  {
19088
19427
  target: approval.token,
19089
19428
  value: 0n,
19090
- data: (0, import_viem23.encodeFunctionData)({
19091
- abi: import_viem23.erc20Abi,
19429
+ data: (0, import_viem25.encodeFunctionData)({
19430
+ abi: import_viem25.erc20Abi,
19092
19431
  functionName: "approve",
19093
- args: [approval.spender, import_viem23.maxUint256]
19432
+ args: [approval.spender, import_viem25.maxUint256]
19094
19433
  })
19095
19434
  }
19096
19435
  ]);
@@ -19141,10 +19480,10 @@ var DelegationService = class {
19141
19480
  return await walletClient.sendTransaction({
19142
19481
  to: approval.token,
19143
19482
  value: 0n,
19144
- data: (0, import_viem23.encodeFunctionData)({
19145
- abi: import_viem23.erc20Abi,
19483
+ data: (0, import_viem25.encodeFunctionData)({
19484
+ abi: import_viem25.erc20Abi,
19146
19485
  functionName: "approve",
19147
- args: [approval.spender, import_viem23.maxUint256]
19486
+ args: [approval.spender, import_viem25.maxUint256]
19148
19487
  }),
19149
19488
  authorizationList: [authorization],
19150
19489
  chain: walletClient.chain,
@@ -19378,8 +19717,8 @@ var DelegationService = class {
19378
19717
  {
19379
19718
  chain,
19380
19719
  authority,
19381
- nativeBalance: (0, import_viem23.formatEther)(native.nativeBalance),
19382
- requiredNative: (0, import_viem23.formatEther)(native.requiredNative)
19720
+ nativeBalance: (0, import_viem25.formatEther)(native.nativeBalance),
19721
+ requiredNative: (0, import_viem25.formatEther)(native.requiredNative)
19383
19722
  },
19384
19723
  "Delegation failed: insufficient native balance for direct EIP-7702 tx and paymaster path unavailable"
19385
19724
  );
@@ -19454,7 +19793,7 @@ var DelegationService = class {
19454
19793
  { chain, authority: this.signer.address, mode: this.signer.mode ?? "custom" },
19455
19794
  "Revoking EIP-7702 delegation"
19456
19795
  );
19457
- const authorization = await this.buildAuthorization(chain, import_viem23.zeroAddress);
19796
+ const authorization = await this.buildAuthorization(chain, import_viem25.zeroAddress);
19458
19797
  const hash = await this.sendDelegationTransaction(chain, authorization);
19459
19798
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
19460
19799
  if (receipt.status === "success") {
@@ -19470,13 +19809,13 @@ var DelegationService = class {
19470
19809
  };
19471
19810
 
19472
19811
  // src/services/RebalancingService.ts
19473
- var import_viem26 = require("viem");
19812
+ var import_viem28 = require("viem");
19474
19813
  var import_sdk13 = require("@hyperbridge/sdk");
19475
19814
  var import_decimal3 = require("decimal.js");
19476
19815
 
19477
19816
  // src/services/rebalancers/binance.ts
19478
19817
  var import_wallet2 = require("@binance/wallet");
19479
- var import_viem24 = require("viem");
19818
+ var import_viem26 = require("viem");
19480
19819
  var import_sdk10 = require("@hyperbridge/sdk");
19481
19820
  var DEPOSIT_STATUS_SUCCESS = 1;
19482
19821
  var WITHDRAW_STATUS_COMPLETED = 6;
@@ -19788,7 +20127,7 @@ var BinanceRebalancer = class {
19788
20127
  abi: ERC20_ABI,
19789
20128
  functionName: "decimals"
19790
20129
  });
19791
- const amountWei = (0, import_viem24.parseUnits)(amount, Number(tokenDecimals));
20130
+ const amountWei = (0, import_viem26.parseUnits)(amount, Number(tokenDecimals));
19792
20131
  const balance = await publicClient.readContract({
19793
20132
  address: tokenAddress,
19794
20133
  abi: ERC20_ABI,
@@ -20012,7 +20351,7 @@ var CctpRebalancer = class {
20012
20351
  };
20013
20352
 
20014
20353
  // src/services/rebalancers/usdt0.ts
20015
- var import_viem25 = require("viem");
20354
+ var import_viem27 = require("viem");
20016
20355
  var import_sdk12 = require("@hyperbridge/sdk");
20017
20356
 
20018
20357
  // src/config/abis/Oft.ts
@@ -20175,7 +20514,7 @@ var Usdt0Rebalancer = class {
20175
20514
  const publicClient = this.chainClientManager.getPublicClient(source);
20176
20515
  const walletClient = this.chainClientManager.getWalletClient(source);
20177
20516
  const recipient = recipientAddress || this.account.address;
20178
- const amountWei = (0, import_viem25.parseUnits)(amount, 6);
20517
+ const amountWei = (0, import_viem27.parseUnits)(amount, 6);
20179
20518
  if (sourceChainId === 1) {
20180
20519
  const allowance = await publicClient.readContract({
20181
20520
  address: tokenAddress,
@@ -20188,7 +20527,7 @@ var Usdt0Rebalancer = class {
20188
20527
  address: tokenAddress,
20189
20528
  abi: ERC20_ABI,
20190
20529
  functionName: "approve",
20191
- args: [oftAddress, import_viem25.maxUint256],
20530
+ args: [oftAddress, import_viem27.maxUint256],
20192
20531
  chain: walletClient.chain
20193
20532
  });
20194
20533
  await publicClient.waitForTransactionReceipt({ hash: approveTx, confirmations: 1 });
@@ -20270,8 +20609,8 @@ var Usdt0Rebalancer = class {
20270
20609
  if (!oftAddress) throw new Error(`Chain ${source} not supported by USDT0`);
20271
20610
  const publicClient = this.chainClientManager.getPublicClient(source);
20272
20611
  const recipient = recipientAddress || this.account.address;
20273
- const amountWei = (0, import_viem25.parseUnits)(amount, 6);
20274
- 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 });
20275
20614
  const sendParam = {
20276
20615
  dstEid: destEid,
20277
20616
  to: recipientBytes32,
@@ -20623,7 +20962,7 @@ var RebalancingService = class {
20623
20962
  functionName: "balanceOf",
20624
20963
  args: [this.walletAddress]
20625
20964
  });
20626
- 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)));
20627
20966
  return balanceDecimal.toNumber();
20628
20967
  } catch (error) {
20629
20968
  this.logger.error({ error, chain, asset }, "Failed to get current balance");
@@ -20671,11 +21010,11 @@ function byBestRate(a, b) {
20671
21010
  return left === right ? 0 : left > right ? -1 : 1;
20672
21011
  }
20673
21012
  function chargedFor(hold, delivered, released) {
20674
- if (hold.take === void 0 || released === 0n) return delivered;
20675
- const take = BigInt(hold.take);
20676
- if (take === 0n) return delivered;
20677
- const atRate = (BigInt(hold.amount) * released + take - 1n) / take;
20678
- 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);
20679
21018
  }
20680
21019
  var IntentFiller = class {
20681
21020
  constructor(chainConfigs, strategies, config, configService, chainClientManager, contractService, signer, scanners, rebalancingService, bidStorage, limitOrders, assetRegistry) {
@@ -20752,6 +21091,17 @@ var IntentFiller = class {
20752
21091
  setLimitOrderService(service) {
20753
21092
  this.limitOrderService = service;
20754
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
+ }
20755
21105
  /** Whether the given chain id is configured for watch-only (monitor, never fill). */
20756
21106
  isChainWatchOnly(chainId) {
20757
21107
  const watchOnly = this.config.watchOnly;
@@ -21397,7 +21747,7 @@ var IntentFiller = class {
21397
21747
  let reservation = [];
21398
21748
  if (plan) {
21399
21749
  if (order.id) {
21400
- cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs);
21750
+ cache.setFillerOutputs(order.id, plan.fillerOutputs, plan.fillerInputs, plan.budget);
21401
21751
  cache.setPartialFill(order.id, plan.partialFill);
21402
21752
  if (plan.fundingCalls.length > 0) cache.setFundingPrepends(order.id, plan.fundingCalls);
21403
21753
  else cache.clearFundingPrepends(order.id);
@@ -21598,7 +21948,7 @@ var IntentFiller = class {
21598
21948
  await this.releaseAll(claimed);
21599
21949
  return;
21600
21950
  }
21601
- const settled = executedHold(claimed, delivered, released);
21951
+ const settled = executedHold(claimed, delivered.scaled, released);
21602
21952
  const charged = chargedFor(settled, delivered, released);
21603
21953
  const settledBid = rows.find(
21604
21954
  (row) => row.reservations.some(
@@ -21651,15 +22001,15 @@ var IntentFiller = class {
21651
22001
  if (delivered === null) return [];
21652
22002
  const settled = executedHold(
21653
22003
  holds.map((entry) => entry.hold),
21654
- delivered,
22004
+ delivered.scaled,
21655
22005
  released
21656
22006
  );
21657
22007
  const row = holds.find((entry) => entry.hold === settled).row;
21658
22008
  return this.bidStorage.claimReservation(commitment, row.bid ?? void 0);
21659
22009
  }
21660
22010
  /**
21661
- * What this fill delivered of the token the limit order pays, at 1e18, or null
21662
- * 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.
21663
22013
  */
21664
22014
  async deliveredAgainst(limitOrderId, chainId, outputs) {
21665
22015
  const order = await this.limitOrders?.get(limitOrderId);
@@ -21675,7 +22025,7 @@ var IntentFiller = class {
21675
22025
  }
21676
22026
  if (total === 0n) return null;
21677
22027
  const decimals = await this.contractService.getTokenDecimals(address, chain);
21678
- return toScaled(total, decimals);
22028
+ return { scaled: toScaled(total, decimals), raw: total, decimals };
21679
22029
  }
21680
22030
  /**
21681
22031
  * Gives back whatever this bid still holds against its limit order.
@@ -21765,94 +22115,9 @@ var IntentFiller = class {
21765
22115
 
21766
22116
  // src/strategies/fx.ts
21767
22117
  var import_sdk15 = require("@hyperbridge/sdk");
21768
- var import_viem27 = require("viem");
22118
+ var import_viem29 = require("viem");
21769
22119
  var import_decimal6 = require("decimal.js");
21770
22120
 
21771
- // src/orderbook/matching.ts
21772
- function limitOrderLegs(order) {
21773
- return order.side === "BID" ? { input: order.base, output: order.quote } : { input: order.quote, output: order.base };
21774
- }
21775
- function availableOn(order) {
21776
- const available = BigInt(order.remaining) - BigInt(order.reserved);
21777
- return available > 0n ? available : 0n;
21778
- }
21779
- function matchLimitOrders(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
21780
- const qualifying = orders.filter((order) => serves(order, incoming, resolve, now)).map((order) => {
21781
- const offer = offerFor({
21782
- side: order.side,
21783
- inputAmount: incoming.inputNet,
21784
- price: BigInt(order.price),
21785
- outputDecimals: incoming.outputDecimals
21786
- });
21787
- const available = availableOn(order);
21788
- return { order, offer, available, payout: offer < available ? offer : available };
21789
- }).filter((candidate) => candidate.offer >= incoming.requestedOutput && candidate.payout > 0n).sort(byBestOfferFirst);
21790
- return qualifying;
21791
- }
21792
- function byBestOfferFirst(a, b) {
21793
- if (a.offer !== b.offer) return a.offer > b.offer ? -1 : 1;
21794
- if (a.available !== b.available) return a.available > b.available ? -1 : 1;
21795
- return a.order.id < b.order.id ? -1 : 1;
21796
- }
21797
- function whyUnmatched(orders, incoming, resolve, now = /* @__PURE__ */ new Date()) {
21798
- let furthest = -1;
21799
- for (const order of orders) {
21800
- const failed = firstFailed(order, incoming, resolve, now);
21801
- furthest = Math.max(furthest, failed === null ? CHECKS.length : CHECKS.indexOf(failed));
21802
- }
21803
- const { source, destination, inputSymbol } = incoming;
21804
- if (furthest < CHECKS.length) {
21805
- switch (CHECKS[Math.max(furthest, 0)]) {
21806
- case "open":
21807
- return "no limit order is open";
21808
- case "chain":
21809
- return `no open limit order fills on ${destination}`;
21810
- case "input":
21811
- return `no limit order on ${destination} takes ${inputSymbol} in`;
21812
- case "output":
21813
- return `no limit order on ${destination} taking ${inputSymbol} in pays out the token this order asks for`;
21814
- case "source":
21815
- return `no limit order for this pair on ${destination} accepts swaps from ${source}`;
21816
- }
21817
- }
21818
- const priced = orders.filter((order) => firstFailed(order, incoming, resolve, now) === null).map((order) => ({
21819
- order,
21820
- offer: offerFor({
21821
- side: order.side,
21822
- inputAmount: incoming.inputNet,
21823
- price: BigInt(order.price),
21824
- outputDecimals: incoming.outputDecimals
21825
- })
21826
- }));
21827
- if (priced.some(({ offer }) => offer >= incoming.requestedOutput)) {
21828
- return "the limit orders that meet its rate have nothing left to pay out";
21829
- }
21830
- const best = priced.reduce((a, b) => b.offer > a.offer ? b : a);
21831
- const symbol = limitOrderLegs(best.order).output;
21832
- return `it asks for ${readable(incoming.requestedOutput)} ${symbol}; the best limit order offers ${readable(best.offer)} ${symbol}`;
21833
- }
21834
- var CHECKS = ["open", "chain", "input", "output", "source"];
21835
- function serves(order, incoming, resolve, now) {
21836
- return firstFailed(order, incoming, resolve, now) === null;
21837
- }
21838
- function firstFailed(order, incoming, resolve, now) {
21839
- if (order.status !== "open") return "open";
21840
- if (order.expiresAt !== null && new Date(order.expiresAt) <= now) return "open";
21841
- if (order.fillChain !== incoming.destination) return "chain";
21842
- const legs = limitOrderLegs(order);
21843
- if (normalizeSymbol(legs.input) !== normalizeSymbol(incoming.inputSymbol)) return "input";
21844
- const outputToken = resolve(legs.output, order.fillChain);
21845
- if (!outputToken || outputToken.toLowerCase() !== incoming.outputToken.toLowerCase()) return "output";
21846
- if (incoming.source === incoming.destination) return null;
21847
- return order.acceptedSources.includes(incoming.source) ? null : "source";
21848
- }
21849
- function readable(amount) {
21850
- const [whole, fraction] = toHuman(amount).split(".");
21851
- const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
21852
- const cut = fraction?.slice(0, 6).replace(/0+$/, "");
21853
- return cut ? `${grouped}.${cut}` : grouped;
21854
- }
21855
-
21856
22121
  // src/orderbook/usd.ts
21857
22122
  var import_decimal5 = require("decimal.js");
21858
22123
  function limitOrderUsdEdges(orders) {
@@ -21907,9 +22172,13 @@ var FXFiller = class _FXFiller {
21907
22172
  */
21908
22173
  constructor(signer, configService, clientManager, contractService, pairs, registry, options) {
21909
22174
  this.name = "FXFiller";
21910
- /** 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
+ */
21911
22180
  this.consecutiveClamps = 0;
21912
- /** 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. */
21913
22182
  this.halted = false;
21914
22183
  this.logger = moduleLogger(configService.loggers, "fx-simplex");
21915
22184
  const { confirmationPolicy, fundingVenues = [], limitOrders } = options ?? {};
@@ -21958,10 +22227,9 @@ var FXFiller = class _FXFiller {
21958
22227
  this.pairs.push(pair);
21959
22228
  }
21960
22229
  /**
21961
- * Removes a market from the running engine. All-or-nothing: at least one
21962
- * market must remain and no other pair may lose its USD anchor (removing a
21963
- * reference feed that anchors a dependent market is rejected). In-flight
21964
- * 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.
21965
22233
  */
21966
22234
  removePair(pair) {
21967
22235
  const index = this.pairs.indexOf(pair);
@@ -22017,22 +22285,20 @@ var FXFiller = class _FXFiller {
22017
22285
  }
22018
22286
  }
22019
22287
  /**
22020
- * Evaluates whether an order is profitable to fill under the per-pair
22021
- * `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.
22022
22290
  *
22023
22291
  * High-level flow:
22024
- * - Resolve each (input, output) leg to a configured pair and direction.
22025
- * - Estimate each pair's total token0 notional in the order and cap it at
22026
- * the pair's `maxOrderSize` when one is set; pair curves are evaluated at
22027
- * that (possibly uncapped) notional.
22028
- * - Walk the legs, allocating from each pair's capped token0 budget and
22029
- * pricing outputs at the pair's rate.
22030
- * - Further cap each leg by the filler's current token balance plus
22031
- * funding-venue withdrawals.
22032
- * - Cache the resulting outputs for later use in `executeOrder`.
22033
- *
22034
- * Note: we may intentionally overfill relative to the user's requested
22035
- * 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`.
22036
22302
  */
22037
22303
  async calculateProfitability(order) {
22038
22304
  if (order.id) this.contractService.cacheService.clearPartialFill(order.id);
@@ -22050,6 +22316,7 @@ var FXFiller = class _FXFiller {
22050
22316
  const multiLeg = order.inputs.length > 1;
22051
22317
  const plans = [];
22052
22318
  const plannedOn = /* @__PURE__ */ new Map();
22319
+ const spentOn = /* @__PURE__ */ new Map();
22053
22320
  for (let leg = 0; leg < order.inputs.length; leg++) {
22054
22321
  const input = order.inputs[leg];
22055
22322
  const output = order.output.assets[leg];
@@ -22071,7 +22338,30 @@ var FXFiller = class _FXFiller {
22071
22338
  for (const candidate of matches) {
22072
22339
  let partialFill = false;
22073
22340
  const fundingCalls = [];
22074
- 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);
22075
22365
  if (left <= 0n) {
22076
22366
  this.logger.info(
22077
22367
  { orderId: order.id, leg, limitOrder: candidate.order.id },
@@ -22106,10 +22396,7 @@ var FXFiller = class _FXFiller {
22106
22396
  {
22107
22397
  orderId: order.id,
22108
22398
  limitOrder: candidate.order.id,
22109
- available: (0, import_viem27.formatUnits)(
22110
- candidate.available,
22111
- 18
22112
- ),
22399
+ available: (0, import_viem29.formatUnits)(available, 18),
22113
22400
  userRequested: output.amount.toString(),
22114
22401
  payout: targetOutput.toString(),
22115
22402
  crossChain: sourceChain !== destChain
@@ -22154,7 +22441,10 @@ var FXFiller = class _FXFiller {
22154
22441
  }
22155
22442
  }
22156
22443
  const effectiveBalance = walletContribution + credited;
22157
- const finalOutputAmount = effectiveBalance > targetOutput ? targetOutput : effectiveBalance;
22444
+ const finalOutputAmount = toRaw(
22445
+ toScaled(effectiveBalance > targetOutput ? targetOutput : effectiveBalance, outputDecimals),
22446
+ outputDecimals
22447
+ );
22158
22448
  if (finalOutputAmount === 0n) {
22159
22449
  this.logger.info(
22160
22450
  {
@@ -22243,7 +22533,7 @@ var FXFiller = class _FXFiller {
22243
22533
  const spread = convertedInput - finalOutputAmount;
22244
22534
  if (spread <= 0n) sameAssetProfitable = false;
22245
22535
  realizedSpreadProfit = adjustDecimalsFloor(spread, outputDecimals, feeTokenDecimals);
22246
- 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)));
22247
22537
  if (spreadUsd) sameAssetEdgeUsd = spreadUsd;
22248
22538
  }
22249
22539
  const payoutSurplusUsd = new import_decimal6.Decimal(0);
@@ -22258,9 +22548,9 @@ var FXFiller = class _FXFiller {
22258
22548
  {
22259
22549
  orderId: order.id,
22260
22550
  feeToken: feeTokenLower,
22261
- residual: (0, import_viem27.formatUnits)(residual, feeToken.decimals),
22262
- dispatchFee: (0, import_viem27.formatUnits)(dispatchFee, feeToken.decimals),
22263
- 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)
22264
22554
  },
22265
22555
  "Skipping order: fill leaves too little of the fee token to dispatch the escrow release"
22266
22556
  );
@@ -22273,10 +22563,10 @@ var FXFiller = class _FXFiller {
22273
22563
  this.logger.info(
22274
22564
  {
22275
22565
  orderId: order.id,
22276
- orderFees: (0, import_viem27.formatUnits)(order.fees, feeTokenDecimals),
22277
- fillGas: (0, import_viem27.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22278
- relayerFee: (0, import_viem27.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22279
- 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)
22280
22570
  },
22281
22571
  "Skipping order: attached fees do not cover execution cost (fill gas + relayer fee)"
22282
22572
  );
@@ -22284,13 +22574,13 @@ var FXFiller = class _FXFiller {
22284
22574
  }
22285
22575
  if (sameAsset && !sameAssetProfitable) {
22286
22576
  this.logger.info(
22287
- { orderId: order.id, realizedSpreadProfit: (0, import_viem27.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22577
+ { orderId: order.id, realizedSpreadProfit: (0, import_viem29.formatUnits)(realizedSpreadProfit, feeTokenDecimals) },
22288
22578
  "Skipping order: a same-asset fill does not net a positive spread"
22289
22579
  );
22290
22580
  continue;
22291
22581
  }
22292
22582
  const feeProfit = order.fees - executionCost;
22293
- 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));
22294
22584
  this.logger.info(
22295
22585
  {
22296
22586
  orderId: order.id,
@@ -22306,12 +22596,12 @@ var FXFiller = class _FXFiller {
22306
22596
  limitOrders: matches.length,
22307
22597
  available: candidate.available.toString(),
22308
22598
  payout: targetOutput.toString(),
22309
- orderFees: (0, import_viem27.formatUnits)(order.fees, feeTokenDecimals),
22310
- fillGas: (0, import_viem27.formatUnits)(totalCostInSourceFeeToken, feeTokenDecimals),
22311
- relayerFee: (0, import_viem27.formatUnits)(relayerFeeInSourceFeeToken, feeTokenDecimals),
22312
- executionCost: (0, import_viem27.formatUnits)(executionCost, feeTokenDecimals),
22313
- feeProfit: (0, import_viem27.formatUnits)(feeProfit, feeTokenDecimals),
22314
- 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),
22315
22605
  payoutSurplusUsd: payoutSurplusUsd.toString(),
22316
22606
  totalProfit,
22317
22607
  profitable: totalProfit > 0
@@ -22330,7 +22620,8 @@ var FXFiller = class _FXFiller {
22330
22620
  fillerInputs,
22331
22621
  fundingCalls: [...fundingCalls],
22332
22622
  partialFill,
22333
- profit: totalProfit
22623
+ profit: totalProfit,
22624
+ budget
22334
22625
  });
22335
22626
  if (!partialEligibleCheap) break;
22336
22627
  }
@@ -22338,7 +22629,12 @@ var FXFiller = class _FXFiller {
22338
22629
  if (plans.length === 0) return 0;
22339
22630
  if (order.id) {
22340
22631
  this.contractService.cacheService.setBidPlans(order.id, plans);
22341
- 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
+ );
22342
22638
  this.contractService.cacheService.setPartialFill(order.id, plans[0].partialFill);
22343
22639
  this.contractService.cacheService.setMatchedLimitOrder(order.id, [
22344
22640
  { limitOrderId: plans[0].limitOrderId, payout: plans[0].payout }
@@ -22433,10 +22729,14 @@ var FXFiller = class _FXFiller {
22433
22729
  // =========================================================================
22434
22730
  /**
22435
22731
  * Update consecutive-clamp counter after a successful order evaluation.
22436
- * Only venue-priced legs feed this counter (see clamp site) — a streak of those
22437
- * is the signal that a live market source has gone off (stale pool, manipulated
22438
- * venue) and the filler should stop until an operator investigates. Offline
22439
- * 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.
22440
22740
  */
22441
22741
  recordOrderOutcome(clamped, orderId) {
22442
22742
  if (clamped) {
@@ -22462,18 +22762,6 @@ var FXFiller = class _FXFiller {
22462
22762
  this.consecutiveClamps = 0;
22463
22763
  this.logger.warn("FXFiller halt reset by operator \u2014 resuming order evaluation");
22464
22764
  }
22465
- /**
22466
- * Given a single (input, output) leg and the remaining token0 budget of its
22467
- * pair, computes how much token0 notional to allocate to this leg and the
22468
- * corresponding maximum output amount at the pair's rate.
22469
- *
22470
- * `rate` is **token1 per 1 token0**:
22471
- * - token0 input → token1 output: token0 × rate → token1 amount.
22472
- * - token1 input → token0 output: token1 ÷ rate → token0 amount.
22473
- *
22474
- * Returns `null` when this leg cannot consume any of the pair's remaining
22475
- * budget (e.g. the cap has already been exhausted).
22476
- */
22477
22765
  /**
22478
22766
  * Whether any solver has already delivered output against this order.
22479
22767
  *
@@ -22613,7 +22901,7 @@ var FXFiller = class _FXFiller {
22613
22901
  const symbol = this.registry.symbolFor(inputToken, order.source);
22614
22902
  if (!symbol) return null;
22615
22903
  const decimals = await this.contractService.getTokenDecimals(inputToken, order.source);
22616
- 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)));
22617
22905
  if (!legUsd) return null;
22618
22906
  inputUsd = inputUsd.plus(legUsd);
22619
22907
  }
@@ -22626,7 +22914,7 @@ function minBigInt(first, ...rest) {
22626
22914
 
22627
22915
  // src/services/TokenSender.ts
22628
22916
  var import_sdk16 = require("@hyperbridge/sdk");
22629
- var import_viem28 = require("viem");
22917
+ var import_viem30 = require("viem");
22630
22918
  var TokenSender = class {
22631
22919
  constructor(clientManager, solver, getVaults, userOpSender, configService) {
22632
22920
  this.clientManager = clientManager;
@@ -22638,18 +22926,18 @@ var TokenSender = class {
22638
22926
  }
22639
22927
  async send(params) {
22640
22928
  const { chain, token, amount, to } = params;
22641
- 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}`);
22642
22930
  const publicClient = this.clientManager.getPublicClient(chain);
22643
22931
  const calls = [];
22644
22932
  let redeemed = false;
22645
22933
  if (token === "native") {
22646
- const value = (0, import_viem28.parseUnits)(amount, 18);
22934
+ const value = (0, import_viem30.parseUnits)(amount, 18);
22647
22935
  if (value <= 0n) throw new Error("Amount must be greater than 0");
22648
22936
  const balance = await publicClient.getBalance({ address: this.solver });
22649
22937
  if (balance < value) throw new Error(`Insufficient native balance: have ${balance}, need ${value}`);
22650
22938
  calls.push({ target: to, value, data: "0x" });
22651
22939
  } else {
22652
- 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}`);
22653
22941
  const tokenLower = token.toLowerCase();
22654
22942
  const [decimals, balance] = await Promise.all([
22655
22943
  publicClient.readContract({ abi: ERC20_ABI, address: token, functionName: "decimals" }),
@@ -22660,7 +22948,7 @@ var TokenSender = class {
22660
22948
  args: [this.solver]
22661
22949
  })
22662
22950
  ]);
22663
- const units = (0, import_viem28.parseUnits)(amount, decimals);
22951
+ const units = (0, import_viem30.parseUnits)(amount, decimals);
22664
22952
  if (units <= 0n) throw new Error("Amount must be greater than 0");
22665
22953
  const isVaultShare = this.getVaults().some(
22666
22954
  (v) => v.chain === chain && v.vault.toLowerCase() === tokenLower
@@ -22671,7 +22959,7 @@ var TokenSender = class {
22671
22959
  calls.push({
22672
22960
  target: token,
22673
22961
  value: 0n,
22674
- 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] })
22675
22963
  });
22676
22964
  } else {
22677
22965
  const reserve = this.userOpSender?.canSponsor(chain) && this.configService ? paymasterReserveForToken(chain, tokenLower, this.configService) : 0n;
@@ -22694,7 +22982,7 @@ var TokenSender = class {
22694
22982
  calls.push({
22695
22983
  target: token,
22696
22984
  value: 0n,
22697
- 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] })
22698
22986
  });
22699
22987
  }
22700
22988
  }
@@ -22737,7 +23025,7 @@ var TokenSender = class {
22737
23025
  });
22738
23026
  const required = shortfall > 0n ? shortfall : 0n;
22739
23027
  if (available < required) continue;
22740
- 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);
22741
23029
  const headroom = reserve > floor ? reserve : floor;
22742
23030
  const wanted = shortfall + headroom;
22743
23031
  const amount = available >= wanted ? wanted : required;
@@ -22745,7 +23033,7 @@ var TokenSender = class {
22745
23033
  return {
22746
23034
  target: vault.vault,
22747
23035
  value: 0n,
22748
- data: (0, import_viem28.encodeFunctionData)({
23036
+ data: (0, import_viem30.encodeFunctionData)({
22749
23037
  abi: ERC4626_ABI,
22750
23038
  functionName: "withdraw",
22751
23039
  args: [amount, this.solver, this.solver]
@@ -22811,9 +23099,9 @@ var LimitOrderLifecycle = class {
22811
23099
  const reconcileMs = this.options.reconcileIntervalSecs * 1e3;
22812
23100
  this.every(heartbeatMs, "heartbeat", () => this.service.heartbeat());
22813
23101
  this.every(EXPIRY_SWEEP_MS, "expiry", () => this.expire());
22814
- this.every(reconcileMs, "reconciliation", () => this.reconcile());
23102
+ this.every(reconcileMs, "reconciliation", () => this.upkeep());
22815
23103
  this.logger.info({ heartbeatMs, expiryMs: EXPIRY_SWEEP_MS, reconcileMs }, "Orderbook lifecycle started");
22816
- void this.run("reconciliation", () => this.reconcile());
23104
+ void this.upkeep();
22817
23105
  }
22818
23106
  stop() {
22819
23107
  this.timers.forEach((timer) => clearInterval(timer));
@@ -22824,6 +23112,14 @@ var LimitOrderLifecycle = class {
22824
23112
  const expired = await this.service.expireStale();
22825
23113
  if (expired > 0) this.logger.info({ expired }, "Withdrew limit orders that had outlived their expiry");
22826
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
+ }
22827
23123
  async reconcile() {
22828
23124
  const report = await this.service.reconcile();
22829
23125
  if (report.cancelled || report.reposted || report.underFunded) {
@@ -22859,7 +23155,7 @@ var LimitOrderLifecycle = class {
22859
23155
 
22860
23156
  // src/services/BalanceProvider.ts
22861
23157
  var import_node_events = require("node:events");
22862
- var import_viem29 = require("viem");
23158
+ var import_viem31 = require("viem");
22863
23159
  var BalanceProvider = class extends import_node_events.EventEmitter {
22864
23160
  constructor(options) {
22865
23161
  super();
@@ -22952,7 +23248,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
22952
23248
  try {
22953
23249
  const native = await client.getBalance({ address: fillerAddr });
22954
23250
  const symbol = nativeTokenSymbol(chainId);
22955
- 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)) };
22956
23252
  } catch (err) {
22957
23253
  issues.push({ chainId, source: "native", message: errorMessage(err) });
22958
23254
  }
@@ -23023,7 +23319,7 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23023
23319
  functionName: "balanceOf",
23024
23320
  args: [fillerAddr]
23025
23321
  });
23026
- wallet = Number.parseFloat((0, import_viem29.formatUnits)(balance, definition.decimals));
23322
+ wallet = Number.parseFloat((0, import_viem31.formatUnits)(balance, definition.decimals));
23027
23323
  } catch (err) {
23028
23324
  issues.push({
23029
23325
  chainId,
@@ -23051,8 +23347,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23051
23347
  available: wallet !== null && vaultDataAvailable ? Math.max(wallet - walletReserve, 0) + vaultAvailable : null,
23052
23348
  vaults: positions.map((position) => ({
23053
23349
  address: position.vault,
23054
- position: Number.parseFloat((0, import_viem29.formatUnits)(position.positionAssets, position.decimals)),
23055
- 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)),
23056
23352
  acceptsDeposits: position.acceptsDeposits
23057
23353
  })),
23058
23354
  status: complete ? "fresh" : wallet !== null || positions.length > 0 ? "partial" : "unavailable"
@@ -23095,8 +23391,8 @@ var BalanceProvider = class extends import_node_events.EventEmitter {
23095
23391
  const api = await coprocessor.queryApi();
23096
23392
  const account = await api.query.system.account(address);
23097
23393
  const decimals = api.registry.chainDecimals[0] ?? 12;
23098
- const free = Number.parseFloat((0, import_viem29.formatUnits)(BigInt(account.data.free.toString()), decimals));
23099
- 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));
23100
23396
  this.snapshot = { ...this.snapshot, hyperbridge: { address, free, reserved } };
23101
23397
  } catch (err) {
23102
23398
  this.logger.warn({ err }, "Failed to fetch Hyperbridge balance");
@@ -23116,7 +23412,7 @@ function isConfiguredAddress(address) {
23116
23412
  }
23117
23413
  function sumFormatted(positions, field) {
23118
23414
  return positions.reduce(
23119
- (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)),
23120
23416
  0
23121
23417
  );
23122
23418
  }
@@ -23624,7 +23920,7 @@ async function bootFiller(config, options) {
23624
23920
  const sharedCacheService = new CacheService(options.loggers);
23625
23921
  if (!options.signer && !globalWatchOnly && !allChainsWatchOnly(watchOnlyConfig, resolvedChains)) {
23626
23922
  throw new Error(
23627
- "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]."
23628
23924
  );
23629
23925
  }
23630
23926
  const chainClientManager = new ChainClientManager(configService, options.signer);
@@ -23671,48 +23967,42 @@ async function bootFiller(config, options) {
23671
23967
  assertPairSymbolsResolve(config.pairs, assetRegistry, configuredChainNames);
23672
23968
  }
23673
23969
  const adminStrategies = [];
23674
- const strategies = [];
23675
- let confirmationPolicy;
23676
- let tradingPairs;
23677
- let engine;
23678
- if (config.pairs?.length) {
23679
- tradingPairs = config.pairs.map((pair) => ({ token0: pair.token0, token1: pair.token1 }));
23680
- tradingPairs.forEach((pair, pairIndex) => {
23681
- adminStrategies.push({
23682
- index: pairIndex,
23683
- pairIndex,
23684
- exotic: `${pair.token0}/${pair.token1}`,
23685
- token0: pair.token0,
23686
- token1: pair.token1,
23687
- sameToken: normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)
23688
- });
23970
+ const tradingPairs = (config.pairs ?? []).map((pair) => ({
23971
+ token0: pair.token0,
23972
+ token1: pair.token1
23973
+ }));
23974
+ tradingPairs.forEach((pair, pairIndex) => {
23975
+ adminStrategies.push({
23976
+ index: pairIndex,
23977
+ pairIndex,
23978
+ exotic: `${pair.token0}/${pair.token1}`,
23979
+ token0: pair.token0,
23980
+ token1: pair.token1,
23981
+ sameToken: normalizeSymbol(pair.token0) === normalizeSymbol(pair.token1)
23689
23982
  });
23690
- confirmationPolicy = assertConfirmationCoverage(
23691
- config.confirmationPolicies,
23692
- resolvedChains.map((c) => c.chainId)
23693
- );
23694
- const fundingVenues = vaultVenue ? [vaultVenue] : [];
23695
- engine = new FXFiller(
23696
- runtimeSigner,
23697
- configService,
23698
- chainClientManager,
23699
- contractService,
23700
- tradingPairs,
23701
- assetRegistry,
23702
- { confirmationPolicy, fundingVenues, limitOrders: options.data.limitOrders }
23703
- );
23704
- logger.info("Hydrating funding venue state...");
23705
- await engine.initialise();
23706
- strategies.push(engine);
23707
- }
23983
+ });
23984
+ const confirmationPolicy = assertConfirmationCoverage(
23985
+ config.confirmationPolicies,
23986
+ resolvedChains.map((c) => c.chainId)
23987
+ );
23988
+ const fundingVenues = vaultVenue ? [vaultVenue] : [];
23989
+ const engine = new FXFiller(
23990
+ runtimeSigner,
23991
+ configService,
23992
+ chainClientManager,
23993
+ contractService,
23994
+ tradingPairs,
23995
+ assetRegistry,
23996
+ { confirmationPolicy, fundingVenues, limitOrders: options.data.limitOrders }
23997
+ );
23998
+ logger.info("Hydrating funding venue state...");
23999
+ await engine.initialise();
24000
+ const strategies = [engine];
23708
24001
  const haltControls = strategies.map((engine2, index) => ({
23709
24002
  index,
23710
24003
  isHalted: () => engine2.isHalted(),
23711
24004
  resetHalt: () => engine2.resetHalt()
23712
24005
  }));
23713
- if (vaultVenue) {
23714
- await vaultVenue.initialise(runtimeSigner.address);
23715
- }
23716
24006
  let rebalancingService;
23717
24007
  const rebalancingConfig = configService.getRebalancingConfig();
23718
24008
  if (rebalancingConfig?.triggerPercentage !== void 0 && rebalancingConfig?.baseBalances) {
@@ -23769,7 +24059,8 @@ async function bootFiller(config, options) {
23769
24059
  const lifecycle = new LimitOrderLifecycle(
23770
24060
  limitOrderService,
23771
24061
  {
23772
- reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS
24062
+ reconcileIntervalSecs: config.orderbook?.reconcileIntervalSecs ?? DEFAULT_RECONCILE_INTERVAL_SECS,
24063
+ reconcileTallies: () => intentFiller.reconcileLimitOrderTallies()
23773
24064
  },
23774
24065
  options.loggers
23775
24066
  );
@@ -23807,8 +24098,8 @@ async function bootFiller(config, options) {
23807
24098
  if (vaultVenue) {
23808
24099
  vaultVenue.onTx = ({ chain, kind, txHash, sponsored, movements }) => {
23809
24100
  const rows = movements.length > 0 ? movements.map((move) => {
23810
- const underlying = { token: move.symbol, amount: (0, import_viem30.formatUnits)(move.amount, move.decimals) };
23811
- 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) };
23812
24103
  const out = kind === "sweep" ? underlying : shares;
23813
24104
  const back = kind === "sweep" ? shares : underlying;
23814
24105
  return { token: out.token, amount: out.amount, to: move.vault, tokenIn: back.token, amountIn: back.amount };
@@ -24229,8 +24520,8 @@ var MemoryLimitOrderStore = class {
24229
24520
  async reserve(id, amount) {
24230
24521
  const order = this.orders.get(id);
24231
24522
  if (!order || order.status !== "open") return false;
24523
+ if (BigInt(amount) > BigInt(order.remaining)) return false;
24232
24524
  const reserved = BigInt(order.reserved) + BigInt(amount);
24233
- if (reserved > BigInt(order.remaining)) return false;
24234
24525
  this.patch(id, { reserved: reserved.toString() });
24235
24526
  return true;
24236
24527
  }
@@ -24240,6 +24531,14 @@ var MemoryLimitOrderStore = class {
24240
24531
  const remaining = BigInt(order.remaining) - BigInt(amount);
24241
24532
  return this.patch(id, { remaining: (remaining > 0n ? remaining : 0n).toString() });
24242
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
+ }
24243
24542
  async release(id, amount) {
24244
24543
  const order = this.orders.get(id);
24245
24544
  if (!order) return;
@@ -24748,7 +25047,8 @@ var OrderScanner = class _OrderScanner {
24748
25047
  * back from its own endpoints, which also proves they answer for one chain
24749
25048
  * before a filler ever depends on them.
24750
25049
  *
24751
- * @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
24752
25052
  * to the same chain, or if `scanIntervalSecs` is below the minimum.
24753
25053
  */
24754
25054
  static async create(options) {
@@ -24808,7 +25108,10 @@ var OrderScanner = class _OrderScanner {
24808
25108
  async addChainNow(chain) {
24809
25109
  if (this.closed) throw new Error("This OrderScanner is closed");
24810
25110
  const rpcUrls = validateRpcUrls(chain.rpcUrls);
24811
- 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;
24812
25115
  if (this.closed) throw new Error("This OrderScanner is closed");
24813
25116
  if (this.scanners.has(chainId)) {
24814
25117
  throw new Error(`Chain ${chainId} is already in this scanner`);
@@ -24908,7 +25211,7 @@ var LimitOrderController = class {
24908
25211
  get service() {
24909
25212
  const service = this.runtime.limitOrders;
24910
25213
  if (!service) {
24911
- 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");
24912
25215
  }
24913
25216
  return service;
24914
25217
  }
@@ -24982,10 +25285,7 @@ var PairController = class {
24982
25285
  * the pair's symbols have to resolve before the market can be hydrated.
24983
25286
  */
24984
25287
  async add(pair, options) {
24985
- const { engine, tradingPairs, assetRegistry, adminStrategies, balanceTokens, config } = this.runtime;
24986
- if (!engine || !tradingPairs) {
24987
- throw new Error("No trading engine is running \u2014 this filler was started without pairs");
24988
- }
25288
+ const { engine, assetRegistry, adminStrategies, balanceTokens, config } = this.runtime;
24989
25289
  const assets = options?.assets;
24990
25290
  const nextAssets = { ...config.assets ?? {}, ...assets ?? {} };
24991
25291
  const nextPairs = [...this.pairs, pair];
@@ -25024,7 +25324,6 @@ var PairController = class {
25024
25324
  /** Closes a market. Orders already in flight against it still complete. */
25025
25325
  async remove(index) {
25026
25326
  const { engine, tradingPairs, adminStrategies, config } = this.runtime;
25027
- if (!engine || !tradingPairs) throw new Error("No trading engine is running");
25028
25327
  if (index < 0 || index >= this.pairs.length) throw new Error(`Unknown pair ${index}`);
25029
25328
  engine.removePair(tradingPairs[index]);
25030
25329
  config.pairs = this.pairs.filter((_, i) => i !== index);
@@ -25036,7 +25335,7 @@ var PairController = class {
25036
25335
  await this.persist();
25037
25336
  }
25038
25337
  livePair(index) {
25039
- const pair = this.runtime.tradingPairs?.[index];
25338
+ const pair = this.runtime.tradingPairs[index];
25040
25339
  if (!pair) throw new Error(`Unknown pair ${index}`);
25041
25340
  return pair;
25042
25341
  }
@@ -25128,9 +25427,9 @@ var ChainController = class {
25128
25427
  throw error;
25129
25428
  }
25130
25429
  const curve = confirmationPolicies[String(chainId)];
25131
- if (curve) this.runtime.confirmationPolicy?.add(chainId, curve);
25132
- else if (!this.runtime.confirmationPolicy?.has(chainId)) {
25133
- this.runtime.confirmationPolicy?.adopt(chainId, resolvedPolicy);
25430
+ if (curve) this.runtime.confirmationPolicy.add(chainId, curve);
25431
+ else if (!this.runtime.confirmationPolicy.has(chainId)) {
25432
+ this.runtime.confirmationPolicy.adopt(chainId, resolvedPolicy);
25134
25433
  }
25135
25434
  this.runtime.resolvedChains.push(resolved);
25136
25435
  config.chains = [...config.chains, { rpcUrls: chain.rpcUrls, bundlerUrl: chain.bundlerUrl }];
@@ -25158,7 +25457,7 @@ var ChainController = class {
25158
25457
  await intentFiller.removeChain(chainId);
25159
25458
  if (this.ownsScanner) await this.scanner.removeChain(chainId);
25160
25459
  configService.removeChain(chainId);
25161
- this.runtime.confirmationPolicy?.remove(chainId);
25460
+ this.runtime.confirmationPolicy.remove(chainId);
25162
25461
  this.runtime.chainClientManager.invalidate(chainKey);
25163
25462
  this.runtime.resolvedChains.splice(index, 1);
25164
25463
  config.chains = config.chains.filter((_, i) => i !== index);
@@ -25377,7 +25676,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25377
25676
  const tomlSigner = options.config.simplex?.signer;
25378
25677
  if (tomlSigner && !options.signer) {
25379
25678
  throw new Error(
25380
- "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."
25381
25680
  );
25382
25681
  }
25383
25682
  const loggers = new LoggerContext({
@@ -25581,6 +25880,8 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25581
25880
  0 && (module.exports = {
25582
25881
  AssetController,
25583
25882
  ChainController,
25883
+ LimitOrderController,
25884
+ LimitOrderValidationError,
25584
25885
  LoggerContext,
25585
25886
  MemoryDataStore,
25586
25887
  OrderScanner,
@@ -25593,6 +25894,7 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25593
25894
  WalletController,
25594
25895
  addLogSink,
25595
25896
  assertConfirmationCoverage,
25897
+ canDerive,
25596
25898
  configureLogger,
25597
25899
  createSigner,
25598
25900
  digestSigner,
@@ -25605,10 +25907,12 @@ var Simplex = class _Simplex extends import_node_events3.EventEmitter {
25605
25907
  pickAnchorStable,
25606
25908
  privateKeySigner,
25607
25909
  registrySymbols,
25910
+ secretPhraseSigner,
25608
25911
  turnkeySigner,
25609
25912
  validateAssetDefinitions,
25610
25913
  validateConfig,
25611
25914
  validatePairConfigs,
25915
+ validateSecretPhrase,
25612
25916
  validateSignerConfig,
25613
25917
  validateVaultToml,
25614
25918
  viemSigner