@hyperbridge/simplex 0.18.1 → 0.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import { HexString, ERC7821Call, IntentsCoprocessor, Order, TokenInfo, ChainConfig, ExecutionResult, IntentGateway, FillOptions, FillerConfig } from '@hyperbridge/sdk';
3
- import { S as SignerConfig, a as Signer, b as Signature, D as DerivingSigner, c as SecretPhraseSignerConfig, M as MpcVaultSignerConfig, T as TurnkeySignerConfig, C as ChainClientManager, F as FillerConfigService, U as UserOpSender, L as LoggerContext, A as ActivityStore, d as ActivityInsert, e as ActivityEvent, O as OrderHistoryPage, W as WalletTx, f as LimitOrderStore, g as AssetRegistry, h as DelegationService, i as LimitOrder, j as LimitOrderFilter, B as BidStore, k as SimplexDataStore, l as ConfirmationPolicy, R as ResolvedChainConfig, m as FillerTomlConfig, V as VaultToml, n as TokenSender, o as BridgeTopUp, p as AssetDefinition, q as ChainConfirmationPolicy, r as LimitOrderFill, s as StoredBid, t as RebalancingConfig, u as LogSink, v as BidStats, w as AllowlistConfig, x as LogLevel, y as StateStore, I as InventoryStore } from './types-6MEltdkX.js';
4
- export { z as ActivityType, E as AuthorizationRequest, G as BidInsert, H as BinanceConfig, J as CurveConfig, K as CurvePoint, N as Eip7702Authorization, P as FillerConfigFile, Q as InventorySnapshot, X as LimitOrderFillInsert, Y as LimitOrderFillRecord, Z as LimitOrderHold, _ as LimitOrderInsert, $ as LimitOrderPosting, a0 as LimitOrderSide, a1 as LimitOrderStatus, a2 as Logger, a3 as OperatorNotification, a4 as PrivateKeySignerConfig, a5 as QueueConfig, a6 as RuntimeState, a7 as SignerTransaction, a8 as SignerType, a9 as TypedDataPayload, aa as USD_STABLE_SYMBOLS, ab as UserProvidedChainConfig, ac as VaultTomlConfig, ad as WalletTxKind, ae as addLogSink, af as assertConfirmationCoverage, ag as configureLogger, ah as formatChainKey, ai as getLogger, aj as isRegistrySymbol, ak as normalizeSymbol, al as parseChainKey, am as registrySymbols, an as validateAssetDefinitions, ao as validateConfig, ap as validateVaultToml } from './types-6MEltdkX.js';
2
+ import { HexString, ERC7821Call, IntentsCoprocessor, Order, TokenInfo, ChainConfig, BidSubmissionResult, ExecutionResult, IntentGateway, FillOptions, FillerConfig } from '@hyperbridge/sdk';
3
+ import { S as SignerConfig, a as Signer, b as Signature, D as DerivingSigner, c as SecretPhraseSignerConfig, M as MpcVaultSignerConfig, T as TurnkeySignerConfig, C as ChainClientManager, F as FillerConfigService, U as UserOpSender, d as DelegationService, L as LoggerContext, A as ActivityStore, e as ActivityInsert, f as ActivityEvent, O as OrderHistoryPage, W as WalletTx, g as LimitOrderStore, h as AssetRegistry, i as LimitOrder, j as LimitOrderFilter, B as BidStore, k as SimplexDataStore, l as ConfirmationPolicy, R as ResolvedChainConfig, m as FillerTomlConfig, V as VaultToml, n as TokenSender, o as BridgeTopUp, p as AssetDefinition, q as ChainConfirmationPolicy, r as LimitOrderFill, s as StoredBid, t as RebalancingConfig, u as LogSink, v as BidStats, w as AllowlistConfig, x as LogLevel, y as StateStore, I as InventoryStore } from './types-CW6STIE7.js';
4
+ export { z as ActivityType, E as AuthorizationRequest, G as BidInsert, H as BidPricing, J as BinanceConfig, K as CurveConfig, N as CurvePoint, P as Eip7702Authorization, Q as FillerConfigFile, X as InventorySnapshot, Y as LimitOrderFillInsert, Z as LimitOrderFillRecord, _ as LimitOrderInsert, $ as LimitOrderPosting, a0 as LimitOrderSide, a1 as LimitOrderStatus, a2 as Logger, a3 as OperatorNotification, a4 as PrivateKeySignerConfig, a5 as QueueConfig, a6 as RuntimeState, a7 as SignerTransaction, a8 as SignerType, a9 as TypedDataPayload, aa as USD_STABLE_SYMBOLS, ab as UserProvidedChainConfig, ac as VaultTomlConfig, ad as WalletTxKind, ae as addLogSink, af as assertConfirmationCoverage, ag as configureLogger, ah as formatChainKey, ai as getLogger, aj as isRegistrySymbol, ak as normalizeSymbol, al as parseChainKey, am as registrySymbols, an as validateAssetDefinitions, ao as validateConfig, ap as validateVaultToml } from './types-CW6STIE7.js';
5
5
  import { EventEmitter as EventEmitter$1 } from 'events';
6
6
  import { Decimal } from 'decimal.js';
7
7
  import * as _circle_fin_bridge_kit from '@circle-fin/bridge-kit';
@@ -28,10 +28,15 @@ interface FundingVenue {
28
28
  * from LP positions on `destChain`. Returns the calls and the credited
29
29
  * amount that will become available after execution.
30
30
  *
31
+ * `alreadyPlanned` is what the caller's current evaluation has already
32
+ * planned from this venue for this token on this chain, and is taken off
33
+ * what the venue can release. It is the caller's to keep; the venue holds
34
+ * nothing between calls.
35
+ *
31
36
  * Access is serialised per chain via a mutex so concurrent evaluations
32
37
  * do not race on shared liquidity state.
33
38
  */
34
- planWithdrawalForToken(destChain: string, solver: HexString, tokenOutLower: string, amountNeeded: bigint, deadlineTimestamp?: bigint): Promise<FundingPlanResult>;
39
+ planWithdrawalForToken(destChain: string, solver: HexString, tokenOutLower: string, amountNeeded: bigint, deadlineTimestamp?: bigint, alreadyPlanned?: bigint): Promise<FundingPlanResult>;
35
40
  /**
36
41
  * Wallet balance of `tokenLower` the fill must keep liquid and never source
37
42
  * from — the vault's `minBalance` floor, reserved for gas/paymaster paid in
@@ -85,7 +90,7 @@ interface VaultBalancePosition {
85
90
  decimals: number;
86
91
  /** Solver-owned vault position in underlying-asset terms. */
87
92
  positionAssets: bigint;
88
- /** Immediately withdrawable amount after pending-fill reservations. */
93
+ /** Immediately withdrawable amount: the vault's `maxWithdraw(solver)`. */
89
94
  availableAssets: bigint;
90
95
  /** Underlying amount deliberately kept liquid in the solver wallet. */
91
96
  walletReserve: bigint;
@@ -252,7 +257,7 @@ interface AssetBalanceRow {
252
257
  walletReserve: number;
253
258
  /** Total solver-owned underlying represented by vault shares. */
254
259
  vaultPosition: number;
255
- /** Vault assets that can be withdrawn now, after pending-fill reservations. */
260
+ /** Vault assets that can be withdrawn now: the vault's `maxWithdraw(solver)`. */
256
261
  vaultAvailable: number;
257
262
  /** Wallet plus vault position. Null when any required source was unavailable. */
258
263
  total: number | null;
@@ -327,6 +332,14 @@ declare class BalanceProvider extends EventEmitter {
327
332
  refresh(): Promise<BalanceSnapshot>;
328
333
  private collectChain;
329
334
  private addConfiguredStable;
335
+ private hyperbridgeAccount?;
336
+ /**
337
+ * Re-reads the filler's BRIDGE balance on Hyperbridge, outside the once-a-minute interval:
338
+ * a BRIDGE top-up calls it the moment its delivery lands, so the dashboard's reload shows
339
+ * the new balance. A failed read leaves the last snapshot in place. No-op while tracking
340
+ * is not initialized (no Hyperbridge key).
341
+ */
342
+ refreshHyperbridgeBalance(): Promise<void>;
330
343
  private trackHyperbridgeBalance;
331
344
  }
332
345
 
@@ -503,6 +516,13 @@ declare class VaultFundingPlanner implements FundingVenue {
503
516
  private readonly clientManager;
504
517
  private config;
505
518
  private readonly userOpSender?;
519
+ /**
520
+ * Read-only EIP-7702 check for the native-tx fallback. A vault batch is an
521
+ * `executeBatch` on the solver account, so it only does anything once the EOA is
522
+ * delegated to that account; sent to a codeless EOA it succeeds and moves nothing.
523
+ * Left out, the fallback is sent unchecked (library callers without a signer).
524
+ */
525
+ private readonly delegation?;
506
526
  private readonly logger;
507
527
  name: string;
508
528
  private stateByChain;
@@ -525,7 +545,14 @@ declare class VaultFundingPlanner implements FundingVenue {
525
545
  * sponsored UserOps (gas paid in USDC) where the chain supports it, falling back
526
546
  * to a native EOA tx. Omit to always use native txs.
527
547
  */
528
- constructor(clientManager: ChainClientManager, config: VaultOutputFundingConfig, userOpSender?: UserOpSender | undefined);
548
+ constructor(clientManager: ChainClientManager, config: VaultOutputFundingConfig, userOpSender?: UserOpSender | undefined,
549
+ /**
550
+ * Read-only EIP-7702 check for the native-tx fallback. A vault batch is an
551
+ * `executeBatch` on the solver account, so it only does anything once the EOA is
552
+ * delegated to that account; sent to a codeless EOA it succeeds and moves nothing.
553
+ * Left out, the fallback is sent unchecked (library callers without a signer).
554
+ */
555
+ delegation?: Pick<DelegationService, "isDelegated"> | undefined);
529
556
  /** Invoked after each submitted sweep/redeem batch so wallet history can record it. */
530
557
  onTx?: (tx: {
531
558
  chain: string;
@@ -555,9 +582,9 @@ declare class VaultFundingPlanner implements FundingVenue {
555
582
  initialise(solver: HexString): Promise<void>;
556
583
  refresh(chain?: string): Promise<void>;
557
584
  /**
558
- * Returns a coherent read-only view of every configured vault. Refreshing
559
- * under the same per-chain mutex used by withdrawal planning ensures the
560
- * displayed availability includes all live pending-fill reservations.
585
+ * Returns a coherent read-only view of every configured vault, refreshed
586
+ * under the same per-chain mutex withdrawal planning uses. Availability is
587
+ * the vault's own `maxWithdraw(solver)`.
561
588
  */
562
589
  getBalanceSnapshot(requestedChain?: string): Promise<VaultBalancePosition[]>;
563
590
  /**
@@ -570,8 +597,13 @@ declare class VaultFundingPlanner implements FundingVenue {
570
597
  * Produces a single `vault.withdraw` ERC-7821 call that sends up to
571
598
  * `amountNeeded` of `tokenOutLower` to the solver, capped by the vault's
572
599
  * `maxWithdraw`. Returns a no-op when no configured vault holds the token.
600
+ *
601
+ * `maxWithdraw` is read afresh for every plan, less `alreadyPlanned`: what
602
+ * the caller's own evaluation has planned from this vault so far. Nothing is
603
+ * held back across evaluations: a plan whose withdrawal another evaluation's
604
+ * fill has already spent reverts its batch on chain, which is the accepted cost.
573
605
  */
574
- planWithdrawalForToken(destChain: string, solver: HexString, tokenOutLower: string, amountNeeded: bigint, _deadlineTimestamp?: bigint): Promise<FundingPlanResult>;
606
+ planWithdrawalForToken(destChain: string, solver: HexString, tokenOutLower: string, amountNeeded: bigint, _deadlineTimestamp?: bigint, alreadyPlanned?: bigint): Promise<FundingPlanResult>;
575
607
  /**
576
608
  * Starts the periodic sweep timer. Runs one sweep shortly after start, then
577
609
  * every `sweepIntervalMs`. Idempotent — a second call is a no-op.
@@ -601,7 +633,9 @@ declare class VaultFundingPlanner implements FundingVenue {
601
633
  *
602
634
  * The sponsored path only falls back to native when the op was **never
603
635
  * submitted**; a submitted-but-unconfirmed op throws so a native resend can't
604
- * double-execute the batch (the caller's timer logs and retries next cycle).
636
+ * double-execute the batch (the caller's timer logs and retries next cycle). So does a
637
+ * `PendingOpOnNonceError`: an earlier op from this wallet still holds the nonce, which is
638
+ * "not now", never a reason to send natively.
605
639
  */
606
640
  private submitBatch;
607
641
  /**
@@ -816,413 +850,774 @@ declare class EventMonitor extends EventEmitter$1 {
816
850
  stopListening(): Promise<void>;
817
851
  }
818
852
 
819
- /**
820
- * An execution outcome that can also report a still-pooled bid.
821
- *
822
- * `submitBid` can time out with the extrinsic still in Hyperbridge's pool. That
823
- * is neither success nor failure: the extrinsic may yet land and reserve a
824
- * deposit, so the bid record has to be reclaimable even though `success` is
825
- * false. Widened here rather than on the SDK's `ExecutionResult` so simplex does
826
- * not need an unreleased SDK.
827
- */
828
- interface FillResult extends ExecutionResult {
829
- pending?: boolean;
830
- /**
831
- * The identifier Hyperbridge files the bid under, once its op was built:
832
- * `keccak256` of its calldata. It tells the bids on one order apart, and retracting the bid names it.
833
- */
834
- bid?: HexString;
853
+ interface Book {
854
+ id: string;
855
+ base: string;
856
+ quote: string;
835
857
  }
836
-
837
- interface FillerStrategy {
838
- name: string;
839
- canFill(order: Order): Promise<boolean>;
840
- calculateProfitability(order: Order): Promise<number>;
841
- executeOrder(order: Order, hyperbridge?: IntentsCoprocessor): Promise<FillResult>;
842
- /**
843
- * Optional hook for strategies to provide a USD value for the full input basket.
844
- * Returns null when the strategy cannot or does not want to price the order.
845
- */
846
- getOrderUsdValue?(order: Order): Promise<{
847
- inputUsd: Decimal;
848
- } | null>;
849
- /**
850
- * Optional confirmation policy for this strategy.
851
- * If absent, no confirmation waiting is required (e.g. same-chain strategies).
852
- */
853
- confirmationPolicy?: {
854
- getConfirmationBlocks: (chainId: number, amountUsd: number) => number;
855
- };
858
+ /** Live price levels. All prices and sizes are decimal integer strings at 1e18. */
859
+ interface OrderbookLevel {
860
+ fillChain: string;
861
+ priceBucket: string;
862
+ price: string;
863
+ worstPrice: string;
864
+ baseSize: string;
865
+ quoteSize: string;
866
+ orderCount: number;
867
+ solverCount: number;
856
868
  }
857
-
858
- /** Symbol and decimals for a token on a chain, as far as the filler knows them. */
859
- interface TokenDescription {
860
- symbol: string | null;
861
- decimals: number | null;
869
+ interface OrderbookSnapshot extends Book {
870
+ /** Best first: bids descending, asks ascending. */
871
+ bids: OrderbookLevel[];
872
+ asks: OrderbookLevel[];
873
+ /** True backing liquidity, in quote for bids and base for asks. */
874
+ bidLiquidity: string;
875
+ askLiquidity: string;
876
+ /** Price bucket width, quote per 1 base at 1e18; null when the orderbook does not report one. */
877
+ granularity: string | null;
862
878
  }
863
- /** Resolves display metadata for `token` (20-byte address) on `chain` (state machine id). */
864
- type TokenDescriber = (chain: string, token: string) => Promise<TokenDescription>;
865
- interface ActivityRecorderOptions {
866
- /**
867
- * Enriches order legs with symbol/decimals so the feed can show "19,585 USDC"
868
- * rather than a raw integer. Omit to record addresses and raw amounts only.
869
- */
870
- describeToken?: TokenDescriber;
879
+ /** One solver's order inside a price level. Prices and sizes are decimal integer strings at 1e18. */
880
+ interface OrderbookLevelOrder {
881
+ solver: string;
882
+ commitment: string;
883
+ fillChain: string;
884
+ price: string;
885
+ /** What the order publishes as depth, in the token its side pays out: quote for bids, base for asks. */
886
+ advertisedSize: string;
887
+ /** The signed output before any balance cap, in the same token as `advertisedSize`. */
888
+ quotedAmount: string;
889
+ /** True when the solver's balance, not the quote, binds `advertisedSize`. */
890
+ resized: boolean;
891
+ expiresAt: string;
892
+ acceptedSources: string[];
871
893
  }
872
- /**
873
- * Bridges the filler's order lifecycle onto an {@link ActivityStore}.
874
- *
875
- * The store used to subscribe to the `EventMonitor` itself, which meant a
876
- * consumer implementing the data interface also had to know about the filler's
877
- * internal event names. Ownership is inverted here: the recorder listens, the
878
- * store just receives rows.
879
- *
880
- * Writes are fire-and-forget — the activity feed is observability, and a failed
881
- * insert must never propagate back into the fill path that emitted it. Each
882
- * stored row is re-emitted as `event` for live consumers (the UI's SSE stream,
883
- * and `Simplex`'s `activity` event).
884
- */
885
- declare class ActivityRecorder extends EventEmitter {
886
- private store;
887
- private logger;
888
- private monitor?;
889
- private handlers;
890
- private describeToken?;
891
- /** Order summaries by order id, so filled/skipped rows carry the order too. Insertion-ordered for eviction. */
892
- private summaries;
893
- constructor(store: ActivityStore, loggers?: LoggerContext, options?: ActivityRecorderOptions);
894
- /** Subscribes to the filler's order lifecycle events. Idempotent per monitor. */
895
- attach(monitor: EventMonitor): void;
896
- private subscribe;
897
- /** Unsubscribes from the attached monitor. Safe to call when never attached. */
898
- detach(): void;
899
- private remember;
900
- private summaryFor;
901
- /**
902
- * Settles an order from its on-chain fill: `filled` when this filler won it,
903
- * `lost` (reason = the winner) when a rival did. A rival's fill is recorded
904
- * only for orders this filler has rows for — every fill on the chain passes
905
- * through here, and the feed is about this filler's orders.
906
- */
907
- private settle;
908
- /**
909
- * Captures the order for the feed. Token metadata lookups may hit the chain;
910
- * a failure degrades that leg to address + raw amount rather than dropping the row.
911
- */
912
- private summarize;
894
+ interface OrderbookLevelQuery {
895
+ side: "BID" | "ASK";
896
+ fillChain: string;
897
+ priceBucket: string;
898
+ sourceChain?: string;
899
+ }
900
+ interface OrderbookFilters {
901
+ sourceChain?: string;
902
+ fillChain?: string;
903
+ }
904
+ interface TokenMinSize {
905
+ symbol: string;
906
+ size: string;
907
+ }
908
+ interface ServerInfo {
909
+ minOrderTtlSecs: number;
910
+ heartbeatIntervalSecs: number;
911
+ signatureSkewSecs: number;
912
+ maxBatchSize: number;
913
+ minOrderSizes: TokenMinSize[];
913
914
  /**
914
- * Writes an event and emits the stored row. Never rejects — a failed write is
915
- * logged and dropped, because the caller is usually a fill in progress.
915
+ * Every chain the orderbook serves, by state machine id: the chains orders
916
+ * fill on, and the source chains they may declare.
916
917
  */
917
- record(event: ActivityInsert): void;
918
- recent(limit?: number, beforeId?: number): Promise<ActivityEvent[]>;
919
- fills(limit?: number): Promise<ActivityEvent[]>;
920
- orderHistory(page: number, pageSize: number): Promise<OrderHistoryPage>;
921
- recordWalletTx(tx: Omit<WalletTx, "id" | "ts">): Promise<void>;
922
- walletTxs(limit?: number): Promise<WalletTx[]>;
918
+ chains: string[];
919
+ eip712DomainName: string;
920
+ eip712DomainVersion: string;
923
921
  }
924
-
925
- /** Self-halt visibility/reset for one FX strategy (overfill protection). */
926
- interface HaltControl {
927
- index: number;
928
- isHalted(): boolean;
929
- resetHalt(): void;
922
+ /** One token on one chain, as the orderbook's own registry has it. */
923
+ interface ChainToken {
924
+ symbol: string;
925
+ /** Its decimals there: USDC and USDT carry 18 on BNB Chain and 6 on the rest. */
926
+ decimals: number;
930
927
  }
931
-
932
- /** What a limit order may pay out over its whole life, as the fill chain enforces it. */
933
- interface LimitOrderBudget {
934
- budgetId: HexString;
935
- /** The order's whole size in the output token's own units. */
936
- cap: bigint;
937
- /** The token the order pays on its fill chain. */
938
- token: HexString;
928
+ /** One chain the orderbook serves, with the tokens it registers on it. */
929
+ interface ChainInfo {
930
+ /** State machine id, such as `EVM-8453`. */
931
+ id: string;
932
+ name: string;
933
+ tokens: ChainToken[];
939
934
  }
940
-
941
- interface SwapCall {
942
- to: string;
943
- data: string;
944
- value: string;
935
+ /** `serverInfo`, `books` and `chains` together, which is how simplex reads them. */
936
+ interface OrderbookLimits {
937
+ serverInfo: ServerInfo;
938
+ books: Book[];
939
+ chains: ChainInfo[];
945
940
  }
946
- interface CachedPairClassification {
947
- inputIsStable: boolean;
948
- stableToken: string;
949
- exoticToken: string;
941
+ interface PostedOrder {
942
+ commitment: HexString;
943
+ side: "BID" | "ASK";
944
+ status: "ACTIVE" | "SUSPENDED";
945
+ price: string;
946
+ quotedAmount: string;
947
+ advertisedSize: string;
948
+ expiresAt: string;
949
+ acceptedSources: string[];
950
+ /** The balance, not the quote, is what binds `advertisedSize`. */
951
+ resized?: boolean;
952
+ /**
953
+ * Whether the solver's balance covers the whole quote. Also false before any
954
+ * balance has been read, which is why {@link PostedOrder.validatedAt} decides
955
+ * whether a false here means anything.
956
+ */
957
+ backed?: boolean;
958
+ /**
959
+ * The last cycle that read this order's balance, absent until one has. An
960
+ * order surfaces at its quoted size meanwhile.
961
+ */
962
+ validatedAt?: string | null;
950
963
  }
951
964
  /**
952
- * One bid: what a single limit order pays for an incoming order, and what funds it.
965
+ * Every code the orderbook's `RejectionCode` enum can send, listed rather than
966
+ * written as a union so a test can hold it against the published schema.
953
967
  *
954
- * An order that several limit orders can serve produces several of these, one per
955
- * order, each priced against the whole input at its own rate. They are never
956
- * summed: each bid is its own fill, and the gateway clamps whichever one lands
957
- * against what is still outstanding.
968
+ * Why the orderbook refuses an order. `MIN_ORDER_SIZE`, `TTL_TOO_SHORT` and
969
+ * `UNSUPPORTED_SOURCE_CHAIN` are prevented by validation before posting, the last
970
+ * against the chains the server serves and the tokens its registry lists on each;
971
+ * `REPLAYED` and `ORDER_EXISTS` are answered by bumping the nonce; the rest are
972
+ * encoding bugs on our side.
958
973
  */
959
- interface BidPlan {
960
- limitOrderId: string;
961
- /** The order leg this bid fills; every other leg is quoted at zero. */
962
- leg: number;
963
- /** What this bid draws from that limit order, at 1e18. */
964
- payout: bigint;
965
- /** The outputs the bid signs, in the output token's own units. */
966
- fillerOutputs: TokenInfo[];
967
- /** The take signed beside each output: the escrow this bid's fill earns. */
968
- fillerInputs: TokenInfo[];
969
- /** The decimals `fillerInputs[leg]` is counted in: the input token's, on the order's source chain. */
970
- inputDecimals?: number;
971
- fundingCalls: ERC7821Call[];
972
- partialFill: boolean;
973
- profit: number;
974
- /** That limit order's budget, which the bid settles against on the fill chain. */
975
- budget?: LimitOrderBudget;
976
- }
977
- declare class CacheService {
978
- private cacheData;
979
- private readonly CACHE_EXPIRY_MS;
980
- private logger;
981
- constructor(loggers?: LoggerContext);
982
- private isCacheValid;
983
- private cleanupStaleData;
984
- getGasEstimate(orderId: string): {
985
- totalCostInSourceFeeToken: bigint;
986
- relayerFeeInSourceFeeToken: bigint;
987
- dispatchFee: bigint;
988
- callGasLimit: bigint;
989
- verificationGasLimit: bigint;
990
- preVerificationGas: bigint;
991
- maxFeePerGas: bigint;
992
- maxPriorityFeePerGas: bigint;
993
- totalGasCostWei: bigint;
994
- } | null;
995
- setGasEstimate(orderId: string, totalCostInSourceFeeToken: bigint, relayerFeeInSourceFeeToken: bigint, dispatchFee: bigint, callGasLimit: bigint, verificationGasLimit: bigint, preVerificationGas: bigint, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint, totalGasCostWei: bigint): void;
996
- getSwapOperations(orderId: string): {
997
- calls: SwapCall[];
998
- totalGasEstimate: bigint;
999
- } | null;
1000
- setSwapOperations(orderId: string, calls: SwapCall[], totalGasEstimate: bigint): void;
1001
- getFillerOutputs(orderId: string): {
1002
- token: HexString;
1003
- amount: bigint;
1004
- }[] | null;
1005
- /** The takes cached with the order's filler outputs, or null when absent or stale. */
1006
- getFillerInputs(orderId: string): {
1007
- token: HexString;
1008
- amount: bigint;
1009
- }[] | null;
974
+ declare const REJECTION_CODES: readonly ["MALFORMED_USER_OP", "NO_FILL_ORDER", "UNSUPPORTED_CHAIN", "UNSUPPORTED_SHAPE", "NOT_PHANTOM", "TTL_TOO_SHORT", "UNSUPPORTED_PAIR", "BAD_SIGNATURE", "BAD_NONCE_BINDING", "REPLAYED", "ORDER_EXISTS", "TOO_MANY_ORDERS", "MIN_ORDER_SIZE", "MISSING_DECLARATION", "EMPTY_DECLARATION", "UNSUPPORTED_SOURCE_CHAIN", "NO_LIMIT_ORDER", "TOO_MANY_BIDS"];
975
+ type RejectionCode = (typeof REJECTION_CODES)[number];
976
+ /**
977
+ * Why the orderbook could not decide an op at all. Ingest reads no chain, so it
978
+ * is the database or the submission running out of time, and both are worth
979
+ * another attempt.
980
+ */
981
+ declare const FAILURE_CODES: readonly ["DATABASE_UNAVAILABLE", "TIMEOUT"];
982
+ type FailureCode = (typeof FAILURE_CODES)[number];
983
+ type SubmitOrderResult = {
984
+ kind: "accepted";
985
+ order: PostedOrder;
986
+ surfaced: boolean;
987
+ } | {
988
+ kind: "unchanged";
989
+ order: PostedOrder;
990
+ } | {
991
+ kind: "rejected";
992
+ code: RejectionCode;
993
+ message: string;
994
+ } | {
995
+ kind: "failed";
996
+ code: FailureCode | "REQUEST_FAILED";
997
+ message: string;
998
+ retryable: boolean;
999
+ };
1000
+ /**
1001
+ * A bid on a swapper's order as the orderbook holds it, under the order's commitment. Amounts
1002
+ * and the price are decimal strings at 1e18.
1003
+ */
1004
+ interface PostedBid {
1005
+ commitment: HexString;
1006
+ /** Which of the order's legs the bid quotes, from 0. */
1007
+ leg: number;
1008
+ side: "BID" | "ASK";
1009
+ fillChain: string;
1010
+ sourceChain: string;
1011
+ /** The rate the bid signed, quote per 1 base, before the protocol fee. */
1012
+ price: string;
1013
+ amountIn: string;
1014
+ amountOut: string;
1015
+ /** ISO-8601: when the orderbook stops serving the bid (`receivedAt` plus `serverInfo.bidTtlSecs`). */
1016
+ expiresAt: string;
1017
+ }
1018
+ /**
1019
+ * The orderbook's answer to a bid. `unchanged` is the same op posted again while its bid is
1020
+ * still live, which changed nothing; a bid is served until its TTL and cannot be withdrawn.
1021
+ */
1022
+ type SubmitBidResult = {
1023
+ kind: "accepted";
1024
+ bid: PostedBid;
1025
+ } | {
1026
+ kind: "unchanged";
1027
+ bid: PostedBid;
1028
+ } | {
1029
+ kind: "rejected";
1030
+ code: RejectionCode;
1031
+ message: string;
1032
+ } | {
1033
+ kind: "failed";
1034
+ code: FailureCode | "REQUEST_FAILED";
1035
+ message: string;
1036
+ retryable: boolean;
1037
+ };
1038
+ /** Why a signed message was refused, shared by `cancelOrder` and `heartbeat`. */
1039
+ declare const MESSAGE_REJECTION_CODES: readonly ["BAD_SIGNATURE", "SOLVER_MISMATCH", "SIGNATURE_EXPIRED", "SIGNATURE_REUSED", "UNKNOWN_ORDER", "UNKNOWN_SOLVER"];
1040
+ type MessageRejectionCode = (typeof MESSAGE_REJECTION_CODES)[number];
1041
+ type SolverStatus = "ACTIVE" | "SUSPENDED";
1042
+ type HeartbeatResult = {
1043
+ kind: "accepted";
1044
+ status: SolverStatus;
1045
+ reactivatedOrders: number;
1046
+ heartbeatDueBy: string;
1047
+ } | {
1048
+ kind: "rejected";
1049
+ code: MessageRejectionCode;
1050
+ message: string;
1051
+ };
1052
+ /** One page of the solver's own orders, as reconciliation walks them. */
1053
+ interface PostedOrderPage {
1054
+ orders: PostedOrder[];
1055
+ /** Cursor for the next page, absent on the last one. */
1056
+ cursor?: string;
1057
+ status: SolverStatus;
1058
+ }
1059
+ type CancelOrderResult = {
1060
+ kind: "cancelled";
1061
+ commitment: HexString;
1062
+ } | {
1063
+ kind: "rejected";
1064
+ code: MessageRejectionCode;
1065
+ message: string;
1066
+ }
1067
+ /**
1068
+ * The request never got an answer. Distinct from `UNKNOWN_ORDER`, which is the
1069
+ * orderbook saying the entry is already gone: here the entry may well still be
1070
+ * live, and a caller that treats the two alike orphans it.
1071
+ */
1072
+ | {
1073
+ kind: "failed";
1074
+ message: string;
1075
+ };
1076
+
1077
+ /**
1078
+ * Talks to one HyperFX orderbook over GraphQL.
1079
+ *
1080
+ * Stateless apart from the endpoint and timeout. Every method either returns the
1081
+ * orderbook's own answer, including its refusals, or throws
1082
+ * {@link OrderbookRequestError} because it could not get one.
1083
+ */
1084
+ declare class OrderbookClient {
1085
+ private readonly requestTimeoutMs;
1086
+ private logger;
1087
+ /** The configured endpoint, with `/graphql` if it was left off. */
1088
+ private readonly url;
1089
+ constructor(url: string, requestTimeoutMs: number, loggers?: LoggerContext);
1090
+ /** The limits to validate against before posting, plus the books on offer. */
1091
+ limits(): Promise<OrderbookLimits>;
1092
+ /** The public book across all solvers, restricted to the selected route. */
1093
+ snapshot(id: string, filters?: OrderbookFilters): Promise<OrderbookSnapshot | null>;
1010
1094
  /**
1011
- * The budget of the limit order that priced the outputs cached for this order, or
1012
- * null when no limit order did.
1013
- *
1014
- * Kept on the same entry as the outputs, so the two are replaced and expire
1015
- * together: a bid can never sign one limit order's payout beside another's budget.
1095
+ * The orders behind one price level. The orderbook filters orders by side and route but not by
1096
+ * price bucket, so this walks the side's orders on the level's fill chain and keeps the bucket.
1097
+ * Null when the book does not exist.
1016
1098
  */
1017
- getBidBudget(orderId: string): LimitOrderBudget | null;
1018
- setFillerOutputs(orderId: string, outputs: {
1019
- token: HexString;
1020
- amount: bigint;
1021
- }[], inputs?: {
1022
- token: HexString;
1023
- amount: bigint;
1024
- }[], budget?: LimitOrderBudget): void;
1099
+ levelOrders(id: string, level: OrderbookLevelQuery): Promise<OrderbookLevelOrder[] | null>;
1100
+ submitOrder(userOp: HexString): Promise<SubmitOrderResult>;
1025
1101
  /**
1026
- * The limit order this order was priced against, and the payout it allowed.
1027
- *
1028
- * Carried from `calculateProfitability` to the bid so the reservation lands on
1029
- * the same limit order the price came from. A fill later finds it through the
1030
- * bid row, not through here.
1102
+ * Posts a bid on a swapper's order: the signed fill UserOp, SCALE-encoded, exactly as it goes
1103
+ * to Hyperbridge. The orderbook verifies it as it does a limit order, except that it need not
1104
+ * be phantom and carries no TTL, and accepts it only from a solver holding an active limit
1105
+ * order on the leg's book filling on the order's destination (`NO_LIMIT_ORDER`). It serves
1106
+ * the bid under the order's commitment for `serverInfo.bidTtlSecs`; there is no withdrawing
1107
+ * it before then.
1031
1108
  */
1032
- getMatchedLimitOrder(orderId: string): {
1033
- limitOrderId: string;
1034
- payout: bigint;
1035
- }[];
1036
- getBidPlans(orderId: string): BidPlan[];
1037
- setBidPlans(orderId: string, plans: BidPlan[]): void;
1038
- clearBidPlans(orderId: string): void;
1039
- setMatchedLimitOrder(orderId: string, holds: {
1040
- limitOrderId: string;
1041
- payout: bigint;
1042
- }[]): void;
1043
- getPairClassifications(orderId: string): CachedPairClassification[] | null;
1044
- setPairClassifications(orderId: string, pairs: CachedPairClassification[]): void;
1045
- getFundingPrepends(orderId: string): {
1046
- calls: ERC7821Call[];
1047
- } | null;
1048
- setFundingPrepends(orderId: string, calls: ERC7821Call[]): void;
1049
- clearFundingPrepends(orderId: string): void;
1109
+ submitBid(userOp: HexString): Promise<SubmitBidResult>;
1050
1110
  /**
1051
- * Whether the strategy's completed evaluation of this order chose a partial fill.
1111
+ * The entry this solver holds at `commitment`, or null when it holds none.
1052
1112
  *
1053
- * Only ever written by an evaluation that went on to return a fillable answer,
1054
- * and cleared at the start of every evaluation — so a refusal can never leave a
1055
- * `true` behind for the caller to act on.
1113
+ * What tells `ORDER_EXISTS` from `REPLAYED`: the first means a live entry is
1114
+ * already sitting there, and posting again on a new nonce would put a second
1115
+ * one behind the same liability.
1056
1116
  */
1057
- isPartialFill(orderId: string): boolean;
1058
- setPartialFill(orderId: string, partial: boolean): void;
1059
- clearPartialFill(orderId: string): void;
1117
+ orderAt(solver: HexString, commitment: HexString): Promise<PostedOrder | null>;
1118
+ cancelOrder(params: {
1119
+ solver: HexString;
1120
+ commitment: HexString;
1121
+ timestamp: number;
1122
+ signature: HexString;
1123
+ }): Promise<CancelOrderResult>;
1124
+ heartbeat(params: {
1125
+ solver: HexString;
1126
+ timestamp: number;
1127
+ signature: HexString;
1128
+ }): Promise<HeartbeatResult>;
1060
1129
  /**
1061
- * Whether the strategy's completed evaluation let a bid through without holding
1062
- * `order.fees` to the execution cost, because the order is at or above the
1063
- * operator's minimum size.
1130
+ * One page of the solver's own orders as the orderbook holds them.
1064
1131
  *
1065
- * Written and cleared the way {@link isPartialFill} is, for the same reason: the
1066
- * caller exempts such an order from its profit floor.
1132
+ * A solver the orderbook has never accepted an order from is not an error:
1133
+ * it answers with no solver at all, which reads here as an empty page.
1067
1134
  */
1068
- isFeeCheckWaived(orderId: string): boolean;
1069
- setFeeCheckWaived(orderId: string, waived: boolean): void;
1070
- clearFeeCheckWaived(orderId: string): void;
1071
- getFeeTokenWithDecimals(chain: string): {
1072
- address: HexString;
1073
- decimals: number;
1074
- } | null;
1075
- setFeeTokenWithDecimals(chain: string, address: HexString, decimals: number): void;
1076
- getTokenDecimals(chain: string, tokenAddress: HexString): number | null;
1077
- setTokenDecimals(chain: string, tokenAddress: HexString, decimals: number): void;
1078
- getSolverSelection(chain: string): boolean | null;
1079
- setSolverSelection(chain: string, active: boolean): void;
1135
+ myOrders(solver: HexString, after?: string): Promise<PostedOrderPage>;
1136
+ private request;
1080
1137
  }
1081
1138
 
1082
- /** What a bid's batch needs to settle its limit order's budget. */
1083
- interface BudgetSettlement {
1084
- budget: LimitOrderBudget;
1085
- /** The solver's account, which is the only caller `debitOrder` accepts. */
1086
- solverAccount: HexString;
1087
- }
1088
- declare class ContractInteractionService {
1089
- private clientManager;
1090
- private configService;
1091
- cacheService: CacheService;
1092
- private logger;
1093
- private sdkHelperCache;
1094
- private blockTimes;
1095
- private solverAccountAddress;
1096
- private signer;
1097
- constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer, sharedCacheService?: CacheService);
1139
+ /**
1140
+ * Where a bid goes. A strategy builds the signed fill op and hands it here; the venues decide
1141
+ * who receives it. The filler passes one of these to `FillerStrategy.executeOrder`.
1142
+ */
1143
+ interface BidPlacer {
1098
1144
  /**
1099
- * The endpoint SDK helpers should use for `chain`: the first configured RPC,
1100
- * matching the one viem's `fallback` transport tries first.
1145
+ * @param commitment - The swap order the bid is on.
1146
+ * @param userOp - The signed fill UserOp, SCALE-encoded: the same bytes every venue takes.
1147
+ * @param bid - The identifier Hyperbridge files the bid under, `keccak256` of its calldata.
1101
1148
  */
1102
- private primaryRpcUrl;
1149
+ place(commitment: HexString, userOp: HexString, bid: HexString): Promise<BidPlacement>;
1150
+ }
1151
+ /** What each venue said. A venue the bid was not sent to is absent. */
1152
+ interface BidPlacement {
1153
+ /** Hyperbridge's answer: the extrinsic landed, is pooled, or failed. */
1154
+ hyperbridge?: BidSubmissionResult;
1155
+ /** The HyperFX orderbook's answer. */
1156
+ orderbook?: SubmitBidResult;
1157
+ }
1158
+
1159
+ /**
1160
+ * An execution outcome that can also report a still-pooled bid, and what the orderbook said.
1161
+ *
1162
+ * `success`, `txHash`, `pending` and `error` are Hyperbridge's answer. `submitBid` can time out
1163
+ * with the extrinsic still in Hyperbridge's pool. That is neither success nor failure: the
1164
+ * extrinsic may yet land and reserve a deposit, so the bid record has to be reclaimable even
1165
+ * though `success` is false. Widened here rather than on the SDK's `ExecutionResult` so simplex
1166
+ * does not need an unreleased SDK.
1167
+ *
1168
+ * A bid goes to the HyperFX orderbook as well, and to it alone when the filler holds no BRIDGE
1169
+ * for the Hyperbridge deposit; `orderbook` is that venue's answer. A bid the orderbook holds is
1170
+ * live whatever Hyperbridge said.
1171
+ */
1172
+ interface FillResult extends ExecutionResult {
1173
+ pending?: boolean;
1103
1174
  /**
1104
- * Gets the SDK helper for a given source and destination chain.
1105
- * Instances are cached and reused to avoid redundant RPC calls.
1175
+ * The identifier Hyperbridge files the bid under, once its op was built:
1176
+ * `keccak256` of its calldata. It tells the bids on one order apart, and retracting the bid names it.
1106
1177
  */
1107
- getIntentGateway(source: string, destination: string): Promise<IntentGateway>;
1108
- /**
1109
- * Best-effort cache warm-up. Deliberately never rejects: this runs unawaited from the
1110
- * constructor, so a rejection would surface as an unhandled rejection and take the
1111
- * process down. A failed prewarm only means the value is fetched on first use, where
1112
- * `getTokenDecimals` is strict — which is the right place for strictness, because
1113
- * there it can skip a single order instead of killing the filler at boot.
1114
- */
1115
- initCache(): Promise<void>;
1116
- getCache(): CacheService;
1117
- /**
1118
- * Gets the decimals for a token
1119
- */
1120
- getTokenDecimals(tokenAddress: string, chain: string): Promise<number>;
1178
+ bid?: HexString;
1179
+ /** The orderbook's answer; absent when the bid was not offered to it. */
1180
+ orderbook?: SubmitBidResult;
1181
+ }
1182
+ interface FillerStrategy {
1183
+ name: string;
1184
+ canFill(order: Order): Promise<boolean>;
1185
+ calculateProfitability(order: Order): Promise<number>;
1121
1186
  /**
1122
- * Estimates gas for filling an order and caches the full estimate for bid preparation
1187
+ * Places the strategy's bid on `order` through `bids`, the venues the filler bids on
1188
+ * (Hyperbridge and the HyperFX orderbook). Without `bids` the destination selects no
1189
+ * solver, so there is nothing to bid on.
1123
1190
  */
1124
- estimateGasFillPost(order: Order): Promise<{
1125
- totalCostInSourceFeeToken: bigint;
1126
- relayerFeeInSourceFeeToken: bigint;
1127
- dispatchFee: bigint;
1128
- callGasLimit: bigint;
1129
- }>;
1191
+ executeOrder(order: Order, bids?: BidPlacer): Promise<FillResult>;
1130
1192
  /**
1131
- * The call gas a bid needs: the shared estimate plus room for the funding calls
1132
- * this bid prepends and for settling its limit order's budget.
1133
- *
1134
- * Funding calls are not simulated (the V4 PositionManager's flash accounting does
1135
- * not resolve in the bundler's estimation context), so each one adds a fixed
1136
- * allowance for its modifyLiquidities + take. They differ between the bids on one
1137
- * order, since each draws on its own limit order and so on its own funding, which
1138
- * is why this is worked out per bid rather than baked into the cached estimate.
1139
- * The estimate is of a lone fillOrder, so the budget call is not in it either.
1193
+ * Optional hook for strategies to provide a USD value for the full input basket.
1194
+ * Returns null when the strategy cannot or does not want to price the order.
1140
1195
  */
1141
- private callGasLimitFor;
1196
+ getOrderUsdValue?(order: Order): Promise<{
1197
+ inputUsd: Decimal;
1198
+ } | null>;
1142
1199
  /**
1143
- * Gets the fee token address and decimals for a given chain.
1144
- *
1145
- * @param chain - The chain identifier to get fee token info for
1146
- * @returns An object containing the fee token address and its decimal places
1200
+ * Optional confirmation policy for this strategy.
1201
+ * If absent, no confirmation waiting is required (e.g. same-chain strategies).
1147
1202
  */
1148
- getFeeTokenWithDecimals(chain: string): Promise<{
1149
- address: HexString;
1150
- decimals: number;
1151
- }>;
1203
+ confirmationPolicy?: {
1204
+ getConfirmationBlocks: (chainId: number, amountUsd: number) => number;
1205
+ };
1206
+ }
1207
+
1208
+ /** Symbol and decimals for a token on a chain, as far as the filler knows them. */
1209
+ interface TokenDescription {
1210
+ symbol: string | null;
1211
+ decimals: number | null;
1212
+ }
1213
+ /** Resolves display metadata for `token` (20-byte address) on `chain` (state machine id). */
1214
+ type TokenDescriber = (chain: string, token: string) => Promise<TokenDescription>;
1215
+ interface ActivityRecorderOptions {
1152
1216
  /**
1153
- * Tops up the solver's EntryPoint deposit so it covers at least
1154
- * `targetGasUnits` at the current gas price. Skips if the wallet
1155
- * balance cannot afford at least 1M gas units (not enough to send txs).
1156
- *
1157
- * @param chain - The chain identifier
1158
- * @param targetGasUnits - Gas units the deposit should cover (default 3M)
1159
- * @param thresholdGasUnits - Only top up if deposit is below this many gas units (defaults to targetGasUnits)
1217
+ * Enriches order legs with symbol/decimals so the feed can show "19,585 USDC"
1218
+ * rather than a raw integer. Omit to record addresses and raw amounts only.
1160
1219
  */
1161
- topUpEntryPointDeposit(chain: string, targetGasUnits?: bigint, thresholdGasUnits?: bigint): Promise<void>;
1220
+ describeToken?: TokenDescriber;
1221
+ }
1222
+ /**
1223
+ * Bridges the filler's order lifecycle onto an {@link ActivityStore}.
1224
+ *
1225
+ * The store used to subscribe to the `EventMonitor` itself, which meant a
1226
+ * consumer implementing the data interface also had to know about the filler's
1227
+ * internal event names. Ownership is inverted here: the recorder listens, the
1228
+ * store just receives rows.
1229
+ *
1230
+ * Writes are fire-and-forget — the activity feed is observability, and a failed
1231
+ * insert must never propagate back into the fill path that emitted it. Each
1232
+ * stored row is re-emitted as `event` for live consumers (the UI's SSE stream,
1233
+ * and `Simplex`'s `activity` event).
1234
+ */
1235
+ declare class ActivityRecorder extends EventEmitter {
1236
+ private store;
1237
+ private logger;
1238
+ private monitor?;
1239
+ private handlers;
1240
+ private describeToken?;
1241
+ /** Order summaries by order id, so filled/skipped rows carry the order too. Insertion-ordered for eviction. */
1242
+ private summaries;
1243
+ constructor(store: ActivityStore, loggers?: LoggerContext, options?: ActivityRecorderOptions);
1244
+ /** Subscribes to the filler's order lifecycle events. Idempotent per monitor. */
1245
+ attach(monitor: EventMonitor): void;
1246
+ private subscribe;
1247
+ /** Unsubscribes from the attached monitor. Safe to call when never attached. */
1248
+ detach(): void;
1249
+ private remember;
1250
+ private summaryFor;
1162
1251
  /**
1163
- * Calculates the total USD value of an order's inputs.
1164
- * Only stable (USDC/USDT) inputs contribute; non-stables contribute 0.
1165
- *
1166
- * @param order - The order to calculate input value for
1167
- * @returns The total USD value of inputs (sum of normalized stable amounts, or 0 if none)
1252
+ * Settles an order from its on-chain fill: `filled` when this filler won it,
1253
+ * `lost` (reason = the winner) when a rival did. A rival's fill is recorded
1254
+ * only for orders this filler has rows for — every fill on the chain passes
1255
+ * through here, and the feed is about this filler's orders.
1168
1256
  */
1169
- getInputUsdValue(order: Order): Promise<Decimal>;
1257
+ private settle;
1170
1258
  /**
1171
- * Checks if solver selection mode is active on the destination chain
1172
- * When active, fillers must submit bids to Hyperbridge instead of filling directly
1173
- *
1174
- * @param chain - The chain identifier to check
1175
- * @returns True if solver selection is active
1259
+ * Captures the order for the feed. Token metadata lookups may hit the chain;
1260
+ * a failure degrades that leg to address + raw amount rather than dropping the row.
1176
1261
  */
1177
- isSolverSelectionActive(chain: string): Promise<boolean>;
1262
+ private summarize;
1178
1263
  /**
1179
- * Reads the solver account's deposit balance on the ERC-4337 EntryPoint.
1264
+ * Writes an event and emits the stored row. Never rejects — a failed write is
1265
+ * logged and dropped, because the caller is usually a fill in progress.
1180
1266
  */
1181
- /** What `holder` holds of an ERC-20 on `chain`, in the token's own units. */
1182
- getTokenBalance(chain: string, token: HexString, holder: HexString): Promise<bigint>;
1267
+ record(event: ActivityInsert): void;
1268
+ recent(limit?: number, beforeId?: number): Promise<ActivityEvent[]>;
1269
+ fills(limit?: number): Promise<ActivityEvent[]>;
1270
+ orderHistory(page: number, pageSize: number): Promise<OrderHistoryPage>;
1271
+ recordWalletTx(tx: Omit<WalletTx, "id" | "ts">): Promise<void>;
1272
+ walletTxs(limit?: number): Promise<WalletTx[]>;
1273
+ }
1274
+
1275
+ /** What a limit order may pay out over its whole life, as the fill chain enforces it. */
1276
+ interface LimitOrderBudget {
1277
+ budgetId: HexString;
1278
+ /** The order's whole size in the output token's own units. */
1279
+ cap: bigint;
1280
+ /** The token the order pays on its fill chain. */
1281
+ token: HexString;
1282
+ }
1283
+
1284
+ interface SwapCall {
1285
+ to: string;
1286
+ data: string;
1287
+ value: string;
1288
+ }
1289
+ interface CachedPairClassification {
1290
+ inputIsStable: boolean;
1291
+ stableToken: string;
1292
+ exoticToken: string;
1293
+ }
1294
+ /**
1295
+ * One bid: what a single limit order pays for an incoming order, and what funds it.
1296
+ *
1297
+ * An order that several limit orders can serve produces several of these, one per
1298
+ * order, each priced against the whole input at its own rate. They are never
1299
+ * summed: each bid is its own fill, and the gateway clamps whichever one lands
1300
+ * against what is still outstanding.
1301
+ */
1302
+ interface BidPlan {
1303
+ limitOrderId: string;
1304
+ /** The order leg this bid fills; every other leg is quoted at zero. */
1305
+ leg: number;
1306
+ /** What this bid draws from that limit order, at 1e18. */
1307
+ payout: bigint;
1308
+ /** The outputs the bid signs, in the output token's own units. */
1309
+ fillerOutputs: TokenInfo[];
1310
+ /** The take signed beside each output: the escrow this bid's fill earns. */
1311
+ fillerInputs: TokenInfo[];
1312
+ /** The decimals `fillerInputs[leg]` is counted in: the input token's, on the order's source chain. */
1313
+ inputDecimals?: number;
1314
+ fundingCalls: ERC7821Call[];
1315
+ partialFill: boolean;
1316
+ profit: number;
1317
+ /** That limit order's budget, which the bid settles against on the fill chain. */
1318
+ budget?: LimitOrderBudget;
1319
+ }
1320
+ declare class CacheService {
1321
+ private cacheData;
1322
+ private readonly CACHE_EXPIRY_MS;
1323
+ /** How long each order's source confirmations take, for the orders still waiting for them. */
1324
+ private readonly confirmationWaits;
1325
+ /** When each order's bid loop began, for the orders whose loop is still running. */
1326
+ private readonly pins;
1327
+ private logger;
1328
+ constructor(loggers?: LoggerContext);
1183
1329
  /**
1184
- * What the solver's account has tallied against a limit order's budget on `chain`, in
1185
- * the paid token's own units, or null when it cannot be read.
1186
- *
1187
- * Null rather than a throw: an account whose implementation keeps no tally answers
1188
- * with a revert or with no data, and the caller carries on without the figure either
1189
- * way. Not retried, since that answer does not change on a second ask.
1330
+ * Whether an entry is still live. A pinned order's entries are judged as of the pin,
1331
+ * so what was live when its bid loop began stays live until the loop ends, and
1332
+ * what had already expired stays expired.
1190
1333
  */
1191
- limitOrderSpent(chain: string, budgetId: HexString): Promise<bigint | null>;
1192
- getSolverEntryPointBalance(chain: string): Promise<bigint>;
1334
+ private isCacheValid;
1193
1335
  /**
1194
- * Deposits native tokens to the ERC-4337 EntryPoint on behalf of the solver account.
1195
- * @returns The transaction hash of the deposit.
1336
+ * When an entry written now for the order expires: `CACHE_EXPIRY_MS` from now, plus the
1337
+ * time the order's source confirmations take while it waits for them.
1196
1338
  */
1197
- depositToEntryPoint(chain: string, amount: bigint): Promise<HexString>;
1339
+ private expiresAt;
1198
1340
  /**
1199
- * Withdraws the solver's full EntryPoint deposit back to the solver EOA on a single chain.
1200
- * @returns The transaction hash, or null if there was nothing to withdraw.
1341
+ * Lets what is cached for the order outlive `CACHE_EXPIRY_MS` by `waitMs`, the time its
1342
+ * source confirmations take, and returns the release.
1343
+ *
1344
+ * An order is evaluated while it waits for its confirmations, and its bids are signed after
1345
+ * them from what the evaluation cached, so that has to outlast the wait. Each entry takes
1346
+ * its expiry when it is written: the release does not shorten what is already cached, it
1347
+ * only stops later entries from getting the extra time.
1201
1348
  */
1202
- withdrawFromEntryPoint(chain: string): Promise<HexString | null>;
1349
+ extendForConfirmations(orderId: string, waitMs: number): () => void;
1203
1350
  /**
1204
- * Withdraws EntryPoint deposits on all configured chains that have a positive balance.
1351
+ * Keeps the order's evaluation alive while its bids go out, and returns the release.
1352
+ *
1353
+ * The bids on one order are sent in turn, each priced by the evaluation that planned
1354
+ * them, and every one reads the shared gas estimate back out of here. Submitting a
1355
+ * bid can take longer than `CACHE_EXPIRY_MS` on its own, and the order is not
1356
+ * evaluated again, so the estimate has to outlive the loop for the later bids to be
1357
+ * sent at all. Estimating afresh instead would sign them at a gas cost their payouts
1358
+ * were not priced against.
1205
1359
  */
1206
- withdrawAllEntryPointDeposits(): Promise<void>;
1360
+ pinOrder(orderId: string): () => void;
1361
+ private cleanupStaleData;
1362
+ getGasEstimate(orderId: string): {
1363
+ totalCostInSourceFeeToken: bigint;
1364
+ relayerFeeInSourceFeeToken: bigint;
1365
+ dispatchFee: bigint;
1366
+ callGasLimit: bigint;
1367
+ verificationGasLimit: bigint;
1368
+ preVerificationGas: bigint;
1369
+ maxFeePerGas: bigint;
1370
+ maxPriorityFeePerGas: bigint;
1371
+ totalGasCostWei: bigint;
1372
+ } | null;
1373
+ setGasEstimate(orderId: string, totalCostInSourceFeeToken: bigint, relayerFeeInSourceFeeToken: bigint, dispatchFee: bigint, callGasLimit: bigint, verificationGasLimit: bigint, preVerificationGas: bigint, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint, totalGasCostWei: bigint): void;
1374
+ getSwapOperations(orderId: string): {
1375
+ calls: SwapCall[];
1376
+ totalGasEstimate: bigint;
1377
+ } | null;
1378
+ setSwapOperations(orderId: string, calls: SwapCall[], totalGasEstimate: bigint): void;
1379
+ getFillerOutputs(orderId: string): {
1380
+ token: HexString;
1381
+ amount: bigint;
1382
+ }[] | null;
1383
+ /** The takes cached with the order's filler outputs, or null when absent or stale. */
1384
+ getFillerInputs(orderId: string): {
1385
+ token: HexString;
1386
+ amount: bigint;
1387
+ }[] | null;
1207
1388
  /**
1208
- * Output already delivered against this order by any solver, per leg (the gateway keys fill
1209
- * progress by output index, so legs repeating a token are reported separately; a gateway not yet
1210
- * upgraded to per-leg escrow is read by token).
1211
- *
1212
- * A partially filled order has had its escrow drawn down, so the pro-rata
1213
- * release a later filler receives is computed against the *residual*, not
1214
- * against `order.inputs[i].amount`. A strategy that prices off the original
1215
- * inputs would overstate its take, which is why the partial-fill path refuses
1216
- * any order this reports as already touched.
1389
+ * The budget of the limit order that priced the outputs cached for this order, or
1390
+ * null when no limit order did.
1217
1391
  *
1218
- * Same-chain concept only — the cross-chain path has no partial-fill state.
1392
+ * Kept on the same entry as the outputs, so the two are replaced and expire
1393
+ * together: a bid can never sign one limit order's payout beside another's budget.
1219
1394
  */
1220
- partialFillsFor(order: Order, chain: string): Promise<bigint[]>;
1395
+ getBidBudget(orderId: string): LimitOrderBudget | null;
1396
+ setFillerOutputs(orderId: string, outputs: {
1397
+ token: HexString;
1398
+ amount: bigint;
1399
+ }[], inputs?: {
1400
+ token: HexString;
1401
+ amount: bigint;
1402
+ }[], budget?: LimitOrderBudget): void;
1221
1403
  /**
1222
- * Prepares a signed PackedUserOperation for bid submission to Hyperbridge
1404
+ * The limit order this order was priced against, and the payout it allowed.
1223
1405
  *
1224
- * Uses cached gas estimates from prior profitability check (estimateGasFillPost)
1225
- * to avoid redundant RPC calls.
1406
+ * Carried from `calculateProfitability` to the bid so the reservation lands on
1407
+ * the same limit order the price came from. A fill later finds it through the
1408
+ * bid row, not through here.
1409
+ */
1410
+ getMatchedLimitOrder(orderId: string): {
1411
+ limitOrderId: string;
1412
+ payout: bigint;
1413
+ }[];
1414
+ getBidPlans(orderId: string): BidPlan[];
1415
+ setBidPlans(orderId: string, plans: BidPlan[]): void;
1416
+ clearBidPlans(orderId: string): void;
1417
+ setMatchedLimitOrder(orderId: string, holds: {
1418
+ limitOrderId: string;
1419
+ payout: bigint;
1420
+ }[]): void;
1421
+ getPairClassifications(orderId: string): CachedPairClassification[] | null;
1422
+ setPairClassifications(orderId: string, pairs: CachedPairClassification[]): void;
1423
+ getFundingPrepends(orderId: string): {
1424
+ calls: ERC7821Call[];
1425
+ } | null;
1426
+ setFundingPrepends(orderId: string, calls: ERC7821Call[]): void;
1427
+ clearFundingPrepends(orderId: string): void;
1428
+ /**
1429
+ * Whether the strategy's completed evaluation of this order chose a partial fill.
1430
+ *
1431
+ * Only ever written by an evaluation that went on to return a fillable answer,
1432
+ * and cleared at the start of every evaluation — so a refusal can never leave a
1433
+ * `true` behind for the caller to act on.
1434
+ */
1435
+ isPartialFill(orderId: string): boolean;
1436
+ setPartialFill(orderId: string, partial: boolean): void;
1437
+ clearPartialFill(orderId: string): void;
1438
+ /**
1439
+ * Whether the strategy's completed evaluation let a bid through without holding
1440
+ * `order.fees` to the execution cost, because the order is at or above the
1441
+ * operator's minimum size.
1442
+ *
1443
+ * Written and cleared the way {@link isPartialFill} is, for the same reason: the
1444
+ * caller exempts such an order from its profit floor.
1445
+ */
1446
+ isFeeCheckWaived(orderId: string): boolean;
1447
+ setFeeCheckWaived(orderId: string, waived: boolean): void;
1448
+ clearFeeCheckWaived(orderId: string): void;
1449
+ getFeeTokenWithDecimals(chain: string): {
1450
+ address: HexString;
1451
+ decimals: number;
1452
+ } | null;
1453
+ setFeeTokenWithDecimals(chain: string, address: HexString, decimals: number): void;
1454
+ getTokenDecimals(chain: string, tokenAddress: HexString): number | null;
1455
+ setTokenDecimals(chain: string, tokenAddress: HexString, decimals: number): void;
1456
+ getSolverSelection(chain: string): boolean | null;
1457
+ setSolverSelection(chain: string, active: boolean): void;
1458
+ }
1459
+
1460
+ /** What a bid's batch needs to settle its limit order's budget. */
1461
+ interface BudgetSettlement {
1462
+ budget: LimitOrderBudget;
1463
+ /** The solver's account, which is the only caller `debitOrder` accepts. */
1464
+ solverAccount: HexString;
1465
+ }
1466
+ declare class ContractInteractionService {
1467
+ private clientManager;
1468
+ private configService;
1469
+ cacheService: CacheService;
1470
+ private logger;
1471
+ private sdkHelperCache;
1472
+ private blockTimes;
1473
+ private solverAccountAddress;
1474
+ private signer;
1475
+ constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer, sharedCacheService?: CacheService);
1476
+ /**
1477
+ * The endpoint SDK helpers should use for `chain`: the first configured RPC,
1478
+ * matching the one viem's `fallback` transport tries first.
1479
+ */
1480
+ private primaryRpcUrl;
1481
+ /**
1482
+ * Gets the SDK helper for a given source and destination chain.
1483
+ * Instances are cached and reused to avoid redundant RPC calls.
1484
+ */
1485
+ getIntentGateway(source: string, destination: string): Promise<IntentGateway>;
1486
+ /**
1487
+ * Best-effort cache warm-up. Deliberately never rejects: this runs unawaited from the
1488
+ * constructor, so a rejection would surface as an unhandled rejection and take the
1489
+ * process down. A failed prewarm only means the value is fetched on first use, where
1490
+ * `getTokenDecimals` is strict — which is the right place for strictness, because
1491
+ * there it can skip a single order instead of killing the filler at boot.
1492
+ */
1493
+ initCache(): Promise<void>;
1494
+ getCache(): CacheService;
1495
+ /**
1496
+ * Gets the decimals for a token
1497
+ */
1498
+ getTokenDecimals(tokenAddress: string, chain: string): Promise<number>;
1499
+ /**
1500
+ * Estimates gas for filling an order and caches the full estimate for bid preparation
1501
+ */
1502
+ estimateGasFillPost(order: Order): Promise<{
1503
+ totalCostInSourceFeeToken: bigint;
1504
+ relayerFeeInSourceFeeToken: bigint;
1505
+ dispatchFee: bigint;
1506
+ callGasLimit: bigint;
1507
+ }>;
1508
+ /**
1509
+ * Logs a bundler gas estimate for `order` that failed transiently and is sent again
1510
+ * (the SDK retries a network failure, a JSON-RPC internal error, or an HTTP 408, 429 or 5xx).
1511
+ */
1512
+ private logBundlerRetry;
1513
+ /**
1514
+ * The call gas a bid needs: the shared estimate plus room for the funding calls
1515
+ * this bid prepends and for settling its limit order's budget.
1516
+ *
1517
+ * Funding calls are not simulated (the V4 PositionManager's flash accounting does
1518
+ * not resolve in the bundler's estimation context), so each one adds a fixed
1519
+ * allowance for its modifyLiquidities + take. They differ between the bids on one
1520
+ * order, since each draws on its own limit order and so on its own funding, which
1521
+ * is why this is worked out per bid rather than baked into the cached estimate.
1522
+ * The estimate is of a lone fillOrder, so the budget call is not in it either, nor are
1523
+ * the approvals: each token the batch approves adds `APPROVAL_GAS_PER_TOKEN`.
1524
+ */
1525
+ private callGasLimitFor;
1526
+ /**
1527
+ * What the batch approves to the gateway for `order`, per token, and the fee token's key
1528
+ * when the fill dispatches. The fee token is read from the destination host.
1529
+ */
1530
+ private approvalsFor;
1531
+ /**
1532
+ * Gets the fee token address and decimals for a given chain.
1533
+ *
1534
+ * @param chain - The chain identifier to get fee token info for
1535
+ * @returns An object containing the fee token address and its decimal places
1536
+ */
1537
+ getFeeTokenWithDecimals(chain: string): Promise<{
1538
+ address: HexString;
1539
+ decimals: number;
1540
+ }>;
1541
+ /**
1542
+ * Tops up the solver's EntryPoint deposit so it covers at least
1543
+ * `targetGasUnits` at the current gas price. Skips if the wallet
1544
+ * balance cannot afford at least 1M gas units (not enough to send txs).
1545
+ *
1546
+ * @param chain - The chain identifier
1547
+ * @param targetGasUnits - Gas units the deposit should cover (default 3M)
1548
+ * @param thresholdGasUnits - Only top up if deposit is below this many gas units (defaults to targetGasUnits)
1549
+ */
1550
+ topUpEntryPointDeposit(chain: string, targetGasUnits?: bigint, thresholdGasUnits?: bigint): Promise<void>;
1551
+ /**
1552
+ * Calculates the total USD value of an order's inputs.
1553
+ * Only stable (USDC/USDT) inputs contribute; non-stables contribute 0.
1554
+ *
1555
+ * @param order - The order to calculate input value for
1556
+ * @returns The total USD value of inputs (sum of normalized stable amounts, or 0 if none)
1557
+ */
1558
+ getInputUsdValue(order: Order): Promise<Decimal>;
1559
+ /**
1560
+ * Checks if solver selection mode is active on the destination chain
1561
+ * When active, fillers must submit bids to Hyperbridge instead of filling directly
1562
+ *
1563
+ * @param chain - The chain identifier to check
1564
+ * @returns True if solver selection is active
1565
+ */
1566
+ isSolverSelectionActive(chain: string): Promise<boolean>;
1567
+ /**
1568
+ * Reads the solver account's deposit balance on the ERC-4337 EntryPoint.
1569
+ */
1570
+ /** What `holder` holds of an ERC-20 on `chain`, in the token's own units. */
1571
+ getTokenBalance(chain: string, token: HexString, holder: HexString): Promise<bigint>;
1572
+ /**
1573
+ * What the solver's account has tallied against a limit order's budget on `chain`, in
1574
+ * the paid token's own units, or null when it cannot be read.
1575
+ *
1576
+ * Null rather than a throw: an account whose implementation keeps no tally answers
1577
+ * with a revert or with no data, and the caller carries on without the figure either
1578
+ * way. Not retried, since that answer does not change on a second ask.
1579
+ *
1580
+ * Read at `blockNumber` when given, otherwise at the latest block. A load-balanced
1581
+ * endpoint can lag the one that saw a fill, so a read at `latest` right after the
1582
+ * fill can return the tally from before it. The tally only grows, so a read at the
1583
+ * fill's own block already includes that fill. An endpoint that has not reached the
1584
+ * block answers with an error, which is null here: no figure, rather than a stale one.
1585
+ */
1586
+ limitOrderSpent(chain: string, budgetId: HexString, blockNumber?: bigint): Promise<bigint | null>;
1587
+ getSolverEntryPointBalance(chain: string): Promise<bigint>;
1588
+ /**
1589
+ * Deposits native tokens to the ERC-4337 EntryPoint on behalf of the solver account.
1590
+ * @returns The transaction hash of the deposit.
1591
+ */
1592
+ depositToEntryPoint(chain: string, amount: bigint): Promise<HexString>;
1593
+ /**
1594
+ * Withdraws the solver's full EntryPoint deposit back to the solver EOA on a single chain.
1595
+ * @returns The transaction hash, or null if there was nothing to withdraw.
1596
+ */
1597
+ withdrawFromEntryPoint(chain: string): Promise<HexString | null>;
1598
+ /**
1599
+ * Withdraws EntryPoint deposits on all configured chains that have a positive balance.
1600
+ */
1601
+ withdrawAllEntryPointDeposits(): Promise<void>;
1602
+ /**
1603
+ * Output already delivered against this order by any solver, per leg (the gateway keys fill
1604
+ * progress by output index, so legs repeating a token are reported separately; a gateway not yet
1605
+ * upgraded to per-leg escrow is read by token).
1606
+ *
1607
+ * A partially filled order has had its escrow drawn down, so the pro-rata
1608
+ * release a later filler receives is computed against the *residual*, not
1609
+ * against `order.inputs[i].amount`. A strategy that prices off the original
1610
+ * inputs would overstate its take, which is why the partial-fill path refuses
1611
+ * any order this reports as already touched.
1612
+ *
1613
+ * Same-chain concept only — the cross-chain path has no partial-fill state.
1614
+ */
1615
+ partialFillsFor(order: Order, chain: string): Promise<bigint[]>;
1616
+ /**
1617
+ * Prepares a signed PackedUserOperation for bid submission to Hyperbridge
1618
+ *
1619
+ * Uses cached gas estimates from prior profitability check (estimateGasFillPost)
1620
+ * to avoid redundant RPC calls.
1226
1621
  *
1227
1622
  * @param order - The order to prepare a bid for
1228
1623
  * @param entryPointAddress - The ERC-4337 EntryPoint address on the destination chain
@@ -1239,6 +1634,16 @@ declare class ContractInteractionService {
1239
1634
  userOp: HexString;
1240
1635
  bid: HexString;
1241
1636
  }>;
1637
+ /**
1638
+ * Throws unless an op with no paymaster can pay its own prefund. The EntryPoint takes it from
1639
+ * the account's deposit and asks the account for the rest out of its native balance, so
1640
+ * between them they must cover `required`. Nothing funds a deposit on a chain with a
1641
+ * paymaster configured, so when selection comes back empty there a solver holding little
1642
+ * native would place a bid, pay Hyperbridge's bid fee for it, and fail validation (AA21)
1643
+ * when it is selected. Reads that fail let the bid through, as the paymaster deposit gate
1644
+ * does: a transient RPC error must not cost a bid, and the worst case is that failure.
1645
+ */
1646
+ private assertUnsponsoredBidFunded;
1242
1647
  /**
1243
1648
  * Last block on the destination chain at which a bid signed now may still execute.
1244
1649
  *
@@ -1264,12 +1669,16 @@ declare class ContractInteractionService {
1264
1669
  * speeds up (BSC makes a block every 0.45s against a declared 0.75s) and some chains
1265
1670
  * declare none. Converting at a slower block time than the chain's ends a bid early, so
1266
1671
  * the faster of the measured and the declared figure is used, and a stalled or idle
1267
- * stretch in the sample never shortens a bid.
1672
+ * stretch in the sample never shortens a bid. A measurement below half the declared figure is
1673
+ * discarded, which limits how far one RPC can stretch a bid to twice its window. A chain that
1674
+ * declares none has no reference to judge a sample against, so it only gets the flat floor
1675
+ * {@link MIN_PLAUSIBLE_BLOCK_TIME_SECONDS}; below that the measurement is discarded for
1676
+ * {@link FALLBACK_BLOCK_TIME_SECONDS}.
1268
1677
  *
1269
1678
  * Blocks that cannot be read fall back to the declared figure, then to
1270
1679
  * {@link FALLBACK_BLOCK_TIME_SECONDS}. That answer is not kept.
1271
1680
  */
1272
- private blockTimeSeconds;
1681
+ blockTimeSeconds(chain: string): Promise<number>;
1273
1682
  /** The average block time over the chain's last {@link BLOCK_TIME_SAMPLE_BLOCKS} blocks, in seconds. */
1274
1683
  private measureBlockTimeSeconds;
1275
1684
  /**
@@ -1307,6 +1716,13 @@ declare class ContractInteractionService {
1307
1716
  * Builds ERC-7821 batch calldata that approves exactly what this bid can pay
1308
1717
  * before the fillOrder call, all within a single UserOp payload.
1309
1718
  *
1719
+ * The gateway is approved for exactly what this fill pays: the outputs in each
1720
+ * ERC20, plus the dispatch fee (`dispatchFee`) in the fee token on a cross-chain
1721
+ * fill. Nothing else goes in an approval: the fill's gas is paid through the
1722
+ * paymaster's Permit2 pull. The batch ends by setting every allowance it set back
1723
+ * to zero, so none stands once it completes. The gateway is an upgradeable proxy
1724
+ * and a standing allowance is exposure to a future implementation.
1725
+ *
1310
1726
  * The approvals are unconditional. One solver can hold several bids on one
1311
1727
  * order, at different levels of its book, and they execute one after another:
1312
1728
  * an approval skipped because the allowance covered this bid when it was signed
@@ -1322,9 +1738,11 @@ declare class ContractInteractionService {
1322
1738
  * A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
1323
1739
  * as its `orderId`, works out what the fill paid from what is left of the allowance
1324
1740
  * and reverts the whole batch if the order's payouts would pass its size. It has to
1325
- * come last, after the gateway has drawn on the allowance.
1741
+ * come after the gateway has drawn on the allowance, and the zero-resets after it,
1742
+ * since it reads the allowance that is left. It clears the budget token's allowance
1743
+ * itself, so the resets skip that token.
1326
1744
  */
1327
- buildApprovalAndFillCalldata(order: Order, fillerOutputs: TokenInfo[], fillOptions: FillOptions, requiredFeeTokenAmount: bigint, settlement?: BudgetSettlement): Promise<HexString>;
1745
+ buildApprovalAndFillCalldata(order: Order, fillerOutputs: TokenInfo[], fillOptions: FillOptions, settlement?: BudgetSettlement): Promise<HexString>;
1328
1746
  }
1329
1747
 
1330
1748
  interface BinanceCexConfig {
@@ -1392,384 +1810,145 @@ declare class CctpRebalancer {
1392
1810
  /**
1393
1811
  * Estimates the cost of a CCTP transfer
1394
1812
  */
1395
- estimateCctp(options: RebalanceOptions): Promise<EstimateResult>;
1396
- /**
1397
- * Retries a failed CCTP transfer
1398
- */
1399
- retrySendCctp(failedResult: BridgeResult): Promise<BridgeResult>;
1400
- }
1401
-
1402
- interface Usdt0TransferResult {
1403
- success: boolean;
1404
- txHash: HexString;
1405
- amountSent: bigint;
1406
- amountReceived: bigint;
1407
- nativeFee: bigint;
1408
- }
1409
- interface Usdt0EstimateResult {
1410
- amountSent: bigint;
1411
- amountReceived: bigint;
1412
- nativeFee: bigint;
1413
- minAmount: bigint;
1414
- maxAmount: bigint;
1415
- }
1416
-
1417
- /**
1418
- * Options for rebalancing transfers (CCTP, USDT0, or CEX).
1419
- * - For CCTP/USDT0: coin is implicit (CCTP=USDC, USDT0=USDT), recipientAddress is optional
1420
- * - For unified router/CEX: coin is required for routing, recipientAddress is ignored
1421
- */
1422
- interface RebalanceOptions {
1423
- /** Amount in human-readable format (e.g., "100.00") */
1424
- amount: string;
1425
- /** Source chain in state machine format (e.g., "EVM-137") */
1426
- source: string;
1427
- /** Destination chain in state machine format (e.g., "EVM-42161") */
1428
- destination: string;
1429
- /** Optional recipient address (defaults to sender's address) - only used for CCTP/USDT0, ignored for CEX */
1430
- recipientAddress?: HexString;
1431
- }
1432
- /**
1433
- * UnifiedRebalanceOptions is kept for broader compatibility.
1434
- */
1435
- type UnifiedRebalanceOptions = RebalanceOptions & {
1436
- coin: "USDC" | "USDT";
1437
- };
1438
-
1439
- /**
1440
- * Represents a planned transfer from one chain to another for portfolio-level rebalancing.
1441
- */
1442
- interface PlannedTransfer {
1443
- sourceChain: string;
1444
- destChain: string;
1445
- coin: "USDC" | "USDT";
1446
- amount: string;
1447
- }
1448
- /**
1449
- * Result of checking portfolio rebalancing triggers.
1450
- */
1451
- interface TriggerCheckResult {
1452
- triggered: boolean;
1453
- triggeredChains: Array<{
1454
- chain: string;
1455
- asset: "USDC" | "USDT";
1456
- deficit: number;
1457
- }>;
1458
- }
1459
- /**
1460
- * Result of a portfolio rebalancing operation.
1461
- */
1462
- interface RebalancingResult {
1463
- success: boolean;
1464
- transfers: PlannedTransfer[];
1465
- executedTransfers: Array<{
1466
- transfer: PlannedTransfer;
1467
- result: unknown;
1468
- }>;
1469
- errors?: string[];
1470
- }
1471
- /**
1472
- * RebalancingService - Facade over CCTP, USDT0, and Binance CEX rebalancers.
1473
- * Provides both low-level methods (sendCctp, sendUsdt0, sendViaCex) and a high-level router (rebalance).
1474
- */
1475
- declare class RebalancingService {
1476
- private readonly chainClientManager;
1477
- private readonly configService;
1478
- private readonly walletAddress;
1479
- private readonly cctpRebalancer;
1480
- private readonly usdt0Rebalancer;
1481
- private readonly binanceRebalancer?;
1482
- private readonly logger;
1483
- constructor(chainClientManager: ChainClientManager, configService: FillerConfigService, binanceConfig?: BinanceCexConfig);
1484
- sendCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.BridgeResult>;
1485
- estimateCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.EstimateResult>;
1486
- retrySendCctp(failedResult: Awaited<ReturnType<CctpRebalancer["sendCctp"]>>): Promise<_circle_fin_bridge_kit.BridgeResult>;
1487
- sendUsdt0(options: RebalanceOptions): Promise<Usdt0TransferResult>;
1488
- estimateUsdt0(options: RebalanceOptions): Promise<Usdt0EstimateResult>;
1489
- sendViaCex(options: UnifiedRebalanceOptions): Promise<CexRebalanceResult>;
1490
- estimateCexRebalance(options: UnifiedRebalanceOptions): Promise<CexRebalanceEstimate>;
1491
- /**
1492
- * Execute a single cross-chain transfer, selecting the best route (CCTP / USDT0 / CEX).
1493
- * Requires coin to be specified for routing decisions.
1494
- */
1495
- rebalance(options: UnifiedRebalanceOptions): Promise<Awaited<ReturnType<CctpRebalancer["sendCctp"]>> | Usdt0TransferResult | CexRebalanceResult>;
1496
- /**
1497
- * Check if any chains/assets have triggered portfolio rebalancing thresholds.
1498
- */
1499
- checkRebalanceTriggers(): Promise<TriggerCheckResult>;
1500
- /**
1501
- * Plan portfolio-level rebalancing transfers for triggered chains using greedy matching.
1502
- */
1503
- planPortfolioRebalancing(triggeredChains: TriggerCheckResult["triggeredChains"]): Promise<PlannedTransfer[]>;
1504
- /**
1505
- * Plan transfers for a specific asset using greedy surplus–deficit matching.
1506
- */
1507
- private planTransfersForAsset;
1508
- /**
1509
- * Execute planned portfolio-level transfers via this service.
1510
- */
1511
- executePortfolioRebalancing(transfers: PlannedTransfer[]): Promise<RebalancingResult>;
1512
- /**
1513
- * High-level portfolio rebalance:
1514
- * 1) Check triggers
1515
- * 2) Plan transfers
1516
- * 3) Execute transfers
1517
- */
1518
- rebalancePortfolio(): Promise<RebalancingResult>;
1519
- /**
1520
- * Get current balance for a token on a chain.
1521
- * Returns balance in normalized USD value (treating token as $1).
1522
- */
1523
- private getCurrentBalance;
1524
- estimate(options: UnifiedRebalanceOptions): Promise<{
1525
- method: "cctp";
1526
- estimate: Awaited<ReturnType<CctpRebalancer["estimateCctp"]>>;
1527
- } | {
1528
- method: "usdt0";
1529
- estimate: Usdt0EstimateResult;
1530
- } | {
1531
- method: "cex";
1532
- estimate: CexRebalanceEstimate;
1533
- }>;
1534
- }
1535
-
1536
- interface Book {
1537
- id: string;
1538
- base: string;
1539
- quote: string;
1540
- }
1541
- /** Live price levels. All prices and sizes are decimal integer strings at 1e18. */
1542
- interface OrderbookLevel {
1543
- fillChain: string;
1544
- priceBucket: string;
1545
- price: string;
1546
- worstPrice: string;
1547
- baseSize: string;
1548
- quoteSize: string;
1549
- orderCount: number;
1550
- solverCount: number;
1551
- }
1552
- interface OrderbookSnapshot extends Book {
1553
- /** Best first: bids descending, asks ascending. */
1554
- bids: OrderbookLevel[];
1555
- asks: OrderbookLevel[];
1556
- /** True backing liquidity, in quote for bids and base for asks. */
1557
- bidLiquidity: string;
1558
- askLiquidity: string;
1559
- /** Price bucket width, quote per 1 base at 1e18; null when the orderbook does not report one. */
1560
- granularity: string | null;
1561
- }
1562
- /** One solver's order inside a price level. Prices and sizes are decimal integer strings at 1e18. */
1563
- interface OrderbookLevelOrder {
1564
- solver: string;
1565
- commitment: string;
1566
- fillChain: string;
1567
- price: string;
1568
- /** What the order publishes as depth, in the token its side pays out: quote for bids, base for asks. */
1569
- advertisedSize: string;
1570
- /** The signed output before any balance cap, in the same token as `advertisedSize`. */
1571
- quotedAmount: string;
1572
- /** True when the solver's balance, not the quote, binds `advertisedSize`. */
1573
- resized: boolean;
1574
- expiresAt: string;
1575
- acceptedSources: string[];
1576
- }
1577
- interface OrderbookLevelQuery {
1578
- side: "BID" | "ASK";
1579
- fillChain: string;
1580
- priceBucket: string;
1581
- sourceChain?: string;
1582
- }
1583
- interface OrderbookFilters {
1584
- sourceChain?: string;
1585
- fillChain?: string;
1586
- }
1587
- interface TokenMinSize {
1588
- symbol: string;
1589
- size: string;
1590
- }
1591
- interface ServerInfo {
1592
- minOrderTtlSecs: number;
1593
- heartbeatIntervalSecs: number;
1594
- signatureSkewSecs: number;
1595
- maxBatchSize: number;
1596
- minOrderSizes: TokenMinSize[];
1597
- /**
1598
- * Every chain the orderbook serves, by state machine id: the chains orders
1599
- * fill on, and the source chains they may declare.
1600
- */
1601
- chains: string[];
1602
- eip712DomainName: string;
1603
- eip712DomainVersion: string;
1604
- }
1605
- /** One token on one chain, as the orderbook's own registry has it. */
1606
- interface ChainToken {
1607
- symbol: string;
1608
- /** Its decimals there: USDC and USDT carry 18 on BNB Chain and 6 on the rest. */
1609
- decimals: number;
1610
- }
1611
- /** One chain the orderbook serves, with the tokens it registers on it. */
1612
- interface ChainInfo {
1613
- /** State machine id, such as `EVM-8453`. */
1614
- id: string;
1615
- name: string;
1616
- tokens: ChainToken[];
1617
- }
1618
- /** `serverInfo`, `books` and `chains` together, which is how simplex reads them. */
1619
- interface OrderbookLimits {
1620
- serverInfo: ServerInfo;
1621
- books: Book[];
1622
- chains: ChainInfo[];
1623
- }
1624
- interface PostedOrder {
1625
- commitment: HexString;
1626
- side: "BID" | "ASK";
1627
- status: "ACTIVE" | "SUSPENDED";
1628
- price: string;
1629
- quotedAmount: string;
1630
- advertisedSize: string;
1631
- expiresAt: string;
1632
- acceptedSources: string[];
1633
- /** The balance, not the quote, is what binds `advertisedSize`. */
1634
- resized?: boolean;
1635
- /**
1636
- * Whether the solver's balance covers the whole quote. Also false before any
1637
- * balance has been read, which is why {@link PostedOrder.validatedAt} decides
1638
- * whether a false here means anything.
1639
- */
1640
- backed?: boolean;
1641
- /**
1642
- * The last cycle that read this order's balance, absent until one has. An
1643
- * order surfaces at its quoted size meanwhile.
1644
- */
1645
- validatedAt?: string | null;
1646
- }
1647
- /**
1648
- * Every code the orderbook's `RejectionCode` enum can send, listed rather than
1649
- * written as a union so a test can hold it against the published schema.
1650
- *
1651
- * Why the orderbook refuses an order. `MIN_ORDER_SIZE`, `TTL_TOO_SHORT` and
1652
- * `UNSUPPORTED_SOURCE_CHAIN` are prevented by validation before posting, the last
1653
- * against the chains the server serves and the tokens its registry lists on each;
1654
- * `REPLAYED` and `ORDER_EXISTS` are answered by bumping the nonce; the rest are
1655
- * encoding bugs on our side.
1656
- */
1657
- declare const REJECTION_CODES: readonly ["MALFORMED_USER_OP", "NO_FILL_ORDER", "UNSUPPORTED_CHAIN", "UNSUPPORTED_SHAPE", "NOT_PHANTOM", "TTL_TOO_SHORT", "UNSUPPORTED_PAIR", "BAD_SIGNATURE", "BAD_NONCE_BINDING", "REPLAYED", "ORDER_EXISTS", "TOO_MANY_ORDERS", "MIN_ORDER_SIZE", "MISSING_DECLARATION", "EMPTY_DECLARATION", "UNSUPPORTED_SOURCE_CHAIN"];
1658
- type RejectionCode = (typeof REJECTION_CODES)[number];
1659
- /**
1660
- * Why the orderbook could not decide an op at all. Ingest reads no chain, so it
1661
- * is the database or the submission running out of time, and both are worth
1662
- * another attempt.
1663
- */
1664
- declare const FAILURE_CODES: readonly ["DATABASE_UNAVAILABLE", "TIMEOUT"];
1665
- type FailureCode = (typeof FAILURE_CODES)[number];
1666
- type SubmitOrderResult = {
1667
- kind: "accepted";
1668
- order: PostedOrder;
1669
- surfaced: boolean;
1670
- } | {
1671
- kind: "unchanged";
1672
- order: PostedOrder;
1673
- } | {
1674
- kind: "rejected";
1675
- code: RejectionCode;
1676
- message: string;
1677
- } | {
1678
- kind: "failed";
1679
- code: FailureCode | "REQUEST_FAILED";
1680
- message: string;
1681
- retryable: boolean;
1682
- };
1683
- /** Why a signed message was refused, shared by `cancelOrder` and `heartbeat`. */
1684
- declare const MESSAGE_REJECTION_CODES: readonly ["BAD_SIGNATURE", "SOLVER_MISMATCH", "SIGNATURE_EXPIRED", "SIGNATURE_REUSED", "UNKNOWN_ORDER", "UNKNOWN_SOLVER"];
1685
- type MessageRejectionCode = (typeof MESSAGE_REJECTION_CODES)[number];
1686
- type SolverStatus = "ACTIVE" | "SUSPENDED";
1687
- type HeartbeatResult = {
1688
- kind: "accepted";
1689
- status: SolverStatus;
1690
- reactivatedOrders: number;
1691
- heartbeatDueBy: string;
1692
- } | {
1693
- kind: "rejected";
1694
- code: MessageRejectionCode;
1695
- message: string;
1696
- };
1697
- /** One page of the solver's own orders, as reconciliation walks them. */
1698
- interface PostedOrderPage {
1699
- orders: PostedOrder[];
1700
- /** Cursor for the next page, absent on the last one. */
1701
- cursor?: string;
1702
- status: SolverStatus;
1813
+ estimateCctp(options: RebalanceOptions): Promise<EstimateResult>;
1814
+ /**
1815
+ * Retries a failed CCTP transfer
1816
+ */
1817
+ retrySendCctp(failedResult: BridgeResult): Promise<BridgeResult>;
1703
1818
  }
1704
- type CancelOrderResult = {
1705
- kind: "cancelled";
1706
- commitment: HexString;
1707
- } | {
1708
- kind: "rejected";
1709
- code: MessageRejectionCode;
1710
- message: string;
1819
+
1820
+ interface Usdt0TransferResult {
1821
+ success: boolean;
1822
+ txHash: HexString;
1823
+ amountSent: bigint;
1824
+ amountReceived: bigint;
1825
+ nativeFee: bigint;
1826
+ }
1827
+ interface Usdt0EstimateResult {
1828
+ amountSent: bigint;
1829
+ amountReceived: bigint;
1830
+ nativeFee: bigint;
1831
+ minAmount: bigint;
1832
+ maxAmount: bigint;
1711
1833
  }
1834
+
1712
1835
  /**
1713
- * The request never got an answer. Distinct from `UNKNOWN_ORDER`, which is the
1714
- * orderbook saying the entry is already gone: here the entry may well still be
1715
- * live, and a caller that treats the two alike orphans it.
1836
+ * Options for rebalancing transfers (CCTP, USDT0, or CEX).
1837
+ * - For CCTP/USDT0: coin is implicit (CCTP=USDC, USDT0=USDT), recipientAddress is optional
1838
+ * - For unified router/CEX: coin is required for routing, recipientAddress is ignored
1716
1839
  */
1717
- | {
1718
- kind: "failed";
1719
- message: string;
1840
+ interface RebalanceOptions {
1841
+ /** Amount in human-readable format (e.g., "100.00") */
1842
+ amount: string;
1843
+ /** Source chain in state machine format (e.g., "EVM-137") */
1844
+ source: string;
1845
+ /** Destination chain in state machine format (e.g., "EVM-42161") */
1846
+ destination: string;
1847
+ /** Optional recipient address (defaults to sender's address) - only used for CCTP/USDT0, ignored for CEX */
1848
+ recipientAddress?: HexString;
1849
+ }
1850
+ /**
1851
+ * UnifiedRebalanceOptions is kept for broader compatibility.
1852
+ */
1853
+ type UnifiedRebalanceOptions = RebalanceOptions & {
1854
+ coin: "USDC" | "USDT";
1720
1855
  };
1721
1856
 
1722
1857
  /**
1723
- * Talks to one HyperFX orderbook over GraphQL.
1724
- *
1725
- * Stateless apart from the endpoint and timeout. Every method either returns the
1726
- * orderbook's own answer, including its refusals, or throws
1727
- * {@link OrderbookRequestError} because it could not get one.
1858
+ * Represents a planned transfer from one chain to another for portfolio-level rebalancing.
1728
1859
  */
1729
- declare class OrderbookClient {
1730
- private readonly requestTimeoutMs;
1731
- private logger;
1732
- /** The configured endpoint, with `/graphql` if it was left off. */
1733
- private readonly url;
1734
- constructor(url: string, requestTimeoutMs: number, loggers?: LoggerContext);
1735
- /** The limits to validate against before posting, plus the books on offer. */
1736
- limits(): Promise<OrderbookLimits>;
1737
- /** The public book across all solvers, restricted to the selected route. */
1738
- snapshot(id: string, filters?: OrderbookFilters): Promise<OrderbookSnapshot | null>;
1860
+ interface PlannedTransfer {
1861
+ sourceChain: string;
1862
+ destChain: string;
1863
+ coin: "USDC" | "USDT";
1864
+ amount: string;
1865
+ }
1866
+ /**
1867
+ * Result of checking portfolio rebalancing triggers.
1868
+ */
1869
+ interface TriggerCheckResult {
1870
+ triggered: boolean;
1871
+ triggeredChains: Array<{
1872
+ chain: string;
1873
+ asset: "USDC" | "USDT";
1874
+ deficit: number;
1875
+ }>;
1876
+ }
1877
+ /**
1878
+ * Result of a portfolio rebalancing operation.
1879
+ */
1880
+ interface RebalancingResult {
1881
+ success: boolean;
1882
+ transfers: PlannedTransfer[];
1883
+ executedTransfers: Array<{
1884
+ transfer: PlannedTransfer;
1885
+ result: unknown;
1886
+ }>;
1887
+ errors?: string[];
1888
+ }
1889
+ /**
1890
+ * RebalancingService - Facade over CCTP, USDT0, and Binance CEX rebalancers.
1891
+ * Provides both low-level methods (sendCctp, sendUsdt0, sendViaCex) and a high-level router (rebalance).
1892
+ */
1893
+ declare class RebalancingService {
1894
+ private readonly chainClientManager;
1895
+ private readonly configService;
1896
+ private readonly walletAddress;
1897
+ private readonly cctpRebalancer;
1898
+ private readonly usdt0Rebalancer;
1899
+ private readonly binanceRebalancer?;
1900
+ private readonly logger;
1901
+ constructor(chainClientManager: ChainClientManager, configService: FillerConfigService, binanceConfig?: BinanceCexConfig);
1902
+ sendCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.BridgeResult>;
1903
+ estimateCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.EstimateResult>;
1904
+ retrySendCctp(failedResult: Awaited<ReturnType<CctpRebalancer["sendCctp"]>>): Promise<_circle_fin_bridge_kit.BridgeResult>;
1905
+ sendUsdt0(options: RebalanceOptions): Promise<Usdt0TransferResult>;
1906
+ estimateUsdt0(options: RebalanceOptions): Promise<Usdt0EstimateResult>;
1907
+ sendViaCex(options: UnifiedRebalanceOptions): Promise<CexRebalanceResult>;
1908
+ estimateCexRebalance(options: UnifiedRebalanceOptions): Promise<CexRebalanceEstimate>;
1739
1909
  /**
1740
- * The orders behind one price level. The orderbook filters orders by side and route but not by
1741
- * price bucket, so this walks the side's orders on the level's fill chain and keeps the bucket.
1742
- * Null when the book does not exist.
1910
+ * Execute a single cross-chain transfer, selecting the best route (CCTP / USDT0 / CEX).
1911
+ * Requires coin to be specified for routing decisions.
1743
1912
  */
1744
- levelOrders(id: string, level: OrderbookLevelQuery): Promise<OrderbookLevelOrder[] | null>;
1745
- submitOrder(userOp: HexString): Promise<SubmitOrderResult>;
1913
+ rebalance(options: UnifiedRebalanceOptions): Promise<Awaited<ReturnType<CctpRebalancer["sendCctp"]>> | Usdt0TransferResult | CexRebalanceResult>;
1746
1914
  /**
1747
- * The entry this solver holds at `commitment`, or null when it holds none.
1748
- *
1749
- * What tells `ORDER_EXISTS` from `REPLAYED`: the first means a live entry is
1750
- * already sitting there, and posting again on a new nonce would put a second
1751
- * one behind the same liability.
1915
+ * Check if any chains/assets have triggered portfolio rebalancing thresholds.
1752
1916
  */
1753
- orderAt(solver: HexString, commitment: HexString): Promise<PostedOrder | null>;
1754
- cancelOrder(params: {
1755
- solver: HexString;
1756
- commitment: HexString;
1757
- timestamp: number;
1758
- signature: HexString;
1759
- }): Promise<CancelOrderResult>;
1760
- heartbeat(params: {
1761
- solver: HexString;
1762
- timestamp: number;
1763
- signature: HexString;
1764
- }): Promise<HeartbeatResult>;
1917
+ checkRebalanceTriggers(): Promise<TriggerCheckResult>;
1765
1918
  /**
1766
- * One page of the solver's own orders as the orderbook holds them.
1767
- *
1768
- * A solver the orderbook has never accepted an order from is not an error:
1769
- * it answers with no solver at all, which reads here as an empty page.
1919
+ * Plan portfolio-level rebalancing transfers for triggered chains using greedy matching.
1770
1920
  */
1771
- myOrders(solver: HexString, after?: string): Promise<PostedOrderPage>;
1772
- private request;
1921
+ planPortfolioRebalancing(triggeredChains: TriggerCheckResult["triggeredChains"]): Promise<PlannedTransfer[]>;
1922
+ /**
1923
+ * Plan transfers for a specific asset using greedy surplus–deficit matching.
1924
+ */
1925
+ private planTransfersForAsset;
1926
+ /**
1927
+ * Execute planned portfolio-level transfers via this service.
1928
+ */
1929
+ executePortfolioRebalancing(transfers: PlannedTransfer[]): Promise<RebalancingResult>;
1930
+ /**
1931
+ * High-level portfolio rebalance:
1932
+ * 1) Check triggers
1933
+ * 2) Plan transfers
1934
+ * 3) Execute transfers
1935
+ */
1936
+ rebalancePortfolio(): Promise<RebalancingResult>;
1937
+ /**
1938
+ * Get current balance for a token on a chain.
1939
+ * Returns balance in normalized USD value (treating token as $1).
1940
+ */
1941
+ private getCurrentBalance;
1942
+ estimate(options: UnifiedRebalanceOptions): Promise<{
1943
+ method: "cctp";
1944
+ estimate: Awaited<ReturnType<CctpRebalancer["estimateCctp"]>>;
1945
+ } | {
1946
+ method: "usdt0";
1947
+ estimate: Usdt0EstimateResult;
1948
+ } | {
1949
+ method: "cex";
1950
+ estimate: CexRebalanceEstimate;
1951
+ }>;
1773
1952
  }
1774
1953
 
1775
1954
  /**
@@ -1809,8 +1988,9 @@ interface CreateLimitOrderRequest {
1809
1988
  }
1810
1989
  /**
1811
1990
  * What the service reports about an order nobody asked it about: a fill worked it
1812
- * down, or took it under the dust floor. Operator-initiated changes are reported
1813
- * by the controller that took the request.
1991
+ * down, took it under the dust floor, it outlived its `expiresAt` and was
1992
+ * retired, or the orderbook refused its posting. Operator-initiated changes are
1993
+ * reported by the controller that took the request.
1814
1994
  */
1815
1995
  type LimitOrderEvent = {
1816
1996
  kind: "resized";
@@ -1819,6 +1999,12 @@ type LimitOrderEvent = {
1819
1999
  } | {
1820
2000
  kind: "filled";
1821
2001
  order: LimitOrder;
2002
+ } | {
2003
+ kind: "expired";
2004
+ order: LimitOrder;
2005
+ } | {
2006
+ kind: "rejected";
2007
+ order: LimitOrder;
1822
2008
  };
1823
2009
  /**
1824
2010
  * What came of an order being created: the orderbook's answer, or `unposted` for
@@ -1876,6 +2062,11 @@ declare class LimitOrderService {
1876
2062
  private readonly startingNonce;
1877
2063
  /** The configured ERC-4626 vaults, when the operator runs any. */
1878
2064
  private readonly vaultBalances?;
2065
+ /**
2066
+ * Whether the filler only watches a chain (`--watch-only`, or `watchOnly` in the config,
2067
+ * globally or for that chain). Live: the operator can flip it while running.
2068
+ */
2069
+ private readonly isWatchOnly?;
1879
2070
  private logger;
1880
2071
  private cachedLimits?;
1881
2072
  private onEvent?;
@@ -1885,7 +2076,12 @@ declare class LimitOrderService {
1885
2076
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
1886
2077
  startingNonce?: () => bigint,
1887
2078
  /** The configured ERC-4626 vaults, when the operator runs any. */
1888
- vaultBalances?: VaultHoldings | undefined);
2079
+ vaultBalances?: VaultHoldings | undefined,
2080
+ /**
2081
+ * Whether the filler only watches a chain (`--watch-only`, or `watchOnly` in the config,
2082
+ * globally or for that chain). Live: the operator can flip it while running.
2083
+ */
2084
+ isWatchOnly?: ((chain: string) => boolean) | undefined);
1889
2085
  /**
1890
2086
  * Attaches the listener fill-driven changes are reported to. `Simplex` does
1891
2087
  * this after boot, the same way the filler is handed the service itself.
@@ -1970,7 +2166,7 @@ declare class LimitOrderService {
1970
2166
  * balance between them. Whichever order fills first draws the inventory down,
1971
2167
  * and the rest are cut to what is left. Netting them here would refuse the
1972
2168
  * second side of every book. Vault positions are counted the same way: the
1973
- * whole position, not what is left after reservations for fills in flight.
2169
+ * whole position.
1974
2170
  */
1975
2171
  private assertWalletCanPay;
1976
2172
  /**
@@ -1981,28 +2177,19 @@ declare class LimitOrderService {
1981
2177
  * filler might have managed rather than accepting one it could not.
1982
2178
  */
1983
2179
  private vaultHoldings;
1984
- /**
1985
- * Works a limit order down by what a fill delivered, and puts the rest back on
1986
- * the orderbook.
1987
- *
1988
- * The order is moved to `resizing` and its new size written before the
1989
- * orderbook is touched, so a crash mid-way leaves a row that says what it was
1990
- * doing rather than one that silently advertises output it has already paid.
1991
- *
1992
- * The old entry is cancelled before the new one is posted. Two live entries
1993
- * for one liability would advertise the same output twice, and the gap between
1994
- * the two calls is at most one request.
1995
- */
1996
- settleFill(id: string, delivered: bigint): Promise<LimitOrder | null>;
1997
2180
  /**
1998
2181
  * Puts an order that has already been worked down back on the book, or closes
1999
2182
  * it when what is left is under the orderbook's dust floor for what it pays.
2000
2183
  *
2001
- * Separate from the draw-down because that is a store write that belongs in
2002
- * the same transaction as the hold it settles, while this is a round trip to
2003
- * the orderbook and must not be inside one.
2184
+ * The order is moved to `resizing` before the orderbook is touched, so a crash
2185
+ * mid-way leaves a row that says what it was doing rather than one that silently
2186
+ * advertises output it has already paid. The old entry is cancelled before the
2187
+ * new one is posted: two live entries for one liability would advertise the same
2188
+ * output twice, and the gap between the two calls is at most one request.
2004
2189
  */
2005
2190
  resize(remaining: LimitOrder, delivered: bigint): Promise<LimitOrder | null>;
2191
+ /** Takes an order that has been worked down past the dust floor off the book and closes the row. */
2192
+ private closeFilled;
2006
2193
  /**
2007
2194
  * Cancels the current posting and puts the order back at its present size.
2008
2195
  *
@@ -2011,6 +2198,13 @@ declare class LimitOrderService {
2011
2198
  * thing: whatever the order has left, live on the book again.
2012
2199
  */
2013
2200
  repost(order: LimitOrder | null): Promise<LimitOrder | null>;
2201
+ /**
2202
+ * The order at the size the chain's tally allows, ready to post: lowered in the store
2203
+ * when the tally leaves it less than it holds, and closed when what is left is under
2204
+ * the dust floor (answering null). A tally that cannot be read leaves the size alone,
2205
+ * and the periodic tally pass follows up.
2206
+ */
2207
+ private lowerBeforePosting;
2014
2208
  /** A listener that throws is the listener's problem, not the fill's. */
2015
2209
  private report;
2016
2210
  /**
@@ -2042,9 +2236,10 @@ declare class LimitOrderService {
2042
2236
  *
2043
2237
  * The row is read again and claimed out of `open` first: a fill can have worked
2044
2238
  * it down or started its own repost since reconciliation listed it, and that
2045
- * write is the one to follow. A fill that lands once the claim is taken waits
2046
- * in `resize` for this to finish. The EntryPoint is only written with a posting
2047
- * that landed, so a pass cut short anywhere leaves a row the next pass still
2239
+ * write is the one to follow. A fill that lands once the claim is taken cannot
2240
+ * sync the row, which is `resizing`; the repost lowers the order to the chain's
2241
+ * tally before it posts, and `resize` waits for this to finish. The EntryPoint
2242
+ * is only written with a posting that landed, so a pass cut short anywhere leaves a row the next pass still
2048
2243
  * reads as stale, or as having no entry, and finishes the job.
2049
2244
  */
2050
2245
  private resign;
@@ -2054,15 +2249,40 @@ declare class LimitOrderService {
2054
2249
  * pay for it, and puts the order back on the book at that size.
2055
2250
  *
2056
2251
  * The account tallies each order's payouts on the fill chain and refuses a fill
2057
- * that would take the tally past the order's size. A fill that never settled
2058
- * here, or a store restored from an older copy, leaves `remaining` above that
2059
- * room and the order posted at a size the account will refuse. A tally behind
2060
- * the store changes nothing: `remaining` is never raised.
2252
+ * that would take the tally past the order's size. Settlement brings an order
2253
+ * down to its tally after each fill; this pass catches what that missed: a sync
2254
+ * that failed, a chain read that did not yet show the fill, a store restored
2255
+ * from an older copy. A tally behind the store changes nothing: `remaining` is
2256
+ * never raised.
2061
2257
  *
2062
2258
  * Answers how many orders were lowered.
2063
2259
  */
2064
2260
  reconcileTallies(): Promise<number>;
2065
- /** The order as the store now holds it when its tally lowered it, null when it was left alone. */
2261
+ /**
2262
+ * Brings one order's `remaining` down to its on-chain tally and puts it back on
2263
+ * the book at that size, or closes it under the dust floor: what
2264
+ * {@link reconcileTallies} does for every open order.
2265
+ *
2266
+ * The tally is read at `blockNumber` when given: a fill passes the block it landed
2267
+ * in, so an endpoint lagging behind it cannot answer with the tally from before the
2268
+ * fill. The periodic pass reads the latest block.
2269
+ *
2270
+ * The read falls back to the latest block when the pinned one fails.
2271
+ *
2272
+ * Answers whether the order was lowered. False when it is not `open` (also when a
2273
+ * repost takes it between the check and the write), or when the tally leaves it where it is, including a chain that has not yet counted a
2274
+ * fill and a tally that cannot be read (logged by the read). Throws when the chain
2275
+ * cannot be read for the token's decimals, or the store or orderbook fail;
2276
+ * settlement catches that and the periodic pass follows up.
2277
+ */
2278
+ syncToChain(id: string, blockNumber?: bigint): Promise<boolean>;
2279
+ private lowerToTally;
2280
+ /**
2281
+ * The order as the store now holds it when its tally lowered it, null when it was left alone.
2282
+ *
2283
+ * `only` is the statuses the row may hold for the clamp to apply, so a row that moved to
2284
+ * another between the caller's read and this write is not touched.
2285
+ */
2066
2286
  private clampToTally;
2067
2287
  /**
2068
2288
  * Withdraws every order that has outlived the operator's own `expiresAt`.
@@ -2074,7 +2294,13 @@ declare class LimitOrderService {
2074
2294
  * reaches its TTL.
2075
2295
  */
2076
2296
  expireStale(now?: Date): Promise<number>;
2077
- /** Takes an expired order off the book and closes the row. */
2297
+ /**
2298
+ * Takes an expired order off the book and closes the row, and reports it so the bids
2299
+ * it priced, which would otherwise still pay out at its price, are retracted.
2300
+ *
2301
+ * Guarded like every other posting write: an order the operator cancelled, or a fill
2302
+ * closed, while the entry was coming down keeps that status and is not reported here.
2303
+ */
2078
2304
  private retire;
2079
2305
  /** Every order that has a posting on the book, or should have one. */
2080
2306
  private live;
@@ -2137,20 +2363,55 @@ declare class IntentFiller {
2137
2363
  * built from this same store, so it cannot exist before the filler does.
2138
2364
  */
2139
2365
  private limitOrderService?;
2366
+ /**
2367
+ * Set after construction, like the service: the orderbook every bid is posted to, beside
2368
+ * Hyperbridge. Absent only in tests that bid on Hyperbridge alone.
2369
+ */
2370
+ private orderbookClient?;
2371
+ /**
2372
+ * Whether the Hyperbridge account can pay for a bid, read once the connection is up. A bid
2373
+ * the account cannot pay for goes to the orderbook alone.
2374
+ */
2375
+ private bridgeBalance?;
2140
2376
  private retractionQueue;
2141
2377
  /**
2142
2378
  * Fills are settled one at a time, in the order they were scanned. Several fills of
2143
- * one order can land in one scan, and each settlement names its bid from the holds
2144
- * the previous ones left: run side by side, two of them read the same holds and
2145
- * claim the same one.
2379
+ * one order can land in one scan, and each settlement names its bid from the fills
2380
+ * the previous ones recorded: run side by side, two of them would name the same bid.
2146
2381
  */
2147
2382
  private settlementQueue;
2148
2383
  private paused;
2149
2384
  private stopping;
2150
2385
  private pendingRetractions;
2386
+ /**
2387
+ * Orders seen completed on chain, by us or a rival, lower-cased. The bid loop reads it
2388
+ * before each placement, because a bid placed on a completed order can only revert with
2389
+ * `Filled()` and still pays the placement fee and locks a deposit. Capped at
2390
+ * {@link MAX_COMPLETED_ORDERS}, oldest first.
2391
+ */
2392
+ private completedOrders;
2151
2393
  private rebalancingInterval?;
2152
2394
  private initialRebalanceTimer?;
2153
2395
  private activeRebalances;
2396
+ /**
2397
+ * Chains whose EIP-7702 delegation to the SolverAccount failed at boot.
2398
+ *
2399
+ * The bundler's estimate injects the SolverAccount code by state override, so an
2400
+ * undelegated EOA's bid passes estimation and pays the Hyperbridge bid fees and
2401
+ * deposit for a fill that can never execute. Orders for these chains are skipped
2402
+ * at intake, and a later successful delegation (`enableFills`) clears the entry.
2403
+ *
2404
+ * A mark is a belief about the chain, not proof: a delegation transaction that
2405
+ * mines after its receipt timeout, or a `getCode` that failed, leaves the wallet
2406
+ * delegated on chain while the chain stays marked. The dashboard cannot clear it
2407
+ * (its switch reads the chain and shows "Fills enabled"), so intake re-reads the
2408
+ * chain at most once per `DELEGATION_RECHECK_MS` and drops a stale mark.
2409
+ */
2410
+ private undelegatedChains;
2411
+ /** When each marked chain was last compared with the chain, by `Date.now()`. */
2412
+ private delegationCheckedAt;
2413
+ /** The re-check running now for a chain, so concurrent orders await its answer. */
2414
+ private delegationRechecks;
2154
2415
  private retractionSweepInterval?;
2155
2416
  private hyperbridge;
2156
2417
  private hyperbridgeEndpoint?;
@@ -2176,6 +2437,11 @@ declare class IntentFiller {
2176
2437
  */
2177
2438
  /** Hands the filler the service that keeps the orderbook in step with a fill. */
2178
2439
  setLimitOrderService(service: LimitOrderService): void;
2440
+ /**
2441
+ * Hands the filler the orderbook its bids are posted to. Every bid goes there; it goes to
2442
+ * Hyperbridge as well when the account holds BRIDGE for the deposit.
2443
+ */
2444
+ setOrderbook(client: OrderbookClient): void;
2179
2445
  /**
2180
2446
  * Brings the limit orders' `remaining` in line with their on-chain tallies.
2181
2447
  *
@@ -2198,6 +2464,24 @@ declare class IntentFiller {
2198
2464
  initialize(): Promise<void>;
2199
2465
  /** Chains that both fill and have solver selection active, as state machine ids. */
2200
2466
  private solverSelectionChains;
2467
+ private delegation;
2468
+ /**
2469
+ * Whether a marked chain is in fact delegated, and clears the mark when it is.
2470
+ *
2471
+ * Read at most once per `DELEGATION_RECHECK_MS` per chain. Orders arriving while a
2472
+ * read is in flight await its answer rather than being skipped on the old mark: the
2473
+ * read can be the one that clears it. A read that fails counts as not delegated,
2474
+ * the same as at boot.
2475
+ */
2476
+ private recheckDelegation;
2477
+ /**
2478
+ * Marks a chain undelegated when the wallet is not delegated there. For a chain
2479
+ * that was watch-only at boot: boot skips those, so delegation was never tried or
2480
+ * recorded, and turning watch-only off must not open a chain nothing can fill on.
2481
+ * A chain without solver selection needs no delegation and is left alone. The mark
2482
+ * clears through intake's re-check or `enableFills`.
2483
+ */
2484
+ guardDelegation(chainId: number): Promise<void>;
2201
2485
  /**
2202
2486
  * Delegates the filler EOA via EIP-7702 and tops up EntryPoint deposits.
2203
2487
  *
@@ -2268,12 +2552,34 @@ declare class IntentFiller {
2268
2552
  */
2269
2553
  private checkAndRebalance;
2270
2554
  private startRetractionSweep;
2271
- /** Enqueues retraction for every due bid; returns how many were queued. */
2555
+ /** Enqueues retraction for every due bid, one pass per commitment; returns how many bids were due. */
2272
2556
  private sweepExpiredBids;
2273
2557
  stop(): Promise<void>;
2558
+ /**
2559
+ * Whether the source gateway still holds escrow for every leg of the order:
2560
+ * `"held"`, `"missing"` for a read that came back empty, or `"unverifiable"` when
2561
+ * the read itself failed. The two failures are kept apart because they mean
2562
+ * different things to an operator: a confirmed zero is a phantom or reorged
2563
+ * commitment, while an unreadable escrow is an RPC fault that says nothing about
2564
+ * the order.
2565
+ *
2566
+ * With `quorum`, every leg is read in one `readWithQuorum` call so each
2567
+ * endpoint's vote is the whole escrow vector — an endpoint that disagrees on any
2568
+ * leg fails to join the agreeing group. That is the read used right before
2569
+ * bidding, where one endpoint alone should not vouch for a commitment a source
2570
+ * reorg may have rewritten; a quorum of the endpoints that answer must. Without
2571
+ * it, the legs are read on the single source client, as the pre-bid check has
2572
+ * always done.
2573
+ */
2274
2574
  private verifyOrderOnSource;
2275
2575
  private handleNewOrder;
2276
2576
  private evaluateOrder;
2577
+ /**
2578
+ * How long `confirmations` take on `chain`, at the block time measured when simplex started.
2579
+ * Zero when there is no block time to be had: the order then keeps the cache's own lifetime,
2580
+ * and an evaluation that expires before its bids is redone.
2581
+ */
2582
+ private confirmationWaitMs;
2277
2583
  /**
2278
2584
  * The destination chain's latest block number, the clock `order.deadline` is read on.
2279
2585
  *
@@ -2298,12 +2604,17 @@ declare class IntentFiller {
2298
2604
  */
2299
2605
  private feeCheckWaived;
2300
2606
  private executeOrder;
2607
+ /**
2608
+ * The venues a bid goes to. The orderbook always; Hyperbridge when there is a connection and
2609
+ * the account holds BRIDGE for the deposit, which the gate decides per bid.
2610
+ */
2611
+ private bidVenues;
2301
2612
  private handleOrderFilledOnChain;
2302
2613
  /**
2303
2614
  * A rival completed an order we bid on. `_filled[commitment]` is now set, so
2304
2615
  * every bid of ours on it can only revert with `Filled()`: retract them, which
2305
- * returns their Hyperbridge deposits and gives their holds back to the limit
2306
- * orders now rather than when the stale-bid sweep reaches them.
2616
+ * returns their Hyperbridge deposits now rather than when the stale-bid sweep
2617
+ * reaches them.
2307
2618
  *
2308
2619
  * Only a complete fill closes the order. After a rival's partial fill our bid
2309
2620
  * may still fill the rest, so it is left standing.
@@ -2313,68 +2624,138 @@ declare class IntentFiller {
2313
2624
  */
2314
2625
  private handleRivalCompletion;
2315
2626
  /**
2316
- * Turns the hold a filled bid was carrying into an actual draw-down.
2627
+ * Settles a fill of ours against the limit orders that priced our bids on its order.
2317
2628
  *
2318
- * The hold and the delivery are two different numbers: the hold is what the
2319
- * bid promised, the delivery is what the gateway recorded going out. Giving
2320
- * the hold back and taking the delivery off `remaining` is what leaves the
2321
- * order describing the output it still has.
2629
+ * The fill is attributed to one of those bids and recorded against that bid's
2630
+ * limit order, for its history and profit. Then every limit order that priced one
2631
+ * of our bids on the order is brought down to its on-chain tally, which is what
2632
+ * counted the fill: `remaining` follows the chain, not arithmetic here. A partial
2633
+ * fill and a completing one take the same path.
2322
2634
  *
2323
- * A fill with no amounts leaves the order alone. Sizing a draw-down from a
2324
- * guess would either advertise output already paid or quietly retire output
2325
- * still available, and both are worse than an order that looks unchanged
2326
- * until reconciliation notices.
2635
+ * Nothing here throws for a sync that failed or a chain that does not show the fill
2636
+ * yet; the periodic tally reconciliation lowers the order instead.
2327
2637
  */
2638
+ private settleFilledLimitOrder;
2328
2639
  /**
2329
- * Takes a hold on every limit order the payout draws on, or none at all.
2640
+ * Records the fill against the limit order of the bid it executed.
2641
+ *
2642
+ * A bid that failed outright cannot have executed, and a bid executes at most once,
2643
+ * so one already recorded against this order is passed over. A retracted bid is not:
2644
+ * retraction takes it out of discovery but does not stop a placer that already fetched
2645
+ * it, which can execute it until its `validUntil`. A retracted bid placed longer before
2646
+ * the fill than its validity window cannot have executed and is passed over; the window is
2647
+ * counted back from when the fill happened, not from when it is settled, which can be long
2648
+ * after behind the settlement queue (see `fillTimeMs`). The
2649
+ * same fill delivered twice, which a scanner replaying from a cursor can do, is recorded
2650
+ * once: a fill is named by its transaction hash and log index, since one transaction can
2651
+ * carry several of our fills.
2330
2652
  *
2331
- * A bid that reserved only part of what it means to pay would promise output
2332
- * no order is holding for it, so a hold that cannot be taken gives back the
2333
- * ones already taken. Reserving in the matcher's order keeps two evaluations
2334
- * racing the same pair of orders from taking them in opposite sequences.
2653
+ * Deduplication is weaker where those are missing. A fill with no transaction hash
2654
+ * cannot be named, and a bid row with no `bid` identifier cannot be told from the
2655
+ * others on its swap, so a replay of either may be recorded again. Only rows from
2656
+ * before bids carried an identifier and logs that carried no hash are affected.
2657
+ *
2658
+ * A multi-leg swap can be priced by limit orders that pay different tokens, so
2659
+ * the delivery is read per limit order and only bids whose order pays a token the
2660
+ * fill delivered are candidates. The fill is attributed by the escrow it released
2661
+ * (see `executedPricing`): a retracted bid at a better rate must not take the fill from a
2662
+ * live bid whose take covers that escrow, so retracted bids are attributed only when no
2663
+ * live bid could have been it. A live bid the escrow rules out does not hide a retracted
2664
+ * one that covers it.
2335
2665
  */
2336
- private holdAll;
2337
- private releaseAll;
2338
- private settleFilledLimitOrder;
2666
+ private recordExecutedFill;
2667
+ /**
2668
+ * Retracts the bids of a limit order the fill left closed (filled, cancelled or expired),
2669
+ * which can no longer pay out at its price. While the order is live its other bids are
2670
+ * left to the chain: `debitOrder` refuses a fill past its budget, and the gas of such a
2671
+ * revert is the accepted cost of not holding anything locally. Never throws.
2672
+ */
2673
+ private retractBidsOfClosedLimitOrder;
2674
+ /**
2675
+ * How long after it is stored a retracted bid may still execute, in milliseconds:
2676
+ * `bidValiditySeconds` plus `BID_DISCOVERY_PAD_SECONDS`, the window the bid is signed for,
2677
+ * counted from the row (written after the head that window was counted from was read),
2678
+ * plus `BID_PERMIT_SKEW_SECONDS`.
2679
+ *
2680
+ * The skew is the same execution tail the bid's Permit2 deadline allows: a bid can execute
2681
+ * until its permit deadline, which is this window plus that skew. `validUntil` is block-based
2682
+ * and the window is converted to blocks at the fastest block time seen, so a slower chain
2683
+ * keeps a bid executable longer in wall-clock time, and the fill is seen only after its block
2684
+ * is scanned.
2685
+ */
2686
+ private retractedBidWindowMs;
2687
+ /**
2688
+ * When a fill happened, in milliseconds: the timestamp of the block it landed in.
2689
+ *
2690
+ * The event's arrival time is only a stand-in. After downtime or a scanner catch-up a
2691
+ * fill arrives long after it landed, and measuring a retracted bid's window from the
2692
+ * arrival would put every such bid outside it. The arrival time is used when the block
2693
+ * is unknown (`undefined`, or the `0n` a scanner substitutes) or cannot be read.
2694
+ */
2695
+ private fillTimeMs;
2339
2696
  /**
2340
- * Claims the holds of the one bid of ours a partial fill executed, and nothing
2341
- * else. It is found among our live bids on the order the same way a completing
2342
- * fill names it, then claimed by its bid identifier, so the other bids keep their
2343
- * holds and can still fill the rest. A fill that names none of what our limit
2344
- * orders pay claims nothing.
2697
+ * Brings one limit order down to its on-chain tally after a fill, logging rather than throwing.
2698
+ * The tally is read at the fill's block, so a lagging endpoint cannot return the tally from before it.
2345
2699
  */
2346
- private claimExecutedBid;
2700
+ private syncLimitOrder;
2347
2701
  /**
2348
2702
  * What this fill delivered of the token the limit order pays, or null when the
2349
2703
  * fill names none of it.
2350
2704
  */
2351
2705
  private deliveredAgainst;
2352
2706
  /**
2353
- * Gives back whatever this bid still holds against its limit order.
2707
+ * Whether a limit order can still stand behind a bid at its price: `open`, or `resizing`
2708
+ * while a repost holds it. False once it is filled, cancelled, expired or rejected, or is
2709
+ * gone. Undefined when the store cannot be read, which a caller placing a bid treats as
2710
+ * unconfirmed and one retracting treats as nothing to do.
2711
+ */
2712
+ private limitOrderStands;
2713
+ private markCompleted;
2714
+ private hasCompleted;
2715
+ /**
2716
+ * Retracts the live bids a limit order priced, on every commitment they sit on.
2717
+ *
2718
+ * Called once the order can no longer stand behind them: the operator cancelled it, it
2719
+ * expired, or a fill left it filled or closed. Each such bid would still pay out at a
2720
+ * price the order no longer offers. Only the rows this order priced are retracted, so
2721
+ * another limit order's bid on the same swap order keeps standing, and a bid a pass
2722
+ * cannot close is flagged `dead` by row only.
2723
+ *
2724
+ * Retraction does not make the bids unexecutable: a placer that already fetched one can
2725
+ * execute it until its `validUntil`. A bid that does is settled by provenance like any
2726
+ * other fill, and the order's budget stays on chain. This only returns the deposit and
2727
+ * takes the bid out of discovery.
2354
2728
  *
2355
- * Safe to call more than once and from either settlement route: the store
2356
- * hands the reservation out once, so a bid that already converted its hold on
2357
- * a fill releases nothing here.
2729
+ * Never throws: it runs after a settlement, a cancel or an expiry that has already
2730
+ * happened, and a bid it misses is still retracted by the stale-bid sweep.
2358
2731
  */
2732
+ retractBidsForLimitOrder(limitOrderId: string): Promise<void>;
2359
2733
  /**
2360
- * Gives back what one bid held. Without a bid identifier it gives back every hold on
2361
- * the commitment, which is what a dead or filled order needs.
2734
+ * Queues a retraction of this filler's live bids on a commitment: all of them, or only
2735
+ * those named by bid identifier (`bids`) or by row (`rows`).
2736
+ *
2737
+ * A retraction takes the bid out of Hyperbridge's discovery and returns its deposit. It
2738
+ * does not stop a placer that already fetched the bid, which can still execute it until
2739
+ * its `validUntil` (`bidValiditySeconds` plus the pad), so it narrows the exposure and
2740
+ * `bidValiditySeconds` remains the hard bound on it.
2362
2741
  */
2363
- private releaseReservation;
2364
2742
  private enqueueRetraction;
2365
2743
  /**
2366
- * Retracts every bid this filler placed on a commitment, by the identifiers recorded when each
2367
- * was placed.
2744
+ * Retracts the given bid rows on a commitment, by the identifiers recorded when each was
2745
+ * placed, and answers which rows each outcome closed.
2368
2746
  *
2369
2747
  * The identifiers come from the bid rows rather than from Hyperbridge's storage: a bid whose
2370
2748
  * `place_bid` is still in the pool is not in storage yet, and reading storage would find
2371
2749
  * nothing and abandon its deposit once it lands. Its retraction goes out after it from the same
2372
2750
  * account, so it lands after it and reclaims it.
2373
2751
  *
2374
- * The answer speaks for the commitment as a whole. A bid already gone (`BidNotFound`) is done
2375
- * with; one that is pending or failed stops the pass, and the sweep comes back for the rest.
2376
- * Nothing recorded to retract reads as `BidNotFound`, which the caller treats as nothing left
2377
- * to reclaim.
2752
+ * A retraction that succeeded closes its rows with its hash. `BidNotFound` is terminal, not
2753
+ * retryable: bids only leave the pallet by retraction, so "no bid" means there is nothing
2754
+ * left to reclaim, and its rows close without one. A legacy row with no identifier closes the
2755
+ * same way. A failure leaves its rows open for a retry, and so does a retraction still in
2756
+ * Hyperbridge's pool: resubmitting can only bounce off it (1014), and if it lands the next
2757
+ * attempt's `BidNotFound` closes the row. A pooled one also stops the pass, since the
2758
+ * account's next extrinsic queues behind it; the bids after it stay open too.
2378
2759
  */
2379
2760
  private retractStoredBids;
2380
2761
  }
@@ -2384,8 +2765,6 @@ interface FillerRuntime {
2384
2765
  intentFiller: IntentFiller;
2385
2766
  balanceProvider: BalanceProvider;
2386
2767
  vaultVenue?: VaultFundingPlanner;
2387
- /** Self-halt visibility/reset for the trading engine (overfill protection). */
2388
- haltControls: HaltControl[];
2389
2768
  /** Live order-activity bridge; emits `event` per stored row. */
2390
2769
  activity: ActivityRecorder;
2391
2770
  /** The persistence backend this filler was started with. */
@@ -2509,9 +2888,14 @@ interface SimplexOptions {
2509
2888
  }
2510
2889
  /** Events a running filler emits. */
2511
2890
  interface SimplexEvents {
2891
+ /**
2892
+ * `blockHash` is the block the scanner read the placement from; a cross-chain
2893
+ * order is only filled while a quorum still places its transaction there.
2894
+ */
2512
2895
  "order:detected": {
2513
2896
  order: Order;
2514
2897
  transactionHash: string;
2898
+ blockHash: string;
2515
2899
  };
2516
2900
  "order:skipped": {
2517
2901
  orderId: string;
@@ -2606,7 +2990,6 @@ interface ChainInput {
2606
2990
  }
2607
2991
  interface SimplexStatus {
2608
2992
  paused: boolean;
2609
- halted: boolean;
2610
2993
  /** The solver's EVM account — the same value as `simplex.address`. */
2611
2994
  address: HexString;
2612
2995
  startedAt: number;
@@ -2630,7 +3013,7 @@ declare class LimitOrderController {
2630
3013
  list(filter?: LimitOrderFilter): Promise<LimitOrder[]>;
2631
3014
  get(id: string): Promise<LimitOrder | null>;
2632
3015
  /**
2633
- * One limit order with its fills and the bids still drawing on it, newest first.
3016
+ * One limit order with its fills and the bids it priced, newest first.
2634
3017
  *
2635
3018
  * What makes `remaining` explicable: a size that shrank is the sum of the fills
2636
3019
  * behind it, and the operator can see which ones. Fills are recorded when they
@@ -2729,7 +3112,17 @@ declare class ChainController {
2729
3112
  * lacks the gateway fee token or the native the paymaster's one-time setup needs.
2730
3113
  */
2731
3114
  enableFills(chainId: number): Promise<void>;
2732
- /** A global boolean is left alone — expanding it per chain changes how the config validates. */
3115
+ /**
3116
+ * Writes watch-only edits back into the config. A global boolean is left alone —
3117
+ * expanding it per chain changes how the config validates.
3118
+ *
3119
+ * `changed` is the chain just added, removed or toggled, and its new value. It only
3120
+ * matters under the `--watch-only` override: there the in-memory snapshot marks every
3121
+ * chain, and writing it back would make one run permanent for every later run.
3122
+ * Instead only the edited chain is applied — its key deleted when it stops being
3123
+ * watch-only, kept as an explicit flag otherwise — so the override itself never lands
3124
+ * in the file and a later override-free run sees what it would have anyway.
3125
+ */
2733
3126
  private syncWatchOnlyToConfig;
2734
3127
  }
2735
3128
  /** ERC-4626 treasury vaults the filler sources fills from and sweeps into. */
@@ -2866,12 +3259,6 @@ declare class Simplex extends EventEmitter {
2866
3259
  * deposits still locked in the solver-selection pallet.
2867
3260
  */
2868
3261
  bidStats(): Promise<BidStats>;
2869
- /**
2870
- * Whether overfill protection has self-halted the engine. A halted filler
2871
- * refuses every order until {@link resetHalt}.
2872
- */
2873
- isHalted(): boolean;
2874
- resetHalt(): void;
2875
3262
  /** Restricts which order `user` addresses are processed. Omit to accept all. */
2876
3263
  setAllowlist(allowlist: AllowlistConfig | undefined): Promise<void>;
2877
3264
  setLogLevel(level: LogLevel): void;