@hyperbridge/simplex 0.18.1 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -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.cjs';
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.cjs';
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.cjs';
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.cjs';
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,412 +850,756 @@ 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;
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>;
1117
1186
  /**
1118
- * Gets the decimals for a token
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.
1119
1190
  */
1120
- getTokenDecimals(tokenAddress: string, chain: string): Promise<number>;
1191
+ executeOrder(order: Order, bids?: BidPlacer): Promise<FillResult>;
1121
1192
  /**
1122
- * Estimates gas for filling an order and caches the full estimate for bid preparation
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.
1123
1195
  */
1124
- estimateGasFillPost(order: Order): Promise<{
1125
- totalCostInSourceFeeToken: bigint;
1126
- relayerFeeInSourceFeeToken: bigint;
1127
- dispatchFee: bigint;
1128
- callGasLimit: bigint;
1129
- }>;
1196
+ getOrderUsdValue?(order: Order): Promise<{
1197
+ inputUsd: Decimal;
1198
+ } | null>;
1130
1199
  /**
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.
1200
+ * Optional confirmation policy for this strategy.
1201
+ * If absent, no confirmation waiting is required (e.g. same-chain strategies).
1140
1202
  */
1141
- private callGasLimitFor;
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 {
1142
1216
  /**
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
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.
1147
1219
  */
1148
- getFeeTokenWithDecimals(chain: string): Promise<{
1149
- address: HexString;
1150
- decimals: number;
1151
- }>;
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;
1152
1251
  /**
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)
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.
1160
1256
  */
1161
- topUpEntryPointDeposit(chain: string, targetGasUnits?: bigint, thresholdGasUnits?: bigint): Promise<void>;
1257
+ private settle;
1162
1258
  /**
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)
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.
1168
1261
  */
1169
- getInputUsdValue(order: Order): Promise<Decimal>;
1262
+ private summarize;
1170
1263
  /**
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
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.
1176
1266
  */
1177
- isSolverSelectionActive(chain: string): Promise<boolean>;
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
+ /** When each order's bid loop began, for the orders whose loop is still running. */
1324
+ private readonly pins;
1325
+ private logger;
1326
+ constructor(loggers?: LoggerContext);
1178
1327
  /**
1179
- * Reads the solver account's deposit balance on the ERC-4337 EntryPoint.
1328
+ * Whether an entry is still fresh. A pinned order's entries are aged as of the pin,
1329
+ * so what was fresh when its bid loop began stays fresh until the loop ends, and
1330
+ * what had already expired stays expired.
1180
1331
  */
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>;
1332
+ private isCacheValid;
1183
1333
  /**
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.
1334
+ * Keeps the order's evaluation alive while its bids go out, and returns the release.
1186
1335
  *
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.
1190
- */
1191
- limitOrderSpent(chain: string, budgetId: HexString): Promise<bigint | null>;
1192
- getSolverEntryPointBalance(chain: string): Promise<bigint>;
1193
- /**
1194
- * Deposits native tokens to the ERC-4337 EntryPoint on behalf of the solver account.
1195
- * @returns The transaction hash of the deposit.
1196
- */
1197
- depositToEntryPoint(chain: string, amount: bigint): Promise<HexString>;
1198
- /**
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.
1201
- */
1202
- withdrawFromEntryPoint(chain: string): Promise<HexString | null>;
1203
- /**
1204
- * Withdraws EntryPoint deposits on all configured chains that have a positive balance.
1336
+ * The bids on one order are sent in turn, each priced by the evaluation that planned
1337
+ * them, and every one reads the shared gas estimate back out of here. Submitting a
1338
+ * bid can take longer than `CACHE_EXPIRY_MS` on its own, and the order is not
1339
+ * evaluated again, so the estimate has to outlive the loop for the later bids to be
1340
+ * sent at all. Estimating afresh instead would sign them at a gas cost their payouts
1341
+ * were not priced against.
1205
1342
  */
1206
- withdrawAllEntryPointDeposits(): Promise<void>;
1343
+ pinOrder(orderId: string): () => void;
1344
+ private cleanupStaleData;
1345
+ getGasEstimate(orderId: string): {
1346
+ totalCostInSourceFeeToken: bigint;
1347
+ relayerFeeInSourceFeeToken: bigint;
1348
+ dispatchFee: bigint;
1349
+ callGasLimit: bigint;
1350
+ verificationGasLimit: bigint;
1351
+ preVerificationGas: bigint;
1352
+ maxFeePerGas: bigint;
1353
+ maxPriorityFeePerGas: bigint;
1354
+ totalGasCostWei: bigint;
1355
+ } | null;
1356
+ setGasEstimate(orderId: string, totalCostInSourceFeeToken: bigint, relayerFeeInSourceFeeToken: bigint, dispatchFee: bigint, callGasLimit: bigint, verificationGasLimit: bigint, preVerificationGas: bigint, maxFeePerGas: bigint, maxPriorityFeePerGas: bigint, totalGasCostWei: bigint): void;
1357
+ getSwapOperations(orderId: string): {
1358
+ calls: SwapCall[];
1359
+ totalGasEstimate: bigint;
1360
+ } | null;
1361
+ setSwapOperations(orderId: string, calls: SwapCall[], totalGasEstimate: bigint): void;
1362
+ getFillerOutputs(orderId: string): {
1363
+ token: HexString;
1364
+ amount: bigint;
1365
+ }[] | null;
1366
+ /** The takes cached with the order's filler outputs, or null when absent or stale. */
1367
+ getFillerInputs(orderId: string): {
1368
+ token: HexString;
1369
+ amount: bigint;
1370
+ }[] | null;
1207
1371
  /**
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.
1372
+ * The budget of the limit order that priced the outputs cached for this order, or
1373
+ * null when no limit order did.
1217
1374
  *
1218
- * Same-chain concept only — the cross-chain path has no partial-fill state.
1375
+ * Kept on the same entry as the outputs, so the two are replaced and expire
1376
+ * together: a bid can never sign one limit order's payout beside another's budget.
1219
1377
  */
1220
- partialFillsFor(order: Order, chain: string): Promise<bigint[]>;
1378
+ getBidBudget(orderId: string): LimitOrderBudget | null;
1379
+ setFillerOutputs(orderId: string, outputs: {
1380
+ token: HexString;
1381
+ amount: bigint;
1382
+ }[], inputs?: {
1383
+ token: HexString;
1384
+ amount: bigint;
1385
+ }[], budget?: LimitOrderBudget): void;
1221
1386
  /**
1222
- * Prepares a signed PackedUserOperation for bid submission to Hyperbridge
1387
+ * The limit order this order was priced against, and the payout it allowed.
1223
1388
  *
1224
- * Uses cached gas estimates from prior profitability check (estimateGasFillPost)
1389
+ * Carried from `calculateProfitability` to the bid so the reservation lands on
1390
+ * the same limit order the price came from. A fill later finds it through the
1391
+ * bid row, not through here.
1392
+ */
1393
+ getMatchedLimitOrder(orderId: string): {
1394
+ limitOrderId: string;
1395
+ payout: bigint;
1396
+ }[];
1397
+ getBidPlans(orderId: string): BidPlan[];
1398
+ setBidPlans(orderId: string, plans: BidPlan[]): void;
1399
+ clearBidPlans(orderId: string): void;
1400
+ setMatchedLimitOrder(orderId: string, holds: {
1401
+ limitOrderId: string;
1402
+ payout: bigint;
1403
+ }[]): void;
1404
+ getPairClassifications(orderId: string): CachedPairClassification[] | null;
1405
+ setPairClassifications(orderId: string, pairs: CachedPairClassification[]): void;
1406
+ getFundingPrepends(orderId: string): {
1407
+ calls: ERC7821Call[];
1408
+ } | null;
1409
+ setFundingPrepends(orderId: string, calls: ERC7821Call[]): void;
1410
+ clearFundingPrepends(orderId: string): void;
1411
+ /**
1412
+ * Whether the strategy's completed evaluation of this order chose a partial fill.
1413
+ *
1414
+ * Only ever written by an evaluation that went on to return a fillable answer,
1415
+ * and cleared at the start of every evaluation — so a refusal can never leave a
1416
+ * `true` behind for the caller to act on.
1417
+ */
1418
+ isPartialFill(orderId: string): boolean;
1419
+ setPartialFill(orderId: string, partial: boolean): void;
1420
+ clearPartialFill(orderId: string): void;
1421
+ /**
1422
+ * Whether the strategy's completed evaluation let a bid through without holding
1423
+ * `order.fees` to the execution cost, because the order is at or above the
1424
+ * operator's minimum size.
1425
+ *
1426
+ * Written and cleared the way {@link isPartialFill} is, for the same reason: the
1427
+ * caller exempts such an order from its profit floor.
1428
+ */
1429
+ isFeeCheckWaived(orderId: string): boolean;
1430
+ setFeeCheckWaived(orderId: string, waived: boolean): void;
1431
+ clearFeeCheckWaived(orderId: string): void;
1432
+ getFeeTokenWithDecimals(chain: string): {
1433
+ address: HexString;
1434
+ decimals: number;
1435
+ } | null;
1436
+ setFeeTokenWithDecimals(chain: string, address: HexString, decimals: number): void;
1437
+ getTokenDecimals(chain: string, tokenAddress: HexString): number | null;
1438
+ setTokenDecimals(chain: string, tokenAddress: HexString, decimals: number): void;
1439
+ getSolverSelection(chain: string): boolean | null;
1440
+ setSolverSelection(chain: string, active: boolean): void;
1441
+ }
1442
+
1443
+ /** What a bid's batch needs to settle its limit order's budget. */
1444
+ interface BudgetSettlement {
1445
+ budget: LimitOrderBudget;
1446
+ /** The solver's account, which is the only caller `debitOrder` accepts. */
1447
+ solverAccount: HexString;
1448
+ }
1449
+ declare class ContractInteractionService {
1450
+ private clientManager;
1451
+ private configService;
1452
+ cacheService: CacheService;
1453
+ private logger;
1454
+ private sdkHelperCache;
1455
+ private blockTimes;
1456
+ private solverAccountAddress;
1457
+ private signer;
1458
+ constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer, sharedCacheService?: CacheService);
1459
+ /**
1460
+ * The endpoint SDK helpers should use for `chain`: the first configured RPC,
1461
+ * matching the one viem's `fallback` transport tries first.
1462
+ */
1463
+ private primaryRpcUrl;
1464
+ /**
1465
+ * Gets the SDK helper for a given source and destination chain.
1466
+ * Instances are cached and reused to avoid redundant RPC calls.
1467
+ */
1468
+ getIntentGateway(source: string, destination: string): Promise<IntentGateway>;
1469
+ /**
1470
+ * Best-effort cache warm-up. Deliberately never rejects: this runs unawaited from the
1471
+ * constructor, so a rejection would surface as an unhandled rejection and take the
1472
+ * process down. A failed prewarm only means the value is fetched on first use, where
1473
+ * `getTokenDecimals` is strict — which is the right place for strictness, because
1474
+ * there it can skip a single order instead of killing the filler at boot.
1475
+ */
1476
+ initCache(): Promise<void>;
1477
+ getCache(): CacheService;
1478
+ /**
1479
+ * Gets the decimals for a token
1480
+ */
1481
+ getTokenDecimals(tokenAddress: string, chain: string): Promise<number>;
1482
+ /**
1483
+ * Estimates gas for filling an order and caches the full estimate for bid preparation
1484
+ */
1485
+ estimateGasFillPost(order: Order): Promise<{
1486
+ totalCostInSourceFeeToken: bigint;
1487
+ relayerFeeInSourceFeeToken: bigint;
1488
+ dispatchFee: bigint;
1489
+ callGasLimit: bigint;
1490
+ }>;
1491
+ /**
1492
+ * Logs a bundler gas estimate for `order` that failed transiently and is sent again
1493
+ * (the SDK retries a network failure, a JSON-RPC internal error, or an HTTP 408, 429 or 5xx).
1494
+ */
1495
+ private logBundlerRetry;
1496
+ /**
1497
+ * The call gas a bid needs: the shared estimate plus room for the funding calls
1498
+ * this bid prepends and for settling its limit order's budget.
1499
+ *
1500
+ * Funding calls are not simulated (the V4 PositionManager's flash accounting does
1501
+ * not resolve in the bundler's estimation context), so each one adds a fixed
1502
+ * allowance for its modifyLiquidities + take. They differ between the bids on one
1503
+ * order, since each draws on its own limit order and so on its own funding, which
1504
+ * is why this is worked out per bid rather than baked into the cached estimate.
1505
+ * The estimate is of a lone fillOrder, so the budget call is not in it either, nor are
1506
+ * the approvals: each token the batch approves adds `APPROVAL_GAS_PER_TOKEN`.
1507
+ */
1508
+ private callGasLimitFor;
1509
+ /**
1510
+ * What the batch approves to the gateway for `order`, per token, and the fee token's key
1511
+ * when the fill dispatches. The fee token is read from the destination host.
1512
+ */
1513
+ private approvalsFor;
1514
+ /**
1515
+ * Gets the fee token address and decimals for a given chain.
1516
+ *
1517
+ * @param chain - The chain identifier to get fee token info for
1518
+ * @returns An object containing the fee token address and its decimal places
1519
+ */
1520
+ getFeeTokenWithDecimals(chain: string): Promise<{
1521
+ address: HexString;
1522
+ decimals: number;
1523
+ }>;
1524
+ /**
1525
+ * Tops up the solver's EntryPoint deposit so it covers at least
1526
+ * `targetGasUnits` at the current gas price. Skips if the wallet
1527
+ * balance cannot afford at least 1M gas units (not enough to send txs).
1528
+ *
1529
+ * @param chain - The chain identifier
1530
+ * @param targetGasUnits - Gas units the deposit should cover (default 3M)
1531
+ * @param thresholdGasUnits - Only top up if deposit is below this many gas units (defaults to targetGasUnits)
1532
+ */
1533
+ topUpEntryPointDeposit(chain: string, targetGasUnits?: bigint, thresholdGasUnits?: bigint): Promise<void>;
1534
+ /**
1535
+ * Calculates the total USD value of an order's inputs.
1536
+ * Only stable (USDC/USDT) inputs contribute; non-stables contribute 0.
1537
+ *
1538
+ * @param order - The order to calculate input value for
1539
+ * @returns The total USD value of inputs (sum of normalized stable amounts, or 0 if none)
1540
+ */
1541
+ getInputUsdValue(order: Order): Promise<Decimal>;
1542
+ /**
1543
+ * Checks if solver selection mode is active on the destination chain
1544
+ * When active, fillers must submit bids to Hyperbridge instead of filling directly
1545
+ *
1546
+ * @param chain - The chain identifier to check
1547
+ * @returns True if solver selection is active
1548
+ */
1549
+ isSolverSelectionActive(chain: string): Promise<boolean>;
1550
+ /**
1551
+ * Reads the solver account's deposit balance on the ERC-4337 EntryPoint.
1552
+ */
1553
+ /** What `holder` holds of an ERC-20 on `chain`, in the token's own units. */
1554
+ getTokenBalance(chain: string, token: HexString, holder: HexString): Promise<bigint>;
1555
+ /**
1556
+ * What the solver's account has tallied against a limit order's budget on `chain`, in
1557
+ * the paid token's own units, or null when it cannot be read.
1558
+ *
1559
+ * Null rather than a throw: an account whose implementation keeps no tally answers
1560
+ * with a revert or with no data, and the caller carries on without the figure either
1561
+ * way. Not retried, since that answer does not change on a second ask.
1562
+ *
1563
+ * Read at `blockNumber` when given, otherwise at the latest block. A load-balanced
1564
+ * endpoint can lag the one that saw a fill, so a read at `latest` right after the
1565
+ * fill can return the tally from before it. The tally only grows, so a read at the
1566
+ * fill's own block already includes that fill. An endpoint that has not reached the
1567
+ * block answers with an error, which is null here: no figure, rather than a stale one.
1568
+ */
1569
+ limitOrderSpent(chain: string, budgetId: HexString, blockNumber?: bigint): Promise<bigint | null>;
1570
+ getSolverEntryPointBalance(chain: string): Promise<bigint>;
1571
+ /**
1572
+ * Deposits native tokens to the ERC-4337 EntryPoint on behalf of the solver account.
1573
+ * @returns The transaction hash of the deposit.
1574
+ */
1575
+ depositToEntryPoint(chain: string, amount: bigint): Promise<HexString>;
1576
+ /**
1577
+ * Withdraws the solver's full EntryPoint deposit back to the solver EOA on a single chain.
1578
+ * @returns The transaction hash, or null if there was nothing to withdraw.
1579
+ */
1580
+ withdrawFromEntryPoint(chain: string): Promise<HexString | null>;
1581
+ /**
1582
+ * Withdraws EntryPoint deposits on all configured chains that have a positive balance.
1583
+ */
1584
+ withdrawAllEntryPointDeposits(): Promise<void>;
1585
+ /**
1586
+ * Output already delivered against this order by any solver, per leg (the gateway keys fill
1587
+ * progress by output index, so legs repeating a token are reported separately; a gateway not yet
1588
+ * upgraded to per-leg escrow is read by token).
1589
+ *
1590
+ * A partially filled order has had its escrow drawn down, so the pro-rata
1591
+ * release a later filler receives is computed against the *residual*, not
1592
+ * against `order.inputs[i].amount`. A strategy that prices off the original
1593
+ * inputs would overstate its take, which is why the partial-fill path refuses
1594
+ * any order this reports as already touched.
1595
+ *
1596
+ * Same-chain concept only — the cross-chain path has no partial-fill state.
1597
+ */
1598
+ partialFillsFor(order: Order, chain: string): Promise<bigint[]>;
1599
+ /**
1600
+ * Prepares a signed PackedUserOperation for bid submission to Hyperbridge
1601
+ *
1602
+ * Uses cached gas estimates from prior profitability check (estimateGasFillPost)
1225
1603
  * to avoid redundant RPC calls.
1226
1604
  *
1227
1605
  * @param order - The order to prepare a bid for
@@ -1239,6 +1617,16 @@ declare class ContractInteractionService {
1239
1617
  userOp: HexString;
1240
1618
  bid: HexString;
1241
1619
  }>;
1620
+ /**
1621
+ * Throws unless an op with no paymaster can pay its own prefund. The EntryPoint takes it from
1622
+ * the account's deposit and asks the account for the rest out of its native balance, so
1623
+ * between them they must cover `required`. Nothing funds a deposit on a chain with a
1624
+ * paymaster configured, so when selection comes back empty there a solver holding little
1625
+ * native would place a bid, pay Hyperbridge's bid fee for it, and fail validation (AA21)
1626
+ * when it is selected. Reads that fail let the bid through, as the paymaster deposit gate
1627
+ * does: a transient RPC error must not cost a bid, and the worst case is that failure.
1628
+ */
1629
+ private assertUnsponsoredBidFunded;
1242
1630
  /**
1243
1631
  * Last block on the destination chain at which a bid signed now may still execute.
1244
1632
  *
@@ -1264,7 +1652,11 @@ declare class ContractInteractionService {
1264
1652
  * speeds up (BSC makes a block every 0.45s against a declared 0.75s) and some chains
1265
1653
  * declare none. Converting at a slower block time than the chain's ends a bid early, so
1266
1654
  * 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.
1655
+ * stretch in the sample never shortens a bid. A measurement below half the declared figure is
1656
+ * discarded, which limits how far one RPC can stretch a bid to twice its window. A chain that
1657
+ * declares none has no reference to judge a sample against, so it only gets the flat floor
1658
+ * {@link MIN_PLAUSIBLE_BLOCK_TIME_SECONDS}; below that the measurement is discarded for
1659
+ * {@link FALLBACK_BLOCK_TIME_SECONDS}.
1268
1660
  *
1269
1661
  * Blocks that cannot be read fall back to the declared figure, then to
1270
1662
  * {@link FALLBACK_BLOCK_TIME_SECONDS}. That answer is not kept.
@@ -1307,6 +1699,13 @@ declare class ContractInteractionService {
1307
1699
  * Builds ERC-7821 batch calldata that approves exactly what this bid can pay
1308
1700
  * before the fillOrder call, all within a single UserOp payload.
1309
1701
  *
1702
+ * The gateway is approved for exactly what this fill pays: the outputs in each
1703
+ * ERC20, plus the dispatch fee (`dispatchFee`) in the fee token on a cross-chain
1704
+ * fill. Nothing else goes in an approval: the fill's gas is paid through the
1705
+ * paymaster's Permit2 pull. The batch ends by setting every allowance it set back
1706
+ * to zero, so none stands once it completes. The gateway is an upgradeable proxy
1707
+ * and a standing allowance is exposure to a future implementation.
1708
+ *
1310
1709
  * The approvals are unconditional. One solver can hold several bids on one
1311
1710
  * order, at different levels of its book, and they execute one after another:
1312
1711
  * an approval skipped because the allowance covered this bid when it was signed
@@ -1322,9 +1721,11 @@ declare class ContractInteractionService {
1322
1721
  * A bid priced by a limit order ends with `debitOrder`, which takes the budget's id
1323
1722
  * as its `orderId`, works out what the fill paid from what is left of the allowance
1324
1723
  * 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.
1724
+ * come after the gateway has drawn on the allowance, and the zero-resets after it,
1725
+ * since it reads the allowance that is left. It clears the budget token's allowance
1726
+ * itself, so the resets skip that token.
1326
1727
  */
1327
- buildApprovalAndFillCalldata(order: Order, fillerOutputs: TokenInfo[], fillOptions: FillOptions, requiredFeeTokenAmount: bigint, settlement?: BudgetSettlement): Promise<HexString>;
1728
+ buildApprovalAndFillCalldata(order: Order, fillerOutputs: TokenInfo[], fillOptions: FillOptions, settlement?: BudgetSettlement): Promise<HexString>;
1328
1729
  }
1329
1730
 
1330
1731
  interface BinanceCexConfig {
@@ -1390,386 +1791,147 @@ declare class CctpRebalancer {
1390
1791
  */
1391
1792
  sendCctp(options: RebalanceOptions): Promise<BridgeResult>;
1392
1793
  /**
1393
- * Estimates the cost of a CCTP transfer
1394
- */
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;
1794
+ * Estimates the cost of a CCTP transfer
1795
+ */
1796
+ estimateCctp(options: RebalanceOptions): Promise<EstimateResult>;
1797
+ /**
1798
+ * Retries a failed CCTP transfer
1799
+ */
1800
+ retrySendCctp(failedResult: BridgeResult): Promise<BridgeResult>;
1703
1801
  }
1704
- type CancelOrderResult = {
1705
- kind: "cancelled";
1706
- commitment: HexString;
1707
- } | {
1708
- kind: "rejected";
1709
- code: MessageRejectionCode;
1710
- message: string;
1802
+
1803
+ interface Usdt0TransferResult {
1804
+ success: boolean;
1805
+ txHash: HexString;
1806
+ amountSent: bigint;
1807
+ amountReceived: bigint;
1808
+ nativeFee: bigint;
1809
+ }
1810
+ interface Usdt0EstimateResult {
1811
+ amountSent: bigint;
1812
+ amountReceived: bigint;
1813
+ nativeFee: bigint;
1814
+ minAmount: bigint;
1815
+ maxAmount: bigint;
1711
1816
  }
1817
+
1712
1818
  /**
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.
1819
+ * Options for rebalancing transfers (CCTP, USDT0, or CEX).
1820
+ * - For CCTP/USDT0: coin is implicit (CCTP=USDC, USDT0=USDT), recipientAddress is optional
1821
+ * - For unified router/CEX: coin is required for routing, recipientAddress is ignored
1716
1822
  */
1717
- | {
1718
- kind: "failed";
1719
- message: string;
1823
+ interface RebalanceOptions {
1824
+ /** Amount in human-readable format (e.g., "100.00") */
1825
+ amount: string;
1826
+ /** Source chain in state machine format (e.g., "EVM-137") */
1827
+ source: string;
1828
+ /** Destination chain in state machine format (e.g., "EVM-42161") */
1829
+ destination: string;
1830
+ /** Optional recipient address (defaults to sender's address) - only used for CCTP/USDT0, ignored for CEX */
1831
+ recipientAddress?: HexString;
1832
+ }
1833
+ /**
1834
+ * UnifiedRebalanceOptions is kept for broader compatibility.
1835
+ */
1836
+ type UnifiedRebalanceOptions = RebalanceOptions & {
1837
+ coin: "USDC" | "USDT";
1720
1838
  };
1721
1839
 
1722
1840
  /**
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.
1841
+ * Represents a planned transfer from one chain to another for portfolio-level rebalancing.
1728
1842
  */
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>;
1843
+ interface PlannedTransfer {
1844
+ sourceChain: string;
1845
+ destChain: string;
1846
+ coin: "USDC" | "USDT";
1847
+ amount: string;
1848
+ }
1849
+ /**
1850
+ * Result of checking portfolio rebalancing triggers.
1851
+ */
1852
+ interface TriggerCheckResult {
1853
+ triggered: boolean;
1854
+ triggeredChains: Array<{
1855
+ chain: string;
1856
+ asset: "USDC" | "USDT";
1857
+ deficit: number;
1858
+ }>;
1859
+ }
1860
+ /**
1861
+ * Result of a portfolio rebalancing operation.
1862
+ */
1863
+ interface RebalancingResult {
1864
+ success: boolean;
1865
+ transfers: PlannedTransfer[];
1866
+ executedTransfers: Array<{
1867
+ transfer: PlannedTransfer;
1868
+ result: unknown;
1869
+ }>;
1870
+ errors?: string[];
1871
+ }
1872
+ /**
1873
+ * RebalancingService - Facade over CCTP, USDT0, and Binance CEX rebalancers.
1874
+ * Provides both low-level methods (sendCctp, sendUsdt0, sendViaCex) and a high-level router (rebalance).
1875
+ */
1876
+ declare class RebalancingService {
1877
+ private readonly chainClientManager;
1878
+ private readonly configService;
1879
+ private readonly walletAddress;
1880
+ private readonly cctpRebalancer;
1881
+ private readonly usdt0Rebalancer;
1882
+ private readonly binanceRebalancer?;
1883
+ private readonly logger;
1884
+ constructor(chainClientManager: ChainClientManager, configService: FillerConfigService, binanceConfig?: BinanceCexConfig);
1885
+ sendCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.BridgeResult>;
1886
+ estimateCctp(options: RebalanceOptions): Promise<_circle_fin_bridge_kit.EstimateResult>;
1887
+ retrySendCctp(failedResult: Awaited<ReturnType<CctpRebalancer["sendCctp"]>>): Promise<_circle_fin_bridge_kit.BridgeResult>;
1888
+ sendUsdt0(options: RebalanceOptions): Promise<Usdt0TransferResult>;
1889
+ estimateUsdt0(options: RebalanceOptions): Promise<Usdt0EstimateResult>;
1890
+ sendViaCex(options: UnifiedRebalanceOptions): Promise<CexRebalanceResult>;
1891
+ estimateCexRebalance(options: UnifiedRebalanceOptions): Promise<CexRebalanceEstimate>;
1739
1892
  /**
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.
1893
+ * Execute a single cross-chain transfer, selecting the best route (CCTP / USDT0 / CEX).
1894
+ * Requires coin to be specified for routing decisions.
1743
1895
  */
1744
- levelOrders(id: string, level: OrderbookLevelQuery): Promise<OrderbookLevelOrder[] | null>;
1745
- submitOrder(userOp: HexString): Promise<SubmitOrderResult>;
1896
+ rebalance(options: UnifiedRebalanceOptions): Promise<Awaited<ReturnType<CctpRebalancer["sendCctp"]>> | Usdt0TransferResult | CexRebalanceResult>;
1746
1897
  /**
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.
1898
+ * Check if any chains/assets have triggered portfolio rebalancing thresholds.
1752
1899
  */
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>;
1900
+ checkRebalanceTriggers(): Promise<TriggerCheckResult>;
1765
1901
  /**
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.
1902
+ * Plan portfolio-level rebalancing transfers for triggered chains using greedy matching.
1770
1903
  */
1771
- myOrders(solver: HexString, after?: string): Promise<PostedOrderPage>;
1772
- private request;
1904
+ planPortfolioRebalancing(triggeredChains: TriggerCheckResult["triggeredChains"]): Promise<PlannedTransfer[]>;
1905
+ /**
1906
+ * Plan transfers for a specific asset using greedy surplus–deficit matching.
1907
+ */
1908
+ private planTransfersForAsset;
1909
+ /**
1910
+ * Execute planned portfolio-level transfers via this service.
1911
+ */
1912
+ executePortfolioRebalancing(transfers: PlannedTransfer[]): Promise<RebalancingResult>;
1913
+ /**
1914
+ * High-level portfolio rebalance:
1915
+ * 1) Check triggers
1916
+ * 2) Plan transfers
1917
+ * 3) Execute transfers
1918
+ */
1919
+ rebalancePortfolio(): Promise<RebalancingResult>;
1920
+ /**
1921
+ * Get current balance for a token on a chain.
1922
+ * Returns balance in normalized USD value (treating token as $1).
1923
+ */
1924
+ private getCurrentBalance;
1925
+ estimate(options: UnifiedRebalanceOptions): Promise<{
1926
+ method: "cctp";
1927
+ estimate: Awaited<ReturnType<CctpRebalancer["estimateCctp"]>>;
1928
+ } | {
1929
+ method: "usdt0";
1930
+ estimate: Usdt0EstimateResult;
1931
+ } | {
1932
+ method: "cex";
1933
+ estimate: CexRebalanceEstimate;
1934
+ }>;
1773
1935
  }
1774
1936
 
1775
1937
  /**
@@ -1809,8 +1971,9 @@ interface CreateLimitOrderRequest {
1809
1971
  }
1810
1972
  /**
1811
1973
  * 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.
1974
+ * down, took it under the dust floor, it outlived its `expiresAt` and was
1975
+ * retired, or the orderbook refused its posting. Operator-initiated changes are
1976
+ * reported by the controller that took the request.
1814
1977
  */
1815
1978
  type LimitOrderEvent = {
1816
1979
  kind: "resized";
@@ -1819,6 +1982,12 @@ type LimitOrderEvent = {
1819
1982
  } | {
1820
1983
  kind: "filled";
1821
1984
  order: LimitOrder;
1985
+ } | {
1986
+ kind: "expired";
1987
+ order: LimitOrder;
1988
+ } | {
1989
+ kind: "rejected";
1990
+ order: LimitOrder;
1822
1991
  };
1823
1992
  /**
1824
1993
  * What came of an order being created: the orderbook's answer, or `unposted` for
@@ -1876,6 +2045,11 @@ declare class LimitOrderService {
1876
2045
  private readonly startingNonce;
1877
2046
  /** The configured ERC-4626 vaults, when the operator runs any. */
1878
2047
  private readonly vaultBalances?;
2048
+ /**
2049
+ * Whether the filler only watches a chain (`--watch-only`, or `watchOnly` in the config,
2050
+ * globally or for that chain). Live: the operator can flip it while running.
2051
+ */
2052
+ private readonly isWatchOnly?;
1879
2053
  private logger;
1880
2054
  private cachedLimits?;
1881
2055
  private onEvent?;
@@ -1885,7 +2059,12 @@ declare class LimitOrderService {
1885
2059
  /** Where a new order's nonce starts; tests pin it, production draws it at random. */
1886
2060
  startingNonce?: () => bigint,
1887
2061
  /** The configured ERC-4626 vaults, when the operator runs any. */
1888
- vaultBalances?: VaultHoldings | undefined);
2062
+ vaultBalances?: VaultHoldings | undefined,
2063
+ /**
2064
+ * Whether the filler only watches a chain (`--watch-only`, or `watchOnly` in the config,
2065
+ * globally or for that chain). Live: the operator can flip it while running.
2066
+ */
2067
+ isWatchOnly?: ((chain: string) => boolean) | undefined);
1889
2068
  /**
1890
2069
  * Attaches the listener fill-driven changes are reported to. `Simplex` does
1891
2070
  * this after boot, the same way the filler is handed the service itself.
@@ -1970,7 +2149,7 @@ declare class LimitOrderService {
1970
2149
  * balance between them. Whichever order fills first draws the inventory down,
1971
2150
  * and the rest are cut to what is left. Netting them here would refuse the
1972
2151
  * 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.
2152
+ * whole position.
1974
2153
  */
1975
2154
  private assertWalletCanPay;
1976
2155
  /**
@@ -1981,28 +2160,19 @@ declare class LimitOrderService {
1981
2160
  * filler might have managed rather than accepting one it could not.
1982
2161
  */
1983
2162
  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
2163
  /**
1998
2164
  * Puts an order that has already been worked down back on the book, or closes
1999
2165
  * it when what is left is under the orderbook's dust floor for what it pays.
2000
2166
  *
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.
2167
+ * The order is moved to `resizing` before the orderbook is touched, so a crash
2168
+ * mid-way leaves a row that says what it was doing rather than one that silently
2169
+ * advertises output it has already paid. The old entry is cancelled before the
2170
+ * new one is posted: two live entries for one liability would advertise the same
2171
+ * output twice, and the gap between the two calls is at most one request.
2004
2172
  */
2005
2173
  resize(remaining: LimitOrder, delivered: bigint): Promise<LimitOrder | null>;
2174
+ /** Takes an order that has been worked down past the dust floor off the book and closes the row. */
2175
+ private closeFilled;
2006
2176
  /**
2007
2177
  * Cancels the current posting and puts the order back at its present size.
2008
2178
  *
@@ -2011,6 +2181,13 @@ declare class LimitOrderService {
2011
2181
  * thing: whatever the order has left, live on the book again.
2012
2182
  */
2013
2183
  repost(order: LimitOrder | null): Promise<LimitOrder | null>;
2184
+ /**
2185
+ * The order at the size the chain's tally allows, ready to post: lowered in the store
2186
+ * when the tally leaves it less than it holds, and closed when what is left is under
2187
+ * the dust floor (answering null). A tally that cannot be read leaves the size alone,
2188
+ * and the periodic tally pass follows up.
2189
+ */
2190
+ private lowerBeforePosting;
2014
2191
  /** A listener that throws is the listener's problem, not the fill's. */
2015
2192
  private report;
2016
2193
  /**
@@ -2042,9 +2219,10 @@ declare class LimitOrderService {
2042
2219
  *
2043
2220
  * The row is read again and claimed out of `open` first: a fill can have worked
2044
2221
  * 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
2222
+ * write is the one to follow. A fill that lands once the claim is taken cannot
2223
+ * sync the row, which is `resizing`; the repost lowers the order to the chain's
2224
+ * tally before it posts, and `resize` waits for this to finish. The EntryPoint
2225
+ * is only written with a posting that landed, so a pass cut short anywhere leaves a row the next pass still
2048
2226
  * reads as stale, or as having no entry, and finishes the job.
2049
2227
  */
2050
2228
  private resign;
@@ -2054,15 +2232,40 @@ declare class LimitOrderService {
2054
2232
  * pay for it, and puts the order back on the book at that size.
2055
2233
  *
2056
2234
  * 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.
2235
+ * that would take the tally past the order's size. Settlement brings an order
2236
+ * down to its tally after each fill; this pass catches what that missed: a sync
2237
+ * that failed, a chain read that did not yet show the fill, a store restored
2238
+ * from an older copy. A tally behind the store changes nothing: `remaining` is
2239
+ * never raised.
2061
2240
  *
2062
2241
  * Answers how many orders were lowered.
2063
2242
  */
2064
2243
  reconcileTallies(): Promise<number>;
2065
- /** The order as the store now holds it when its tally lowered it, null when it was left alone. */
2244
+ /**
2245
+ * Brings one order's `remaining` down to its on-chain tally and puts it back on
2246
+ * the book at that size, or closes it under the dust floor: what
2247
+ * {@link reconcileTallies} does for every open order.
2248
+ *
2249
+ * The tally is read at `blockNumber` when given: a fill passes the block it landed
2250
+ * in, so an endpoint lagging behind it cannot answer with the tally from before the
2251
+ * fill. The periodic pass reads the latest block.
2252
+ *
2253
+ * The read falls back to the latest block when the pinned one fails.
2254
+ *
2255
+ * Answers whether the order was lowered. False when it is not `open` (also when a
2256
+ * 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
2257
+ * fill and a tally that cannot be read (logged by the read). Throws when the chain
2258
+ * cannot be read for the token's decimals, or the store or orderbook fail;
2259
+ * settlement catches that and the periodic pass follows up.
2260
+ */
2261
+ syncToChain(id: string, blockNumber?: bigint): Promise<boolean>;
2262
+ private lowerToTally;
2263
+ /**
2264
+ * The order as the store now holds it when its tally lowered it, null when it was left alone.
2265
+ *
2266
+ * `only` is the statuses the row may hold for the clamp to apply, so a row that moved to
2267
+ * another between the caller's read and this write is not touched.
2268
+ */
2066
2269
  private clampToTally;
2067
2270
  /**
2068
2271
  * Withdraws every order that has outlived the operator's own `expiresAt`.
@@ -2074,7 +2277,13 @@ declare class LimitOrderService {
2074
2277
  * reaches its TTL.
2075
2278
  */
2076
2279
  expireStale(now?: Date): Promise<number>;
2077
- /** Takes an expired order off the book and closes the row. */
2280
+ /**
2281
+ * Takes an expired order off the book and closes the row, and reports it so the bids
2282
+ * it priced, which would otherwise still pay out at its price, are retracted.
2283
+ *
2284
+ * Guarded like every other posting write: an order the operator cancelled, or a fill
2285
+ * closed, while the entry was coming down keeps that status and is not reported here.
2286
+ */
2078
2287
  private retire;
2079
2288
  /** Every order that has a posting on the book, or should have one. */
2080
2289
  private live;
@@ -2137,20 +2346,55 @@ declare class IntentFiller {
2137
2346
  * built from this same store, so it cannot exist before the filler does.
2138
2347
  */
2139
2348
  private limitOrderService?;
2349
+ /**
2350
+ * Set after construction, like the service: the orderbook every bid is posted to, beside
2351
+ * Hyperbridge. Absent only in tests that bid on Hyperbridge alone.
2352
+ */
2353
+ private orderbookClient?;
2354
+ /**
2355
+ * Whether the Hyperbridge account can pay for a bid, read once the connection is up. A bid
2356
+ * the account cannot pay for goes to the orderbook alone.
2357
+ */
2358
+ private bridgeBalance?;
2140
2359
  private retractionQueue;
2141
2360
  /**
2142
2361
  * 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.
2362
+ * one order can land in one scan, and each settlement names its bid from the fills
2363
+ * the previous ones recorded: run side by side, two of them would name the same bid.
2146
2364
  */
2147
2365
  private settlementQueue;
2148
2366
  private paused;
2149
2367
  private stopping;
2150
2368
  private pendingRetractions;
2369
+ /**
2370
+ * Orders seen completed on chain, by us or a rival, lower-cased. The bid loop reads it
2371
+ * before each placement, because a bid placed on a completed order can only revert with
2372
+ * `Filled()` and still pays the placement fee and locks a deposit. Capped at
2373
+ * {@link MAX_COMPLETED_ORDERS}, oldest first.
2374
+ */
2375
+ private completedOrders;
2151
2376
  private rebalancingInterval?;
2152
2377
  private initialRebalanceTimer?;
2153
2378
  private activeRebalances;
2379
+ /**
2380
+ * Chains whose EIP-7702 delegation to the SolverAccount failed at boot.
2381
+ *
2382
+ * The bundler's estimate injects the SolverAccount code by state override, so an
2383
+ * undelegated EOA's bid passes estimation and pays the Hyperbridge bid fees and
2384
+ * deposit for a fill that can never execute. Orders for these chains are skipped
2385
+ * at intake, and a later successful delegation (`enableFills`) clears the entry.
2386
+ *
2387
+ * A mark is a belief about the chain, not proof: a delegation transaction that
2388
+ * mines after its receipt timeout, or a `getCode` that failed, leaves the wallet
2389
+ * delegated on chain while the chain stays marked. The dashboard cannot clear it
2390
+ * (its switch reads the chain and shows "Fills enabled"), so intake re-reads the
2391
+ * chain at most once per `DELEGATION_RECHECK_MS` and drops a stale mark.
2392
+ */
2393
+ private undelegatedChains;
2394
+ /** When each marked chain was last compared with the chain, by `Date.now()`. */
2395
+ private delegationCheckedAt;
2396
+ /** The re-check running now for a chain, so concurrent orders await its answer. */
2397
+ private delegationRechecks;
2154
2398
  private retractionSweepInterval?;
2155
2399
  private hyperbridge;
2156
2400
  private hyperbridgeEndpoint?;
@@ -2176,6 +2420,11 @@ declare class IntentFiller {
2176
2420
  */
2177
2421
  /** Hands the filler the service that keeps the orderbook in step with a fill. */
2178
2422
  setLimitOrderService(service: LimitOrderService): void;
2423
+ /**
2424
+ * Hands the filler the orderbook its bids are posted to. Every bid goes there; it goes to
2425
+ * Hyperbridge as well when the account holds BRIDGE for the deposit.
2426
+ */
2427
+ setOrderbook(client: OrderbookClient): void;
2179
2428
  /**
2180
2429
  * Brings the limit orders' `remaining` in line with their on-chain tallies.
2181
2430
  *
@@ -2198,6 +2447,24 @@ declare class IntentFiller {
2198
2447
  initialize(): Promise<void>;
2199
2448
  /** Chains that both fill and have solver selection active, as state machine ids. */
2200
2449
  private solverSelectionChains;
2450
+ private delegation;
2451
+ /**
2452
+ * Whether a marked chain is in fact delegated, and clears the mark when it is.
2453
+ *
2454
+ * Read at most once per `DELEGATION_RECHECK_MS` per chain. Orders arriving while a
2455
+ * read is in flight await its answer rather than being skipped on the old mark: the
2456
+ * read can be the one that clears it. A read that fails counts as not delegated,
2457
+ * the same as at boot.
2458
+ */
2459
+ private recheckDelegation;
2460
+ /**
2461
+ * Marks a chain undelegated when the wallet is not delegated there. For a chain
2462
+ * that was watch-only at boot: boot skips those, so delegation was never tried or
2463
+ * recorded, and turning watch-only off must not open a chain nothing can fill on.
2464
+ * A chain without solver selection needs no delegation and is left alone. The mark
2465
+ * clears through intake's re-check or `enableFills`.
2466
+ */
2467
+ guardDelegation(chainId: number): Promise<void>;
2201
2468
  /**
2202
2469
  * Delegates the filler EOA via EIP-7702 and tops up EntryPoint deposits.
2203
2470
  *
@@ -2268,9 +2535,25 @@ declare class IntentFiller {
2268
2535
  */
2269
2536
  private checkAndRebalance;
2270
2537
  private startRetractionSweep;
2271
- /** Enqueues retraction for every due bid; returns how many were queued. */
2538
+ /** Enqueues retraction for every due bid, one pass per commitment; returns how many bids were due. */
2272
2539
  private sweepExpiredBids;
2273
2540
  stop(): Promise<void>;
2541
+ /**
2542
+ * Whether the source gateway still holds escrow for every leg of the order:
2543
+ * `"held"`, `"missing"` for a read that came back empty, or `"unverifiable"` when
2544
+ * the read itself failed. The two failures are kept apart because they mean
2545
+ * different things to an operator: a confirmed zero is a phantom or reorged
2546
+ * commitment, while an unreadable escrow is an RPC fault that says nothing about
2547
+ * the order.
2548
+ *
2549
+ * With `quorum`, every leg is read in one `readWithQuorum` call so each
2550
+ * endpoint's vote is the whole escrow vector — an endpoint that disagrees on any
2551
+ * leg fails to join the agreeing group. That is the read used right before
2552
+ * bidding, where one endpoint alone should not vouch for a commitment a source
2553
+ * reorg may have rewritten; a quorum of the endpoints that answer must. Without
2554
+ * it, the legs are read on the single source client, as the pre-bid check has
2555
+ * always done.
2556
+ */
2274
2557
  private verifyOrderOnSource;
2275
2558
  private handleNewOrder;
2276
2559
  private evaluateOrder;
@@ -2298,12 +2581,17 @@ declare class IntentFiller {
2298
2581
  */
2299
2582
  private feeCheckWaived;
2300
2583
  private executeOrder;
2584
+ /**
2585
+ * The venues a bid goes to. The orderbook always; Hyperbridge when there is a connection and
2586
+ * the account holds BRIDGE for the deposit, which the gate decides per bid.
2587
+ */
2588
+ private bidVenues;
2301
2589
  private handleOrderFilledOnChain;
2302
2590
  /**
2303
2591
  * A rival completed an order we bid on. `_filled[commitment]` is now set, so
2304
2592
  * 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.
2593
+ * returns their Hyperbridge deposits now rather than when the stale-bid sweep
2594
+ * reaches them.
2307
2595
  *
2308
2596
  * Only a complete fill closes the order. After a rival's partial fill our bid
2309
2597
  * may still fill the rest, so it is left standing.
@@ -2313,68 +2601,138 @@ declare class IntentFiller {
2313
2601
  */
2314
2602
  private handleRivalCompletion;
2315
2603
  /**
2316
- * Turns the hold a filled bid was carrying into an actual draw-down.
2604
+ * Settles a fill of ours against the limit orders that priced our bids on its order.
2317
2605
  *
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.
2606
+ * The fill is attributed to one of those bids and recorded against that bid's
2607
+ * limit order, for its history and profit. Then every limit order that priced one
2608
+ * of our bids on the order is brought down to its on-chain tally, which is what
2609
+ * counted the fill: `remaining` follows the chain, not arithmetic here. A partial
2610
+ * fill and a completing one take the same path.
2322
2611
  *
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.
2612
+ * Nothing here throws for a sync that failed or a chain that does not show the fill
2613
+ * yet; the periodic tally reconciliation lowers the order instead.
2327
2614
  */
2615
+ private settleFilledLimitOrder;
2328
2616
  /**
2329
- * Takes a hold on every limit order the payout draws on, or none at all.
2617
+ * Records the fill against the limit order of the bid it executed.
2618
+ *
2619
+ * A bid that failed outright cannot have executed, and a bid executes at most once,
2620
+ * so one already recorded against this order is passed over. A retracted bid is not:
2621
+ * retraction takes it out of discovery but does not stop a placer that already fetched
2622
+ * it, which can execute it until its `validUntil`. A retracted bid placed longer before
2623
+ * the fill than its validity window cannot have executed and is passed over; the window is
2624
+ * counted back from when the fill happened, not from when it is settled, which can be long
2625
+ * after behind the settlement queue (see `fillTimeMs`). The
2626
+ * same fill delivered twice, which a scanner replaying from a cursor can do, is recorded
2627
+ * once: a fill is named by its transaction hash and log index, since one transaction can
2628
+ * carry several of our fills.
2330
2629
  *
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.
2630
+ * Deduplication is weaker where those are missing. A fill with no transaction hash
2631
+ * cannot be named, and a bid row with no `bid` identifier cannot be told from the
2632
+ * others on its swap, so a replay of either may be recorded again. Only rows from
2633
+ * before bids carried an identifier and logs that carried no hash are affected.
2634
+ *
2635
+ * A multi-leg swap can be priced by limit orders that pay different tokens, so
2636
+ * the delivery is read per limit order and only bids whose order pays a token the
2637
+ * fill delivered are candidates. The fill is attributed by the escrow it released
2638
+ * (see `executedPricing`): a retracted bid at a better rate must not take the fill from a
2639
+ * live bid whose take covers that escrow, so retracted bids are attributed only when no
2640
+ * live bid could have been it. A live bid the escrow rules out does not hide a retracted
2641
+ * one that covers it.
2335
2642
  */
2336
- private holdAll;
2337
- private releaseAll;
2338
- private settleFilledLimitOrder;
2643
+ private recordExecutedFill;
2644
+ /**
2645
+ * Retracts the bids of a limit order the fill left closed (filled, cancelled or expired),
2646
+ * which can no longer pay out at its price. While the order is live its other bids are
2647
+ * left to the chain: `debitOrder` refuses a fill past its budget, and the gas of such a
2648
+ * revert is the accepted cost of not holding anything locally. Never throws.
2649
+ */
2650
+ private retractBidsOfClosedLimitOrder;
2651
+ /**
2652
+ * How long after it is stored a retracted bid may still execute, in milliseconds:
2653
+ * `bidValiditySeconds` plus `BID_DISCOVERY_PAD_SECONDS`, the window the bid is signed for,
2654
+ * counted from the row (written after the head that window was counted from was read),
2655
+ * plus `BID_PERMIT_SKEW_SECONDS`.
2656
+ *
2657
+ * The skew is the same execution tail the bid's Permit2 deadline allows: a bid can execute
2658
+ * until its permit deadline, which is this window plus that skew. `validUntil` is block-based
2659
+ * and the window is converted to blocks at the fastest block time seen, so a slower chain
2660
+ * keeps a bid executable longer in wall-clock time, and the fill is seen only after its block
2661
+ * is scanned.
2662
+ */
2663
+ private retractedBidWindowMs;
2664
+ /**
2665
+ * When a fill happened, in milliseconds: the timestamp of the block it landed in.
2666
+ *
2667
+ * The event's arrival time is only a stand-in. After downtime or a scanner catch-up a
2668
+ * fill arrives long after it landed, and measuring a retracted bid's window from the
2669
+ * arrival would put every such bid outside it. The arrival time is used when the block
2670
+ * is unknown (`undefined`, or the `0n` a scanner substitutes) or cannot be read.
2671
+ */
2672
+ private fillTimeMs;
2339
2673
  /**
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.
2674
+ * Brings one limit order down to its on-chain tally after a fill, logging rather than throwing.
2675
+ * The tally is read at the fill's block, so a lagging endpoint cannot return the tally from before it.
2345
2676
  */
2346
- private claimExecutedBid;
2677
+ private syncLimitOrder;
2347
2678
  /**
2348
2679
  * What this fill delivered of the token the limit order pays, or null when the
2349
2680
  * fill names none of it.
2350
2681
  */
2351
2682
  private deliveredAgainst;
2352
2683
  /**
2353
- * Gives back whatever this bid still holds against its limit order.
2684
+ * Whether a limit order can still stand behind a bid at its price: `open`, or `resizing`
2685
+ * while a repost holds it. False once it is filled, cancelled, expired or rejected, or is
2686
+ * gone. Undefined when the store cannot be read, which a caller placing a bid treats as
2687
+ * unconfirmed and one retracting treats as nothing to do.
2688
+ */
2689
+ private limitOrderStands;
2690
+ private markCompleted;
2691
+ private hasCompleted;
2692
+ /**
2693
+ * Retracts the live bids a limit order priced, on every commitment they sit on.
2694
+ *
2695
+ * Called once the order can no longer stand behind them: the operator cancelled it, it
2696
+ * expired, or a fill left it filled or closed. Each such bid would still pay out at a
2697
+ * price the order no longer offers. Only the rows this order priced are retracted, so
2698
+ * another limit order's bid on the same swap order keeps standing, and a bid a pass
2699
+ * cannot close is flagged `dead` by row only.
2700
+ *
2701
+ * Retraction does not make the bids unexecutable: a placer that already fetched one can
2702
+ * execute it until its `validUntil`. A bid that does is settled by provenance like any
2703
+ * other fill, and the order's budget stays on chain. This only returns the deposit and
2704
+ * takes the bid out of discovery.
2354
2705
  *
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.
2706
+ * Never throws: it runs after a settlement, a cancel or an expiry that has already
2707
+ * happened, and a bid it misses is still retracted by the stale-bid sweep.
2358
2708
  */
2709
+ retractBidsForLimitOrder(limitOrderId: string): Promise<void>;
2359
2710
  /**
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.
2711
+ * Queues a retraction of this filler's live bids on a commitment: all of them, or only
2712
+ * those named by bid identifier (`bids`) or by row (`rows`).
2713
+ *
2714
+ * A retraction takes the bid out of Hyperbridge's discovery and returns its deposit. It
2715
+ * does not stop a placer that already fetched the bid, which can still execute it until
2716
+ * its `validUntil` (`bidValiditySeconds` plus the pad), so it narrows the exposure and
2717
+ * `bidValiditySeconds` remains the hard bound on it.
2362
2718
  */
2363
- private releaseReservation;
2364
2719
  private enqueueRetraction;
2365
2720
  /**
2366
- * Retracts every bid this filler placed on a commitment, by the identifiers recorded when each
2367
- * was placed.
2721
+ * Retracts the given bid rows on a commitment, by the identifiers recorded when each was
2722
+ * placed, and answers which rows each outcome closed.
2368
2723
  *
2369
2724
  * The identifiers come from the bid rows rather than from Hyperbridge's storage: a bid whose
2370
2725
  * `place_bid` is still in the pool is not in storage yet, and reading storage would find
2371
2726
  * nothing and abandon its deposit once it lands. Its retraction goes out after it from the same
2372
2727
  * account, so it lands after it and reclaims it.
2373
2728
  *
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.
2729
+ * A retraction that succeeded closes its rows with its hash. `BidNotFound` is terminal, not
2730
+ * retryable: bids only leave the pallet by retraction, so "no bid" means there is nothing
2731
+ * left to reclaim, and its rows close without one. A legacy row with no identifier closes the
2732
+ * same way. A failure leaves its rows open for a retry, and so does a retraction still in
2733
+ * Hyperbridge's pool: resubmitting can only bounce off it (1014), and if it lands the next
2734
+ * attempt's `BidNotFound` closes the row. A pooled one also stops the pass, since the
2735
+ * account's next extrinsic queues behind it; the bids after it stay open too.
2378
2736
  */
2379
2737
  private retractStoredBids;
2380
2738
  }
@@ -2384,8 +2742,6 @@ interface FillerRuntime {
2384
2742
  intentFiller: IntentFiller;
2385
2743
  balanceProvider: BalanceProvider;
2386
2744
  vaultVenue?: VaultFundingPlanner;
2387
- /** Self-halt visibility/reset for the trading engine (overfill protection). */
2388
- haltControls: HaltControl[];
2389
2745
  /** Live order-activity bridge; emits `event` per stored row. */
2390
2746
  activity: ActivityRecorder;
2391
2747
  /** The persistence backend this filler was started with. */
@@ -2509,9 +2865,14 @@ interface SimplexOptions {
2509
2865
  }
2510
2866
  /** Events a running filler emits. */
2511
2867
  interface SimplexEvents {
2868
+ /**
2869
+ * `blockHash` is the block the scanner read the placement from; a cross-chain
2870
+ * order is only filled while a quorum still places its transaction there.
2871
+ */
2512
2872
  "order:detected": {
2513
2873
  order: Order;
2514
2874
  transactionHash: string;
2875
+ blockHash: string;
2515
2876
  };
2516
2877
  "order:skipped": {
2517
2878
  orderId: string;
@@ -2606,7 +2967,6 @@ interface ChainInput {
2606
2967
  }
2607
2968
  interface SimplexStatus {
2608
2969
  paused: boolean;
2609
- halted: boolean;
2610
2970
  /** The solver's EVM account — the same value as `simplex.address`. */
2611
2971
  address: HexString;
2612
2972
  startedAt: number;
@@ -2630,7 +2990,7 @@ declare class LimitOrderController {
2630
2990
  list(filter?: LimitOrderFilter): Promise<LimitOrder[]>;
2631
2991
  get(id: string): Promise<LimitOrder | null>;
2632
2992
  /**
2633
- * One limit order with its fills and the bids still drawing on it, newest first.
2993
+ * One limit order with its fills and the bids it priced, newest first.
2634
2994
  *
2635
2995
  * What makes `remaining` explicable: a size that shrank is the sum of the fills
2636
2996
  * behind it, and the operator can see which ones. Fills are recorded when they
@@ -2729,7 +3089,17 @@ declare class ChainController {
2729
3089
  * lacks the gateway fee token or the native the paymaster's one-time setup needs.
2730
3090
  */
2731
3091
  enableFills(chainId: number): Promise<void>;
2732
- /** A global boolean is left alone — expanding it per chain changes how the config validates. */
3092
+ /**
3093
+ * Writes watch-only edits back into the config. A global boolean is left alone —
3094
+ * expanding it per chain changes how the config validates.
3095
+ *
3096
+ * `changed` is the chain just added, removed or toggled, and its new value. It only
3097
+ * matters under the `--watch-only` override: there the in-memory snapshot marks every
3098
+ * chain, and writing it back would make one run permanent for every later run.
3099
+ * Instead only the edited chain is applied — its key deleted when it stops being
3100
+ * watch-only, kept as an explicit flag otherwise — so the override itself never lands
3101
+ * in the file and a later override-free run sees what it would have anyway.
3102
+ */
2733
3103
  private syncWatchOnlyToConfig;
2734
3104
  }
2735
3105
  /** ERC-4626 treasury vaults the filler sources fills from and sweeps into. */
@@ -2866,12 +3236,6 @@ declare class Simplex extends EventEmitter {
2866
3236
  * deposits still locked in the solver-selection pallet.
2867
3237
  */
2868
3238
  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
3239
  /** Restricts which order `user` addresses are processed. Omit to accept all. */
2876
3240
  setAllowlist(allowlist: AllowlistConfig | undefined): Promise<void>;
2877
3241
  setLogLevel(level: LogLevel): void;