@hyperbridge/simplex 0.17.0 → 0.18.0

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.
@@ -0,0 +1,2297 @@
1
+ import { ChainConfig, HexString, Order, ERC7821Call, IntentsCoprocessor } from '@hyperbridge/sdk';
2
+ import { PublicClient, Hash, AbiEvent, BlockNumber, BlockTag, GetLogsParameters, GetLogsReturnType, WalletClient, Transport, Chain } from 'viem';
3
+ import { Account } from 'viem/accounts';
4
+ import pino from 'pino';
5
+ import { ChannelCredentials } from '@grpc/grpc-js';
6
+ import Decimal from 'decimal.js';
7
+
8
+ declare const LOG_LEVELS: readonly ["trace", "debug", "info", "warn", "error"];
9
+ type LogLevel = (typeof LOG_LEVELS)[number];
10
+ /**
11
+ * Anything that accepts a line of text: `process.stdout`, a `fs` write stream, a
12
+ * socket, a `pino-pretty` transform, or an object literal that forwards into
13
+ * your own logger. Node's `Writable` satisfies it as-is.
14
+ */
15
+ interface LogSink {
16
+ write(line: string): void;
17
+ }
18
+ type LogFn = {
19
+ (obj: unknown, msg?: string, ...args: unknown[]): void;
20
+ (msg: string, ...args: unknown[]): void;
21
+ };
22
+ interface Logger {
23
+ trace: LogFn;
24
+ debug: LogFn;
25
+ info: LogFn;
26
+ warn: LogFn;
27
+ error: LogFn;
28
+ fatal: LogFn;
29
+ }
30
+ type LoggerOptions = {
31
+ module?: string;
32
+ };
33
+ /**
34
+ * An isolated logging destination: its own sinks, its own level, its own pino
35
+ * instance.
36
+ *
37
+ * Each `Simplex` owns one, so two fillers in a process log to their own sinks at
38
+ * their own verbosity and never cross-contaminate. Services receive the context
39
+ * their filler was started with and resolve module loggers from it.
40
+ */
41
+ declare class LoggerContext {
42
+ private level;
43
+ private readonly sinks;
44
+ private base;
45
+ private readonly children;
46
+ constructor(options?: {
47
+ level?: LogLevel;
48
+ sink?: LogSink;
49
+ });
50
+ /**
51
+ * Drops the memoized loggers so the next call rebuilds at the current level
52
+ * and sink set. Module loggers are wrappers rather than pino instances
53
+ * precisely so this reaches loggers that already exist — replacing the base
54
+ * logger would leave every service that had captured a child logging at the
55
+ * old level forever.
56
+ */
57
+ private invalidate;
58
+ private baseLogger;
59
+ private childFor;
60
+ /** Registers a destination and returns a function that removes it. */
61
+ addSink(sink: LogSink): () => void;
62
+ /** Sets verbosity, including for loggers already handed out. */
63
+ setLevel(level: LogLevel): void;
64
+ /** A logger tagged with `module`. Safe to hold; it resolves the current state per call. */
65
+ get(module?: string): Logger;
66
+ /** @internal — used by {@link ModuleLogger} to resolve per call. */
67
+ resolve(module?: string): pino.Logger;
68
+ }
69
+ /**
70
+ * Registers a destination on the process-wide context and returns a function
71
+ * that removes it.
72
+ *
73
+ * This is for the application layer. To capture a specific filler's output, pass
74
+ * `SimplexOptions.logger` instead — a filler's records go to its own context,
75
+ * not here.
76
+ */
77
+ declare function addLogSink(sink: LogSink): () => void;
78
+ /** Sets the level of the process-wide context. Does not affect running fillers. */
79
+ declare function configureLogger(level: LogLevel): void;
80
+ /** A logger on the process-wide context. Instance-scoped code resolves from its own {@link LoggerContext}. */
81
+ declare function getLogger(moduleOrOptions?: string | LoggerOptions): Logger;
82
+ /**
83
+ * Resolves a module logger from a context that may be absent.
84
+ *
85
+ * Services read their context off the `FillerConfigService` or
86
+ * `ChainClientManager` they were constructed with, and `bootFiller` always
87
+ * supplies real ones. Hand-built test doubles generally do not, so this falls
88
+ * back to the process context rather than forcing every mock to carry a logger.
89
+ */
90
+ declare function moduleLogger(context: LoggerContext | undefined, module: string): Logger;
91
+
92
+ interface UserProvidedChainConfig {
93
+ /** One or more RPC URLs. When multiple are provided, event scans use quorum consensus. */
94
+ rpcUrls: string[];
95
+ /** Accepted and ignored. Simplex fills through Hyperbridge's bundler for the chain. */
96
+ bundlerUrl?: string;
97
+ }
98
+ interface ResolvedChainConfig {
99
+ chainId: number;
100
+ /** One or more RPC URLs for this chain. When multiple are provided, event scans use quorum consensus. */
101
+ rpcUrls: string[];
102
+ /** Hyperbridge's bundler for the chain, or undefined where it runs none. */
103
+ bundlerUrl?: string;
104
+ }
105
+ interface GasFeeBumpConfig {
106
+ maxPriorityFeePerGasBumpPercent?: number;
107
+ maxFeePerGasBumpPercent?: number;
108
+ }
109
+ interface RebalancingConfig$1 {
110
+ triggerPercentage: number;
111
+ baseBalances: {
112
+ USDC?: Record<string, string>;
113
+ USDT?: Record<string, string>;
114
+ };
115
+ }
116
+ interface OverfillProtectionConfig {
117
+ /** Ceiling bps above user-requested output; filler clamps its computed output to this. Default 500 (5%). */
118
+ maxOverfillBps?: number;
119
+ /** Consecutive clamped evaluations before the strategy halts itself. Default 3. */
120
+ maxConsecutiveClamps?: number;
121
+ }
122
+ interface AllowlistConfig {
123
+ /** Hex addresses eligible across all chains. */
124
+ users?: string[];
125
+ /** Per-source-chain overrides, keyed by state machine id (e.g. "EVM-1"). Merged with the global list. */
126
+ bySource?: Record<string, string[]>;
127
+ }
128
+ interface FillerConfig {
129
+ maxConcurrentOrders: number;
130
+ logging?: LogLevel;
131
+ hyperbridgeWsUrl?: string;
132
+ substratePrivateKey?: string;
133
+ entryPointAddress?: string;
134
+ /**
135
+ * Optional gas fee bump configuration for UserOperation gas estimation.
136
+ * If not provided, defaults will be used (8% for priority fee, 10% for max fee).
137
+ */
138
+ gasFeeBump?: GasFeeBumpConfig;
139
+ rebalancing?: RebalancingConfig$1;
140
+ /**
141
+ * Target gas units the EntryPoint deposit should cover per chain.
142
+ * Defaults to 3,000,000 if not set.
143
+ */
144
+ targetGasUnits?: number;
145
+ /**
146
+ * Seconds a signed bid stays executable. Defaults to 300 (5 minutes).
147
+ * See `bidValiditySeconds` in filler-toml for why this bound matters.
148
+ */
149
+ bidValiditySeconds?: number;
150
+ /**
151
+ * Order size in USD below which `order.fees` must cover the execution cost.
152
+ * Defaults to 20. See `minOrderSizeUsd` in filler-toml.
153
+ */
154
+ minOrderSizeUsd?: number;
155
+ /**
156
+ * Overfill protection knobs. If omitted, defaults are used
157
+ * (maxOverfillBps=500, maxConsecutiveClamps=3).
158
+ */
159
+ overfillProtection?: OverfillProtectionConfig;
160
+ /**
161
+ * Restricts which order `user` addresses the filler processes. When omitted, all
162
+ * users are accepted. When present, only listed users (global ∪ per-source) are
163
+ * accepted; a chain whose merged set is empty rejects every order.
164
+ */
165
+ allowlist?: AllowlistConfig;
166
+ /**
167
+ * How often the block scanner polls each chain, in seconds. Defaults to 3.
168
+ * Each tick costs one `eth_blockNumber` plus one `eth_getLogs` per chain per
169
+ * endpoint, so raising it is the main lever for staying inside a
170
+ * rate-limited RPC's budget — at the cost of seeing orders that much later.
171
+ */
172
+ blockScanIntervalSeconds?: number;
173
+ }
174
+ /**
175
+ * Simplified configuration service for the filler that wraps ChainConfigService
176
+ * and only requires minimal user configuration (RPC URLs, private keys, etc.)
177
+ */
178
+ declare class FillerConfigService {
179
+ /**
180
+ * This filler's logging destination. Services resolve their module loggers
181
+ * from here rather than from the process-wide context, which is what keeps
182
+ * two fillers in one process from writing into each other's sinks.
183
+ */
184
+ readonly loggers: LoggerContext;
185
+ private chainConfigService;
186
+ private rpcOverrides;
187
+ private bundlerUrls;
188
+ private fillerConfig?;
189
+ /** Lowercased global allowlist users, or undefined when no global list is configured. */
190
+ private allowlistGlobal?;
191
+ /** Lowercased per-source allowlist users keyed by state machine id, or undefined when no per-source list is configured. */
192
+ private allowlistBySource?;
193
+ constructor(chainConfigs: ResolvedChainConfig[], fillerConfig?: FillerConfig, loggers?: LoggerContext);
194
+ getAllowlist(): AllowlistConfig | undefined;
195
+ /**
196
+ * Registers a chain at runtime. `getConfiguredChainIds` derives from the RPC
197
+ * override map, so this is what makes a chain "configured" — every address
198
+ * lookup already resolves through the SDK's chain registry, which knows the
199
+ * chain whether or not this filler was started with it.
200
+ *
201
+ * Rejects a chain that is already registered: silently replacing its
202
+ * endpoints would leave the event monitor scanning the old ones. Use
203
+ * {@link setRpcUrls} for that.
204
+ */
205
+ addChain(chain: ResolvedChainConfig): void;
206
+ /** Drops a chain from the configured set. */
207
+ removeChain(chainId: number): void;
208
+ /**
209
+ * Replaces a chain's RPC endpoints. Callers must invalidate the cached viem
210
+ * clients (`ChainClientManager.invalidate`) and rebuild the chain's scanner
211
+ * afterwards — a client bakes its transport in at construction, so this
212
+ * change is invisible to anything already holding one.
213
+ */
214
+ setRpcUrls(chainId: number, rpcUrls: string[]): void;
215
+ /** Replaces the rebalancing config at runtime; trigger checks read it live. */
216
+ setRebalancing(rebalancing: RebalancingConfig$1 | undefined): void;
217
+ /** Replaces the allowlist at runtime; takes effect on the next order. */
218
+ setAllowlist(allowlist: AllowlistConfig | undefined): void;
219
+ /**
220
+ * Whether an order from `user` on `chain` (a source state machine id, e.g. "EVM-1")
221
+ * may be processed. Returns true when no allowlist is configured. Otherwise the user
222
+ * must appear in the global list or the per-source override for that chain; an empty
223
+ * merged set rejects every order on that chain.
224
+ */
225
+ isUserAllowed(user: string, chain: string): boolean;
226
+ getChainConfig(chain: string): ChainConfig;
227
+ getIntentGatewayAddress(chain: string): `0x${string}`;
228
+ getHostAddress(chain: string): `0x${string}`;
229
+ getWrappedNativeAssetWithDecimals(chain: string): {
230
+ asset: HexString;
231
+ decimals: number;
232
+ };
233
+ getDaiAsset(chain: string): HexString;
234
+ getUsdtAsset(chain: string): HexString;
235
+ getUsdcAsset(chain: string): HexString;
236
+ getUsdcDecimals(chain: string): number;
237
+ getSimplexPaymasterAddress(chain: string): HexString | undefined;
238
+ getUsdtDecimals(chain: string): number;
239
+ /**
240
+ * Curated decimals for a token, looked up by address in the SDK's per-chain
241
+ * asset table. Returns `undefined` for any token not in that table.
242
+ *
243
+ * This is the safety net for `ContractInteractionService.getTokenDecimals`
244
+ * when the on-chain `decimals()` read fails: the registry already carries the
245
+ * correct value for every supported asset (verified on-chain at curation
246
+ * time), so a failed read has no business guessing.
247
+ */
248
+ getAssetDecimalsByAddress(chain: string, address: HexString): number | undefined;
249
+ getChainId(chain: string): number;
250
+ getConsensusStateId(chain: string): string;
251
+ getHyperbridgeChainId(): number;
252
+ getHyperbridgeRpcUrl(): string;
253
+ getRpcUrl(chain: string): string;
254
+ /**
255
+ * Returns every user-configured RPC URL for a chain. When multiple URLs are present,
256
+ * consumers (e.g. the event monitor) run queries as a quorum — every provider must
257
+ * return the same result for the batch to succeed.
258
+ */
259
+ getRpcUrls(chain: string): string[];
260
+ private getChainIdFromStateMachineId;
261
+ getUniswapRouterV2Address(chain: string): HexString;
262
+ getUniswapV2FactoryAddress(chain: string): HexString;
263
+ getUniversalRouterAddress(chain: string): HexString;
264
+ getUniswapV3QuoterAddress(chain: string): HexString;
265
+ getUniswapV4QuoterAddress(chain: string): HexString;
266
+ getUniswapV4PositionManagerAddress(chain: string): HexString;
267
+ getUniswapV4PoolManagerAddress(chain: string): HexString;
268
+ getUniswapV4StateViewAddress(chain: string): HexString;
269
+ getPermit2Address(chain: string): HexString;
270
+ getCoingeckoId(chain: string): string | undefined;
271
+ getCNgnAsset(chain: string): HexString | undefined;
272
+ getAssetBySymbol(chain: string, symbol: string): HexString | undefined;
273
+ getCNgnDecimals(chain: string): number | undefined;
274
+ getExtAsset(chain: string): HexString | undefined;
275
+ getExtDecimals(chain: string): number | undefined;
276
+ getConfiguredChainIds(): number[];
277
+ getLoggingConfig(): LogLevel | undefined;
278
+ getHyperbridgeAddress(): string;
279
+ getHyperbridgeWsUrl(): string | undefined;
280
+ getSubstratePrivateKey(): string | undefined;
281
+ getEntryPointAddress(chain: string): HexString | undefined;
282
+ getSolverAccountContractAddress(chain: string): HexString | undefined;
283
+ getBundlerUrl(chain: string): string | undefined;
284
+ /**
285
+ * Get the maxPriorityFeePerGas bump percentage.
286
+ * @returns The configured percentage or undefined if not set (default 8% will be used)
287
+ */
288
+ getMaxPriorityFeePerGasBumpPercent(): number | undefined;
289
+ /**
290
+ * Get the maxFeePerGas bump percentage.
291
+ * @returns The configured percentage or undefined if not set (default 10% will be used)
292
+ */
293
+ getMaxFeePerGasBumpPercent(): number | undefined;
294
+ /**
295
+ * Get the full gas fee bump configuration.
296
+ * @returns The gas fee bump config or undefined if not set
297
+ */
298
+ getGasFeeBumpConfig(): GasFeeBumpConfig | undefined;
299
+ /**
300
+ * Get the LayerZero Endpoint ID for the chain
301
+ * Used for USDT0 cross-chain transfers via LayerZero OFT
302
+ */
303
+ getLayerZeroEid(chain: string): number | undefined;
304
+ /**
305
+ * Get the USDT0 OFT contract address for the chain
306
+ * On Ethereum: OFT Adapter (locks/unlocks USDT)
307
+ * On other chains: OFT contract (mints/burns USDT0)
308
+ */
309
+ getUsdt0OftAddress(chain: string): HexString | undefined;
310
+ /**
311
+ * Get rebalancing configuration
312
+ */
313
+ getRebalancingConfig(): RebalancingConfig$1 | undefined;
314
+ /**
315
+ * Get base balance for a specific chain and asset
316
+ * @param chainId Chain ID as number
317
+ * @param asset "USDC" or "USDT"
318
+ * @returns Base balance as Decimal, or undefined if not configured
319
+ */
320
+ getBaseBalance(chainId: number, asset: "USDC" | "USDT"): number | undefined;
321
+ /**
322
+ * Get trigger percentage for rebalancing
323
+ * @returns Trigger percentage (0-1), or undefined if not configured
324
+ */
325
+ getTriggerPercentage(): number | undefined;
326
+ /**
327
+ * Get target gas units for EntryPoint deposits.
328
+ * Defaults to 3,000,000 if not configured.
329
+ */
330
+ getTargetGasUnits(): bigint;
331
+ /**
332
+ * How long a signed bid remains executable, in seconds. Defaults to 300 (5 minutes).
333
+ *
334
+ * A bid is a firm price the placer may take up at a moment of their choosing; this is the
335
+ * only thing bounding that window, since the order deadline is placer-controlled and
336
+ * Hyperbridge-side retraction does not reach the destination chain.
337
+ */
338
+ getBidValiditySeconds(): number;
339
+ /**
340
+ * The order size, in USD, below which `order.fees` must cover the execution cost.
341
+ * Defaults to 20. An order at or above it is filled whatever fees it carries.
342
+ */
343
+ getMinOrderSizeUsd(): number;
344
+ /** Ceiling bps above user-requested output. Default 500 (5%). */
345
+ getMaxOverfillBps(): bigint;
346
+ /** Consecutive clamped evaluations before the strategy halts. Default 3. */
347
+ getMaxConsecutiveClamps(): number;
348
+ }
349
+
350
+ /**
351
+ * What one quorum call is allowed to spend.
352
+ *
353
+ * `deadlineMs` is the ceiling on the call itself: past it, the endpoints still
354
+ * outstanding are treated as failures, so they stop voting and the call is
355
+ * decided over the endpoints that answered, or fails with a QuorumError naming
356
+ * them. Early exit decides as soon as the endpoints still out could not change
357
+ * the outcome; the deadline decides when one of them never answers.
358
+ *
359
+ * `timeoutMs` bounds one endpoint's attempt, and there is only ever one: the
360
+ * transports are built with `retryCount: 0`, so an endpoint's whole
361
+ * contribution to a call is a single request. That is what keeps the two
362
+ * numbers independent — a task cannot approach `deadlineMs` by retrying inside
363
+ * itself, so a deadline that fires means something outside the budget went
364
+ * wrong rather than an endpoint using the budget as designed.
365
+ */
366
+ interface QuorumBudget {
367
+ timeoutMs: number;
368
+ deadlineMs: number;
369
+ }
370
+ /**
371
+ * Wraps multiple viem `PublicClient`s — one per configured RPC URL — and runs
372
+ * selected read paths (`getLogs`, `getBlockNumber`, `getTransactionConfirmations`)
373
+ * as a **BFT quorum**: a result is accepted only when `quorumThreshold(m)` of the
374
+ * `m` endpoints that answered agree on it.
375
+ *
376
+ * A provider that *answers with divergent data* is never special-cased away — it
377
+ * simply fails to join the agreeing group, which is what makes a lying or reorged
378
+ * endpoint detectable rather than authoritative.
379
+ *
380
+ * A provider that *fails* does not vote. A failed request says nothing about the
381
+ * chain, so it must not outvote the endpoints that agree: the threshold guards
382
+ * against endpoints that answer wrongly, and every answer still counts. Six
383
+ * endpoints with two failing decide over the other four, three of which must
384
+ * agree. The price, worth stating plainly, is that a call can be decided by fewer
385
+ * endpoints than the operator configured; at the limit, one endpoint answering
386
+ * while the rest fail decides alone. The voters are still exclusively the
387
+ * operator's own.
388
+ *
389
+ * The bench is the one state this class keeps, and {@link benchFor} decides who
390
+ * goes on it: a request-rate limit ({@link isSuspendableRateLimit}), a response
391
+ * that is not JSON ({@link isMalformedResponse}), or a head behind the quorum's
392
+ * ({@link isStaleHead}). All three share the property that earns it — the
393
+ * endpoint will keep failing the same way for a while — so a benched endpoint is
394
+ * not asked for its window: asking would spend its quota, or hold the call open
395
+ * for an answer already known to fail. When every endpoint is benched, all are
396
+ * queried again; there is nobody left to prefer. Every other failure is per
397
+ * call, and the endpoint is asked again next time.
398
+ *
399
+ * The constructor validates that URLs resolve to distinct hostnames so a "quorum"
400
+ * isn't secretly the same upstream in disguise.
401
+ */
402
+ declare class QuorumPublicClient {
403
+ readonly clients: PublicClient[];
404
+ readonly rpcUrls: string[];
405
+ /**
406
+ * The quorum bar when every endpoint answers: `quorumThreshold(n)` over the
407
+ * full set. Each call's bar is `quorumThreshold` over the endpoints that answer
408
+ * it, so this is the most it can be.
409
+ */
410
+ readonly threshold: number;
411
+ private readonly logger?;
412
+ /**
413
+ * Rate-limit bench state, shared across every instance and keyed by endpoint
414
+ * URL. The bench describes the ENDPOINT, not the wrapper: the scanner and the
415
+ * confirmation poller construct separate clients over the same URLs, and a
416
+ * throttle one of them observed is a throttle for both. Instance state would
417
+ * halve the relief — and evaporate whenever `setRpcUrls` rebuilds a client.
418
+ */
419
+ private static benchedUntil;
420
+ /** Test hook: forgets every bench. Production never calls it. */
421
+ static clearAllSuspensions(): void;
422
+ /** What this client's calls may spend; see {@link QuorumBudget}. */
423
+ private readonly deadlineMs;
424
+ constructor(chainId: number, rpcUrls: string[], loggers?: LoggerContext, budget?: QuorumBudget);
425
+ get size(): number;
426
+ /** Endpoints currently benched, by URL. See {@link benchFor} for the reasons. */
427
+ suspended(): string[];
428
+ /**
429
+ * The endpoints this call queries: everyone not benched. A benched endpoint is
430
+ * dropped outright: it answers nothing while throttled, so asking it could only
431
+ * cost a request. With every endpoint benched, everyone is queried again; there
432
+ * is nobody left to prefer.
433
+ */
434
+ private participants;
435
+ /**
436
+ * Records a task failure against its endpoint. Only the failures
437
+ * {@link benchFor} names carry state: everything else stays per-call, as the
438
+ * class doc promises.
439
+ * The bench is the one silent state transition this class has, so it warns —
440
+ * an operator whose bar just dropped from 2-of-2 to 1-of-1 should not need a
441
+ * QuorumError to find out.
442
+ */
443
+ private noteFailure;
444
+ /**
445
+ * Highest block a quorum of endpoints have all indexed: the highest B such
446
+ * that `threshold` endpoints report head ≥ B. Returns null when fewer than
447
+ * `threshold` endpoints have responded.
448
+ */
449
+ private quorumHead;
450
+ /**
451
+ * Settles per-client tasks incrementally and resolves the moment the
452
+ * evaluator can decide from the responses seen so far — the hot path never
453
+ * blocks on the slowest provider (a hung endpoint with a 30s timeout × 3
454
+ * retries would otherwise stall every confirmation poll). Stragglers settle
455
+ * out of band and are ignored. `tryDecide` returns undefined to keep
456
+ * waiting; once every task has settled, `finalize` must decide (it throws
457
+ * the QuorumError when the full response set still has no quorum).
458
+ *
459
+ * Both are handed the bar: the BFT threshold over the endpoints that have not
460
+ * failed, answered or still out. A failure lowers it rather than counting
461
+ * against it. Counting the pending endpoints keeps an early decision to a bar
462
+ * no lower than the full response set would get, so a vote it did not wait for
463
+ * could never have reversed it.
464
+ *
465
+ * `deadlineMs` bounds the other direction: when the bar is NOT reachable
466
+ * from the fast responders, the call used to wait out every straggler, and
467
+ * the caller's own retry loop multiplied that into minutes of a chain not
468
+ * being scanned at all, silently. At the deadline the outstanding endpoints
469
+ * are treated as failures — named in the QuorumError, so the operator sees
470
+ * which ones held the call up — and `finalize` decides over the endpoints
471
+ * that answered.
472
+ */
473
+ private settleUntilQuorum;
474
+ /**
475
+ * Highest block head backed by a quorum of the endpoints that answer. Throws
476
+ * {@link QuorumError} when none do.
477
+ */
478
+ getBlockNumber(): Promise<bigint>;
479
+ /**
480
+ * Confirmation count for a transaction under the quorum.
481
+ *
482
+ * Every endpoint is asked for the receipt and its head. A "receipt not found"
483
+ * answer is a valid **no** vote — the endpoint is responsive but does not see
484
+ * the transaction (not yet propagated, or reorged out) — so it counts toward
485
+ * responsiveness but joins no inclusion group. The transaction is confirmed
486
+ * only when a quorum agrees on the same `(blockHash, blockNumber)`; the depth
487
+ * is then the quorum head of that agreeing group. A minority still serving a
488
+ * reorged/fabricated receipt can never reach the quorum.
489
+ *
490
+ * Throws {@link QuorumError} when no inclusion reaches the quorum — including
491
+ * the ordinary window where the tx is not yet mined on enough endpoints.
492
+ */
493
+ getTransactionConfirmations({ hash }: {
494
+ hash: Hash;
495
+ }): Promise<bigint>;
496
+ /**
497
+ * Fetches logs from every endpoint in parallel and returns the result once a
498
+ * quorum agrees. Fails with {@link QuorumError} otherwise.
499
+ *
500
+ * Generics mirror `PublicClient.getLogs` so caller-side inference (event decoding,
501
+ * strict mode, pending vs mined) is preserved end-to-end.
502
+ */
503
+ getLogs<const TAbiEvent extends AbiEvent | undefined = undefined, const TAbiEvents extends readonly AbiEvent[] | readonly unknown[] | undefined = TAbiEvent extends AbiEvent ? [TAbiEvent] : undefined, TStrict extends boolean | undefined = undefined, TFromBlock extends BlockNumber | BlockTag | undefined = undefined, TToBlock extends BlockNumber | BlockTag | undefined = undefined>(params: GetLogsParameters<TAbiEvent, TAbiEvents, TStrict, TFromBlock, TToBlock>): Promise<GetLogsReturnType<TAbiEvent, TAbiEvents, TStrict, TFromBlock, TToBlock>>;
504
+ /**
505
+ * `skipped` is the participants() snapshot for THIS call, not the suspension
506
+ * state at throw time — a call that rides out its deadline can outlive a
507
+ * bench window, and the message must explain
508
+ * who was excluded when the call started, not who would be excluded now.
509
+ */
510
+ private describeResponders;
511
+ private formatFailures;
512
+ }
513
+
514
+ /**
515
+ * The signing contract a solver runs on, and the config shapes the bundled
516
+ * implementations take.
517
+ *
518
+ * `Signer` is the whole surface: implement it and Simplex signs with your
519
+ * backend. Nothing here is viem-shaped, so a consumer never has to match this
520
+ * package's viem version to satisfy it — `viemSigner` adapts a viem account into
521
+ * one, and simplex builds the account its wallet clients need from whatever it
522
+ * is given.
523
+ */
524
+
525
+ interface MpcVaultSignerConfig {
526
+ apiToken: string;
527
+ vaultUuid: string;
528
+ accountAddress: HexString;
529
+ callbackClientSignerPublicKey: string;
530
+ /**
531
+ * gRPC target address. Defaults to "api.mpcvault.com:443".
532
+ * Replaces the previous REST `baseUrl` field.
533
+ */
534
+ grpcTarget?: string;
535
+ /**
536
+ * Destination for signing lifecycle logs. Defaults to the process-wide
537
+ * context, which an embedded filler's `SimplexOptions.logger` never sees —
538
+ * pass your own to capture retries and failures in that setup.
539
+ */
540
+ logger?: Logger;
541
+ /** Channel credentials override for tests against a plaintext server. Defaults to TLS. */
542
+ credentials?: ChannelCredentials;
543
+ }
544
+ declare enum SignerType {
545
+ PrivateKey = "privateKey",
546
+ MpcVault = "mpcVault",
547
+ Turnkey = "turnkey",
548
+ SecretPhrase = "secretPhrase"
549
+ }
550
+ interface TurnkeySignerConfig {
551
+ organizationId: string;
552
+ apiPublicKey: string;
553
+ apiPrivateKey: string;
554
+ signWith: string;
555
+ }
556
+ interface PrivateKeySignerConfig {
557
+ key: HexString;
558
+ }
559
+ interface SecretPhraseSignerConfig {
560
+ /** BIP-39 mnemonic from the English wordlist. */
561
+ phrase: string;
562
+ /**
563
+ * Which wallet under the phrase is the hot wallet: the last component of
564
+ * `m/44'/60'/0'/0/i`. Defaults to 0.
565
+ */
566
+ accountIndex?: number;
567
+ }
568
+ /**
569
+ * The `[simplex.signer]` block of the TOML file the binary reads — a tagged
570
+ * union over the bundled backends. `createSigner` turns one into a {@link Signer};
571
+ * `Simplex.start` takes the `Signer`, not this.
572
+ */
573
+ type SignerConfig = ({
574
+ type: SignerType.PrivateKey;
575
+ } & PrivateKeySignerConfig) | ({
576
+ type: SignerType.MpcVault;
577
+ } & MpcVaultSignerConfig) | ({
578
+ type: SignerType.Turnkey;
579
+ } & TurnkeySignerConfig) | ({
580
+ type: SignerType.SecretPhrase;
581
+ } & SecretPhraseSignerConfig);
582
+ /**
583
+ * A secp256k1 signature in split form, which is how EIP-7702 and raw digests
584
+ * consume it: `r` and `s` as 32-byte 0x-hex, and `yParity` strictly 0 or 1.
585
+ * A backend that hands back the legacy `v` (27/28) should subtract 27 — the
586
+ * delegation path rejects anything else rather than letting viem mis-encode it.
587
+ */
588
+ interface Signature {
589
+ r: HexString;
590
+ s: HexString;
591
+ yParity: number;
592
+ }
593
+ /**
594
+ * An EIP-712 payload, as `eth_signTypedData_v4` defines it.
595
+ *
596
+ * `domain.chainId` is the only field a signing backend is likely to read for
597
+ * itself: the digest covers it, and backends that scope a signing request to a
598
+ * chain (MPCVault) take it from here rather than from a separate argument.
599
+ *
600
+ * **List `EIP712Domain` in `types` on any payload you build.** viem ignores it
601
+ * when hashing locally, so a payload without it looks correct against a private
602
+ * key. Backends that hash server-side from the JSON derive the domain type from
603
+ * that entry — MPCVault and Turnkey both do — and without it they sign a
604
+ * different digest than the one your verifier checks. The failure is quiet: the
605
+ * signature is well-formed and recovers to an address that is not yours.
606
+ * `CryptoUtils.packedUserOpTypedData` in the sdk sets it for this reason.
607
+ */
608
+ interface TypedDataPayload {
609
+ domain?: {
610
+ name?: string;
611
+ version?: string;
612
+ chainId?: number | bigint;
613
+ verifyingContract?: HexString;
614
+ salt?: HexString;
615
+ };
616
+ types: Record<string, readonly {
617
+ name: string;
618
+ type: string;
619
+ }[]>;
620
+ primaryType: string;
621
+ message: Record<string, unknown>;
622
+ }
623
+ /** EIP-7702 authorization tuple, signed, as a set-code transaction carries it. */
624
+ interface Eip7702Authorization$1 extends Signature {
625
+ chainId: number;
626
+ address: HexString;
627
+ nonce: number;
628
+ }
629
+ /**
630
+ * A transaction to sign, handed to backends that implement
631
+ * {@link Signer.signTransaction}. Field names follow the JSON-RPC transaction
632
+ * object; simplex only ever sends EIP-1559 transfers and EIP-7702 set-code
633
+ * transactions, so those are the fields modelled here.
634
+ */
635
+ interface SignerTransaction {
636
+ chainId: number;
637
+ type?: string;
638
+ to?: HexString;
639
+ value?: bigint;
640
+ data?: HexString;
641
+ nonce?: number;
642
+ gas?: bigint;
643
+ maxFeePerGas?: bigint;
644
+ maxPriorityFeePerGas?: bigint;
645
+ /** Present on EIP-7702 set-code transactions, and on nothing else. */
646
+ authorizationList?: Eip7702Authorization$1[];
647
+ }
648
+ /**
649
+ * Everything a solver asks of the key that owns its funds. Pass an
650
+ * implementation to `Simplex.start({ signer })`.
651
+ *
652
+ * Three operations, one identity. Each operation names what is being authorised
653
+ * — a typed-data payload, an EIP-7702 authorization, a transaction — rather than
654
+ * a digest, so a custody backend's policy engine can read what it is signing.
655
+ *
656
+ * A backend that only signs digests does not implement this by hand: pass its
657
+ * `sign(hash)` to {@link digestSigner}, which does the hashing and serialising
658
+ * and returns a `Signer`.
659
+ */
660
+ interface Signer {
661
+ /** The address every fill, bid and delegation is attributed to. */
662
+ readonly address: HexString;
663
+ /** Names the backend in logs — `"privateKey"`, `"turnkey"`, or whatever names yours. */
664
+ readonly mode: string;
665
+ /**
666
+ * EIP-712. Signs every bid UserOperation and every paymaster permit.
667
+ *
668
+ * Returns the 65-byte `r ‖ s ‖ v` signature as 0x-hex (132 characters), with
669
+ * `v` 27 or 28 — exactly what `eth_signTypedData_v4` and viem's
670
+ * `signTypedData` produce. On-chain validation recovers against this form.
671
+ */
672
+ signTypedData(typedData: TypedDataPayload): Promise<HexString>;
673
+ /**
674
+ * Signs an EIP-7702 authorization tuple — the delegation that makes this
675
+ * account a solver. Backends with a structured encoding for it (Turnkey) use
676
+ * that; others sign `keccak256(0x05 ‖ rlp([chainId, contractAddress, nonce]))`.
677
+ *
678
+ * Returns the SPLIT signature — see {@link Signature}: `yParity` must be 0 or
679
+ * 1, not the legacy `v`. This is the one method that does not return hex,
680
+ * because the authorization is embedded field-by-field in the transaction.
681
+ */
682
+ signAuthorization(auth: AuthorizationRequest): Promise<Signature>;
683
+ /**
684
+ * Signs and serialises a transaction. Backends that take transactions
685
+ * (MPCVault's vault API, Turnkey's transaction payloads) keep the transaction
686
+ * legible to their policy engine; others serialise it and sign the digest.
687
+ *
688
+ * Returns the WHOLE SIGNED TRANSACTION — typed-envelope RLP as
689
+ * `eth_sendRawTransaction` takes it (`0x02…` for EIP-1559, `0x04…` for
690
+ * set-code), not a bare signature. viem's `serializeTransaction(tx, signature)`
691
+ * produces it from a signed digest.
692
+ */
693
+ signTransaction(tx: SignerTransaction): Promise<HexString>;
694
+ }
695
+ /**
696
+ * A {@link Signer} backed by a key tree rather than a single key, so it can hand
697
+ * out signers for sibling wallets.
698
+ */
699
+ interface DerivingSigner extends Signer {
700
+ /** Index of the wallet this signer itself signs with. */
701
+ readonly accountIndex: number;
702
+ /** The signer for the wallet at `index`, which may be this signer's own. */
703
+ derive(index: number): Signer;
704
+ }
705
+ /** The tuple an EIP-7702 authorization signature covers. */
706
+ interface AuthorizationRequest {
707
+ chainId: number;
708
+ /** The contract the account delegates to. */
709
+ contractAddress: HexString;
710
+ nonce: number;
711
+ }
712
+
713
+ /**
714
+ * Manages chain clients for different operations
715
+ */
716
+ declare class ChainClientManager {
717
+ private signer;
718
+ /**
719
+ * The viem account every wallet client is built on, derived from the signer
720
+ * once. `Signer` is viem-free by design, so this is where the two meet.
721
+ */
722
+ private account;
723
+ private configService;
724
+ private clientFactory;
725
+ private quorumClients;
726
+ /** This filler's logging destination, relayed from the config service. */
727
+ get loggers(): LoggerContext;
728
+ constructor(configService: FillerConfigService, signer?: Signer);
729
+ /**
730
+ * Drops every cached client for a chain. Call after the chain's RPC URLs
731
+ * change (or when the chain is removed) so the next lookup rebuilds against
732
+ * the current endpoints — transports are baked into a client at construction.
733
+ *
734
+ * Callers that hold a client reference across the invalidation keep talking to
735
+ * the old endpoints; `EventMonitor` re-reads its quorum client per chain
736
+ * rebuild for exactly this reason.
737
+ */
738
+ invalidate(chain: string): void;
739
+ getPublicClient(chain: string): PublicClient;
740
+ /**
741
+ * Quorum client for consensus-critical reads (cross-chain confirmation
742
+ * counting; the scanner builds its own instance over its own chain list).
743
+ * Built from the operator's configured endpoints and cached per chain. The
744
+ * instances are separate but their rate-limit bench is shared state keyed by
745
+ * endpoint URL, so a throttle either observes benches the endpoint for both.
746
+ */
747
+ getQuorumClient(chain: string): QuorumPublicClient;
748
+ getWalletClient(chain: string): WalletClient<Transport, Chain, Account>;
749
+ getAccount(): Account;
750
+ getSigner(): Signer;
751
+ getClientsForOrder(order: Order): {
752
+ destClient: PublicClient;
753
+ sourceClient: PublicClient;
754
+ walletClient: WalletClient<Transport, Chain, Account>;
755
+ };
756
+ }
757
+
758
+ /**
759
+ * Raw EIP-7702 authorization, as produced by the delegation flow. Attached to the
760
+ * bundler UserOp so a not-yet-delegated EOA can be delegated in the same op.
761
+ */
762
+ interface Eip7702Authorization {
763
+ chainId: number;
764
+ address: HexString;
765
+ nonce: number;
766
+ r: HexString;
767
+ s: HexString;
768
+ yParity: number;
769
+ }
770
+ interface UserOpGasLimits {
771
+ verificationGasLimit: bigint;
772
+ callGasLimit: bigint;
773
+ preVerificationGas: bigint;
774
+ }
775
+ interface SponsoredUserOpRequest {
776
+ chain: string;
777
+ /** ERC-7821 batch (or "0x" for a no-op, e.g. delegation). */
778
+ callData: HexString;
779
+ /**
780
+ * Attach when the EOA still needs delegating in this op. Pass a factory rather
781
+ * than a pre-signed tuple: building paymaster data can send an approve tx from
782
+ * the authority EOA (the Simplex bootstrap approve to Permit2), and an authorization signed before
783
+ * that tx embeds a stale nonce the bundler will reject. The factory runs only
784
+ * after paymaster data is built.
785
+ */
786
+ eip7702Auth?: Eip7702Authorization | (() => Promise<Eip7702Authorization>);
787
+ /** EntryPoint nonce key. Defaults to 0. */
788
+ nonceKey?: bigint;
789
+ /**
790
+ * Explicit gas limits. Provide them to skip bundler estimation — required for
791
+ * the not-yet-delegated EIP-7702 case, where the account has no code to
792
+ * simulate so estimation is unreliable. When omitted, limits are estimated.
793
+ */
794
+ gas?: UserOpGasLimits;
795
+ /**
796
+ * Lets the paymaster fall back to an EIP-2612 permit when the fee token has no
797
+ * Permit2 allowance yet. Only a first-time delegation sets this — it carries the
798
+ * `approve(Permit2, max)` in its own callData, so the permit buys the one op that
799
+ * makes every later Permit2 op possible without native.
800
+ */
801
+ permitBootstrap?: boolean;
802
+ }
803
+ /**
804
+ * Submits self-initiated, paymaster-sponsored UserOperations through the bundler,
805
+ * so the solver pays gas in stablecoins rather than native token. Shared by the
806
+ * delegation setup and the vault sweep/redeem paths.
807
+ *
808
+ * Submission contract (so callers can safely fall back to a native tx without
809
+ * risking a double-execution):
810
+ * - Returns `null` when the op was **never submitted** — paymaster/bundler not
811
+ * configured, insufficient USDC, preparation failed (approve tx or signing), or
812
+ * the bundler rejected `eth_sendUserOperation` outright (it never entered the
813
+ * mempool). The caller may fall back to native.
814
+ * - Returns `{ txHash }` once a receipt is observed.
815
+ * - **Throws** only when the op **was submitted** but no receipt arrived. The
816
+ * caller must NOT fall back (the op may still land) — retry on the next cycle.
817
+ */
818
+ declare class UserOpSender {
819
+ private readonly clientManager;
820
+ private readonly configService;
821
+ private readonly signer;
822
+ private logger;
823
+ constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer);
824
+ /** True when a sponsored UserOp is even possible on this chain (paymaster + bundler configured). */
825
+ canSponsor(chain: string): boolean;
826
+ trySendSponsored(req: SponsoredUserOpRequest): Promise<{
827
+ txHash: HexString;
828
+ } | null>;
829
+ /**
830
+ * Estimates account gas limits via the bundler. The op is signed first so the
831
+ * estimation simulation passes the account's ECDSA validation. Falls back to
832
+ * generous constants when the bundler estimate is unavailable.
833
+ */
834
+ private estimateGas;
835
+ private buildSignedUserOp;
836
+ /**
837
+ * Prices the op for the rundler bundler, as the fill path does: its suggested fees, or the
838
+ * chain's gas price raised to the priority fee it requires where it suggests none.
839
+ */
840
+ private getGasPrice;
841
+ private sendBundlerRpc;
842
+ private waitForUserOpReceipt;
843
+ }
844
+
845
+ /**
846
+ * Service for managing EIP-7702 delegation of the filler's EOA to the SolverAccount contract.
847
+ * This enables the filler to participate in solver selection mode.
848
+ *
849
+ * When the Simplex paymaster is configured and the filler holds stablecoins already
850
+ * approved to Permit2, delegation is performed via a no-op UserOp sent through the
851
+ * bundler — the paymaster pays gas in stablecoins. Falls back to a direct type-0x04 tx
852
+ * if the bundler path is unavailable.
853
+ *
854
+ * The paymaster prefunds through Permit2, which needs a standing token approval to the
855
+ * Permit2 contract. On a chain the solver has never used, the delegation op installs
856
+ * that approval itself and pays for the privilege with an EIP-2612 permit, so a
857
+ * permit-capable fee token bootstraps with no native at all. A token without a permit
858
+ * (BNB Chain) still needs native dust once, for the type-0x04 tx that batches the
859
+ * approve in. Every op after that is native-free either way.
860
+ */
861
+ declare class DelegationService {
862
+ private clientManager;
863
+ private configService;
864
+ private signer;
865
+ private logger;
866
+ private readonly userOpSender;
867
+ constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer);
868
+ /**
869
+ * @param viaBundler When true, uses the current nonce (bundler submits the tx).
870
+ * When false, uses nonce+1 (EOA submits the type-0x04 tx itself).
871
+ */
872
+ private buildAuthorization;
873
+ private sendDelegationTransaction;
874
+ /**
875
+ * Best-effort resolution of a Permit2 approval to fold into a native delegation. Never
876
+ * throws: a resolution failure (RPC error, misconfig) yields null and the delegation
877
+ * proceeds as a plain self-call.
878
+ */
879
+ private resolvePendingPermit2Approval;
880
+ /**
881
+ * Checks if the filler's EOA is already delegated to the SolverAccount contract on a specific chain.
882
+ */
883
+ isDelegated(chain: string): Promise<boolean>;
884
+ /**
885
+ * Sets up EIP-7702 delegation via the bundler. Uses the Simplex paymaster when
886
+ * configured and the filler holds a sufficient stablecoin balance.
887
+ *
888
+ * The op is a no-op once the account has a Permit2 allowance. On a chain it has
889
+ * never used, and only when the fee token implements EIP-2612 (`permitCapable`),
890
+ * the op instead carries `approve(Permit2, max)` as its callData and asks the
891
+ * paymaster for PERMIT mode: the permit covers this op's gas, and the op installs
892
+ * the allowance every later op authorizes against. That is the whole zero-native
893
+ * bootstrap — one sponsored op, after which the account never needs a permit again.
894
+ */
895
+ private setupDelegationViaBundler;
896
+ /**
897
+ * Installs `approve(Permit2, max)` on an account that is ALREADY delegated, using the same
898
+ * permit-funded op the first-time delegation carries — minus the authorization, which it
899
+ * does not need.
900
+ *
901
+ * This exists because delegation and bootstrap are separate facts. An account delegated by
902
+ * an earlier release has no Permit2 allowance (that release charged EIP-2612 permits and
903
+ * never touched Permit2), and `setupDelegation` returns before the bootstrap once
904
+ * `isDelegated` holds. Leaving it there strands every upgraded solver on the native-funded
905
+ * approve path.
906
+ *
907
+ * Best-effort and never throws: on failure the first sponsored op still falls back to
908
+ * `sendFundedApprove`, which works for a solver holding native.
909
+ */
910
+ private ensurePermit2Allowance;
911
+ /**
912
+ * Sets up EIP-7702 delegation from the filler's EOA to the SolverAccount contract.
913
+ *
914
+ * A fee token with no Permit2 allowance yet cannot be charged through Permit2, so the
915
+ * approve has to be installed by the very first operation. Which carrier does that
916
+ * depends on the token:
917
+ *
918
+ * - EIP-2612 token: the bundler goes first and the op pays for itself with a permit
919
+ * while carrying the approve in its callData. No native is spent, so this is
920
+ * preferred even on an EOA that holds some.
921
+ * - No permit (BSC pegged stables): only a native type-0x04 tx can carry the approve,
922
+ * so that goes first when the EOA covers it.
923
+ *
924
+ * With nothing pending — the allowance is already in place — the bundler path goes
925
+ * first as usual. A plain direct tx is the last fallback in every case.
926
+ */
927
+ setupDelegation(chain: string): Promise<boolean>;
928
+ /** Whether the EOA can pay for one direct set-code tx at the current gas price. */
929
+ private nativeCoversDirectTx;
930
+ /**
931
+ * Sends one native EIP-7702 delegation tx (optionally batching a Permit2 approve) and
932
+ * waits for its receipt. Never throws: a revert or send error is logged and returns
933
+ * false, so the caller can fall back. Rebuilds the authorization each call, so a fresh
934
+ * nonce is used after a prior attempt consumed one.
935
+ */
936
+ private trySendDelegation;
937
+ /**
938
+ * Sets up delegation on the specified chains where solver selection is active.
939
+ */
940
+ setupDelegationOnChains(chains: string[]): Promise<{
941
+ success: boolean;
942
+ results: Record<string, boolean>;
943
+ }>;
944
+ /**
945
+ * Revokes delegation by delegating to the zero address.
946
+ */
947
+ revokeDelegation(chain: string): Promise<boolean>;
948
+ }
949
+
950
+ /** `[simplex.tunnel]` in the config file. */
951
+ interface TunnelConfig {
952
+ /** Off by default: enabling it makes the embedded SSH server reachable from the internet. */
953
+ enabled?: boolean;
954
+ /** `host[:port]`; port defaults to 443. */
955
+ relay?: string;
956
+ /** `SHA256:…` fingerprint of the relay host key. Without it the key seen on first contact is pinned. */
957
+ relayHostKey?: string;
958
+ }
959
+
960
+ /**
961
+ * A coordinate point on a curve
962
+ * @property amount - The input threshold (e.g., USD amount)
963
+ * @property value - The output value at this threshold (e.g., confirmations, bps)
964
+ */
965
+ interface CurvePoint {
966
+ amount: string;
967
+ value: number;
968
+ }
969
+ /**
970
+ * Configuration for a curve
971
+ */
972
+ interface CurveConfig {
973
+ points: CurvePoint[];
974
+ }
975
+ /**
976
+ * Manages confirmation block requirements per chain.
977
+ * Each chain has its own curve mapping order value to required confirmations.
978
+ */
979
+ /**
980
+ * Parses a per-chain config key. Every other table in the config writes chains
981
+ * as state machine ids ("EVM-1"), so both "EVM-1" and bare "1" are accepted;
982
+ * anything else returns null and callers MUST fail loudly — a silently
983
+ * discarded chain key has meant committing capital on a chain configured as
984
+ * watch-only, or hardening a confirmation curve that never applied.
985
+ */
986
+ declare function parseChainKey(key: string): number | null;
987
+ /** The canonical "EVM-<id>" state machine id `parseChainKey` accepts. */
988
+ declare function formatChainKey(chainId: number | string): string;
989
+ declare class ConfirmationPolicy {
990
+ private policies;
991
+ constructor(policyConfig: Record<string, CurveConfig>);
992
+ getConfirmationBlocks(chainId: number, amountUsd: Decimal): number;
993
+ /**
994
+ * Installs a curve for a chain added at runtime. Validates before it mutates,
995
+ * so a rejected curve leaves the policy set untouched — a chain that started
996
+ * scanning without a curve would drop every cross-chain order sourced on it
997
+ * with a per-order throw.
998
+ */
999
+ add(chainId: number, config: CurveConfig): void;
1000
+ /** Whether a chain has a confirmation curve. */
1001
+ has(chainId: number): boolean;
1002
+ remove(chainId: number): void;
1003
+ /**
1004
+ * Copies one chain's curve across from another policy set.
1005
+ *
1006
+ * Used when a chain is added at runtime and covered by a built-in default: the
1007
+ * defaults are only materialised inside a freshly constructed policy, so the
1008
+ * live one adopts the entry rather than re-deriving it.
1009
+ */
1010
+ adopt(chainId: number, from: ConfirmationPolicy): void;
1011
+ /**
1012
+ * Startup guard: every configured chain must have a confirmation curve.
1013
+ * Without this, a missing policy only surfaces as a per-order throw at
1014
+ * fill time — cross-chain orders sourced on that chain silently dropped.
1015
+ */
1016
+ assertCovers(chainIds: number[]): void;
1017
+ }
1018
+
1019
+ /**
1020
+ * A user-supplied asset entry in the `[assets]` TOML table: symbol → chain
1021
+ * state machine id → contract address.
1022
+ *
1023
+ * ```toml
1024
+ * [assets.BRZ]
1025
+ * "EVM-1" = "0x..."
1026
+ * "EVM-137" = "0x..."
1027
+ * ```
1028
+ */
1029
+ type AssetDefinition = Record<string, HexString>;
1030
+ /**
1031
+ * The subset of `FillerConfigService` used to resolve built-in symbols to
1032
+ * per-chain addresses from the SDK chain registry.
1033
+ */
1034
+ interface BuiltinAssetResolver {
1035
+ getUsdcAsset(chain: string): HexString;
1036
+ getUsdtAsset(chain: string): HexString;
1037
+ getDaiAsset(chain: string): HexString;
1038
+ getCNgnAsset(chain: string): HexString | undefined;
1039
+ /** Generic per-chain lookup into the SDK chain registry's asset table. */
1040
+ getAssetBySymbol(chain: string, symbol: string): HexString | undefined;
1041
+ }
1042
+ /**
1043
+ * Symbols pegged to 1 USD. They are pinned at $1 when an order's USD value is
1044
+ * derived from the limit orders' rates (see `usdFactorsFrom`), which is what
1045
+ * sizes confirmation depth. Trade pricing never uses this as a price.
1046
+ */
1047
+ declare const USD_STABLE_SYMBOLS: ReadonlySet<string>;
1048
+ /** Normalises a symbol for lookups: trimmed, uppercased. "cNGN" ≡ "CNGN". */
1049
+ declare function normalizeSymbol(symbol: string): string;
1050
+ /** Every symbol the registry ships without user configuration. */
1051
+ declare function registrySymbols(): string[];
1052
+ /** Whether `symbol` ships with the registry (built-in or curated). Pure — no chain resolution. */
1053
+ declare function isRegistrySymbol(symbol: string): boolean;
1054
+ /**
1055
+ * Validates the `[assets]` table; throws a descriptive error on the first
1056
+ * invalid entry. Side-effect free — safe to call at config-parse time and again
1057
+ * at registry construction.
1058
+ */
1059
+ declare function validateAssetDefinitions(assets: Record<string, AssetDefinition>): void;
1060
+ /**
1061
+ * Symbol → contract address registry: how a token symbol in a limit order or an
1062
+ * orderbook book becomes a contract on a chain.
1063
+ *
1064
+ * Resolution merges two layers, most specific winning:
1065
+ * 1. the user's `[assets]` table — an *escape hatch* for assets the registry
1066
+ * doesn't ship (or per-deployment overrides), never required for shipped
1067
+ * symbols;
1068
+ * 2. shipped symbols (USDC, USDT, DAI, CNGN, USDR, ZARP, EURC, XSGD, TRYB,
1069
+ * BRIDGE) resolved per chain from the SDK chain registry (`chain.ts`) —
1070
+ * the single source of truth shared with the rest of the SDK, so an
1071
+ * address correction there is never shadowed by a parallel table here.
1072
+ *
1073
+ * Address lookups are per `(symbol, chain)` — a chain where no layer knows the
1074
+ * asset simply doesn't trade it. The registry holds addresses only; prices come
1075
+ * from the operator's limit orders.
1076
+ */
1077
+ declare class AssetRegistry {
1078
+ private readonly resolver;
1079
+ private readonly userAssets;
1080
+ private readonly addressCache;
1081
+ constructor(resolver: BuiltinAssetResolver, userAssets?: Record<string, AssetDefinition>);
1082
+ /** Whether `symbol` is known (user-defined, curated, or built-in). */
1083
+ hasSymbol(symbol: string): boolean;
1084
+ /**
1085
+ * Registers new user-defined assets on the live registry (runtime market
1086
+ * adds). Existing symbols may not be redefined — an address swap under a
1087
+ * running market would silently repoint its fills.
1088
+ */
1089
+ addAssets(defs: Record<string, AssetDefinition>): void;
1090
+ /**
1091
+ * Contract address of `symbol` on `chain`, or `null` when the asset is not
1092
+ * deployed/known there. User `[assets]` addresses win over the SDK chain
1093
+ * registry, per chain.
1094
+ */
1095
+ getAddress(symbol: string, chain: string): HexString | null;
1096
+ /**
1097
+ * The symbol `address` is known by on `chain`, or null when nothing in the
1098
+ * registry resolves to it.
1099
+ *
1100
+ * Every symbol this registry can name is tried, user `[assets]` first so an
1101
+ * overridden address reports the name the operator gave it. Two symbols
1102
+ * resolving to one address would be a registry mistake; the first wins.
1103
+ */
1104
+ symbolFor(address: string, chain: string): string | null;
1105
+ }
1106
+
1107
+ interface PaymasterKeeperConfig {
1108
+ /** Chains to monitor. Defaults to every configured chain with a SimplexPaymaster. */
1109
+ chains?: string[];
1110
+ intervalMinutes?: number;
1111
+ /** Minimum accrued balance (in whole stablecoin units) worth recycling. */
1112
+ minSwapUsd?: number;
1113
+ }
1114
+
1115
+ interface ChainConfirmationPolicy {
1116
+ /**
1117
+ * Array of (amount, value) coordinates defining the confirmation curve.
1118
+ * value = number of confirmations at that order amount
1119
+ */
1120
+ points: Array<{
1121
+ amount: string;
1122
+ value: number;
1123
+ }>;
1124
+ }
1125
+ /**
1126
+ * TOML row for an ERC-4626 vault entry. `threshold` (absolute human units) is the
1127
+ * high-water mark that triggers a sweep down to `minBalance`; omit both for
1128
+ * withdraw-only sourcing.
1129
+ */
1130
+ interface VaultToml {
1131
+ chain: string;
1132
+ vault: HexString;
1133
+ threshold?: string;
1134
+ minBalance?: string;
1135
+ redeemOnShutdown?: boolean;
1136
+ }
1137
+ /** Top-level `[vault]` config: ERC-4626 treasury vaults, for withdraw sourcing and threshold sweeping. */
1138
+ interface VaultTomlConfig {
1139
+ vaults?: VaultToml[];
1140
+ sweepIntervalMs?: number;
1141
+ }
1142
+ interface QueueConfig {
1143
+ maxRechecks: number;
1144
+ recheckDelayMs: number;
1145
+ }
1146
+ interface RebalancingConfig {
1147
+ triggerPercentage: number;
1148
+ baseBalances: {
1149
+ USDC?: Record<string, string>;
1150
+ USDT?: Record<string, string>;
1151
+ };
1152
+ }
1153
+ interface BinanceConfig {
1154
+ apiKey: string;
1155
+ apiSecret: string;
1156
+ basePath?: string;
1157
+ timeout?: number;
1158
+ depositTimeoutMs?: number;
1159
+ pollIntervalMs?: number;
1160
+ withdrawTimeoutMs?: number;
1161
+ }
1162
+ interface FillerTomlConfig {
1163
+ /**
1164
+ * Optional asset-registry escape hatch: symbol → { chain → address }. Only
1165
+ * needed for assets the built-in registry does not ship, or to override a
1166
+ * shipped address for a deployment. Shipped symbols: USDC, USDT, DAI, CNGN
1167
+ * (SDK chain registry) + curated USDR, ZARP, EURC, XSGD, TRYB and BRIDGE.
1168
+ */
1169
+ assets?: Record<string, AssetDefinition>;
1170
+ /**
1171
+ * Per-chain confirmation policies for cross-chain orders, keyed by chain id.
1172
+ * Merged over built-in defaults (ETH, BSC, Polygon, Base, Arbitrum,
1173
+ * Unichain); every configured chain must be covered or startup fails. The
1174
+ * policy's amount axis is the order's USD value: USD stables count at $1 and
1175
+ * other tokens are valued through the open limit orders' rates. An order no
1176
+ * limit order connects to a dollar waits for the policy's maximum depth.
1177
+ */
1178
+ confirmationPolicies?: Record<string, ChainConfirmationPolicy>;
1179
+ simplex: {
1180
+ /** Orders evaluated at once. Defaults to 5. */
1181
+ maxConcurrentOrders?: number;
1182
+ /**
1183
+ * Accepted and ignored. Older configs and the wizard used to write it; the
1184
+ * engine never read it. Kept in the type so those configs still load.
1185
+ */
1186
+ queue?: QueueConfig;
1187
+ logging?: string;
1188
+ watchOnly?: boolean | Record<string, boolean>;
1189
+ substratePrivateKey: string;
1190
+ hyperbridgeWsUrl: string;
1191
+ /**
1192
+ * Hyperbridge indexer GraphQL endpoint, used to backfill order details on
1193
+ * activity rows recorded before they were captured. Defaults per network
1194
+ * (nexus for mainnet, gargantua for testnet).
1195
+ */
1196
+ indexerUrl?: string;
1197
+ /** Accepted and ignored. Contract addresses come from the SDK chain registry. */
1198
+ entryPointAddress?: string;
1199
+ /** Accepted and ignored. Contract addresses come from the SDK chain registry. */
1200
+ solverAccountContractAddress?: string;
1201
+ /** Target gas units for EntryPoint deposits per chain. Defaults to 3,000,000. */
1202
+ targetGasUnits?: number;
1203
+ /**
1204
+ * Block-scanner poll period per chain in seconds. Defaults to 3, and
1205
+ * fractional values are allowed (0.5 = twice a second). Each tick costs
1206
+ * one `eth_blockNumber` + one `eth_getLogs` per chain per endpoint, so
1207
+ * raising this is the lever for fitting inside a rate-limited RPC's
1208
+ * budget; the cost is seeing new orders that much later.
1209
+ */
1210
+ blockScanIntervalSeconds?: number;
1211
+ /** Gas fee bump (percentages added to base gasPrice). Defaults: priority=8%, max=10%. */
1212
+ gasFeeBump?: {
1213
+ maxPriorityFeePerGasBumpPercent?: number;
1214
+ maxFeePerGasBumpPercent?: number;
1215
+ };
1216
+ /**
1217
+ * Overfill protection knobs. Defaults: maxOverfillBps=500, maxConsecutiveClamps=3.
1218
+ * `maxOverfillBps` is warn-only: FXFiller logs a limit order whose offer exceeds
1219
+ * the swapper's ask by more than this, and clamps nothing. `maxConsecutiveClamps`
1220
+ * is accepted but dormant: the clamp that fed it was for Uniswap V4 venue pricing,
1221
+ * which is gone, so FXFiller never halts.
1222
+ */
1223
+ overfillProtection?: {
1224
+ maxOverfillBps?: number;
1225
+ maxConsecutiveClamps?: number;
1226
+ };
1227
+ /**
1228
+ * How long a signed bid stays executable, in seconds. Defaults to 300 (5 minutes).
1229
+ *
1230
+ * Written into `FillOptions.validUntil` and enforced by `fillOrder`, which reverts
1231
+ * `FillExpired` past it. This is how long the quoted price stands as a firm commitment:
1232
+ * the order's `deadline` is chosen by the placer with no ceiling, and retracting the bid
1233
+ * on Hyperbridge does not reach the destination chain, so without it a signed bid stays
1234
+ * executable indefinitely and is taken up only once the rate has moved against us.
1235
+ *
1236
+ * Configured in seconds but written on-chain in blocks, converted per destination chain.
1237
+ */
1238
+ bidValiditySeconds?: number;
1239
+ /**
1240
+ * The order size, in USD, below which `order.fees` must cover the execution cost
1241
+ * (fill gas plus, cross-chain, the relayer fee). Defaults to 20.
1242
+ *
1243
+ * An order worth at least this much is filled whatever fees it carries: the margin
1244
+ * in the limit order that prices it pays for the gas. On a smaller order that
1245
+ * margin is too thin to, so the fees have to. Set to 0 to never check fees. An
1246
+ * order nothing can put a dollar figure on is treated as below it.
1247
+ */
1248
+ minOrderSizeUsd?: number;
1249
+ /**
1250
+ * Remote access: an outbound SSH tunnel to a rendezvous relay so a phone's
1251
+ * SSH client can reach the local web UI. Off unless `enabled = true`; the
1252
+ * relay defaults to the hosted one. Devices are paired from the UI.
1253
+ */
1254
+ tunnel?: TunnelConfig;
1255
+ };
1256
+ chains: UserProvidedChainConfig[];
1257
+ rebalancing?: RebalancingConfig;
1258
+ binance?: BinanceConfig;
1259
+ /** Filler-wide vault config: stablecoin sourcing for fills + threshold sweeping. */
1260
+ vault?: VaultTomlConfig;
1261
+ /** Restricts order processing to listed user addresses. Omit to accept all users. */
1262
+ allowlist?: AllowlistConfig;
1263
+ /** SimplexPaymaster fee-recycling keeper (`paymaster-keeper` subcommand). */
1264
+ keeper?: PaymasterKeeperConfig;
1265
+ /** The HyperFX orderbook simplex posts its limit orders to. */
1266
+ orderbook?: OrderbookConfig;
1267
+ }
1268
+ /**
1269
+ * Where the operator's limit orders are advertised.
1270
+ *
1271
+ * The limit orders themselves are not configured here: they live in `bids.db`
1272
+ * and are created over the API, because they are inventory the operator opens
1273
+ * and closes while the filler runs rather than startup settings.
1274
+ */
1275
+ interface OrderbookConfig {
1276
+ /** GraphQL endpoint. */
1277
+ url: string;
1278
+ /**
1279
+ * How long a limit order lives, in seconds, and the TTL written into its posting,
1280
+ * when the create request names none. Defaults to 365 days; at least 900, the
1281
+ * orderbook's floor. The order and its posting expire together: there is one
1282
+ * clock, and nothing renews it.
1283
+ */
1284
+ defaultTtlSecs?: number;
1285
+ /** How often to reconcile local limit orders against the orderbook, in seconds. */
1286
+ reconcileIntervalSecs?: number;
1287
+ requestTimeoutMs?: number;
1288
+ }
1289
+ /**
1290
+ * The TOML file the binary reads: a {@link FillerTomlConfig} plus the
1291
+ * `[simplex.signer]` block, which is the CLI's way of naming a signing backend.
1292
+ *
1293
+ * The block is not part of the library's config — `Simplex.start` takes a
1294
+ * `Signer` instance — so it lives on this type and nowhere else. The binary
1295
+ * resolves it with `signerFromToml` and passes the parsed file straight through;
1296
+ * the extra key rides along untouched so the dashboard's config writer can put
1297
+ * it back in the file it came from.
1298
+ */
1299
+ interface FillerConfigFile extends FillerTomlConfig {
1300
+ simplex: FillerTomlConfig["simplex"] & {
1301
+ /** Omitted by watch-only configs, which sign nothing. */
1302
+ signer?: SignerConfig;
1303
+ };
1304
+ }
1305
+ /**
1306
+ * Boot-parity confirmation coverage: every configured chain id needs a
1307
+ * confirmation curve (built-in defaults or [confirmationPolicies]), or orders
1308
+ * sourced on it would be silently dropped. Boot and both wizard write gates
1309
+ * run the same construction; the gates know the chain ids the wizard selected,
1310
+ * boot the ids resolved from the RPCs.
1311
+ */
1312
+ declare function assertConfirmationCoverage(confirmationPolicies: FillerTomlConfig["confirmationPolicies"], chainIds: number[]): ConfirmationPolicy;
1313
+ /**
1314
+ * Validates raw TOML `[vault].vaults` entries. Pure and browser-safe — shared
1315
+ * by `validateConfig`, the vault-update endpoint and the funding planner.
1316
+ * Throws on missing/invalid required fields.
1317
+ */
1318
+ declare function validateVaultToml(vaults: {
1319
+ chain?: string;
1320
+ vault?: string;
1321
+ threshold?: string;
1322
+ minBalance?: string;
1323
+ redeemOnShutdown?: boolean;
1324
+ }[]): void;
1325
+ declare function validateConfig(config: FillerTomlConfig, cliWatchOnly?: boolean): void;
1326
+
1327
+ interface SendTokenParams {
1328
+ /** State machine id, e.g. "EVM-8453". */
1329
+ chain: string;
1330
+ /** "native" or an ERC-20 address. Configured vault shares are redeemed to the recipient instead of transferred. */
1331
+ token: string;
1332
+ /** Human-readable amount, parsed against the token's decimals. */
1333
+ amount: string;
1334
+ to: HexString;
1335
+ }
1336
+ interface SendTokenResult {
1337
+ txHash: HexString;
1338
+ sponsored: boolean;
1339
+ /** Set when vault holdings were redeemed as part of the send. */
1340
+ redeemed: boolean;
1341
+ }
1342
+ /**
1343
+ * Operator-initiated outbound transfers from the filler wallet. Uses the same
1344
+ * sponsored UserOp path as fills (gas paid by the paymaster) with a native-tx
1345
+ * fallback. When the wallet balance of an ERC-20 falls short and a configured
1346
+ * ERC-4626 vault holds it as underlying, the shortfall is withdrawn from the
1347
+ * vault in the same batch as the transfer. Sending a vault's share token
1348
+ * directly issues `redeem(shares, recipient, solver)` — shares only ever
1349
+ * leave as the underlying asset.
1350
+ */
1351
+ declare class TokenSender {
1352
+ private readonly clientManager;
1353
+ private readonly solver;
1354
+ /** Live view of the configured vaults so runtime vault edits are honored. */
1355
+ private readonly getVaults;
1356
+ private readonly userOpSender?;
1357
+ /** Only needed to size the paymaster's gas reserve; without it a send reserves nothing. */
1358
+ private readonly configService?;
1359
+ private readonly logger;
1360
+ constructor(clientManager: ChainClientManager, solver: HexString,
1361
+ /** Live view of the configured vaults so runtime vault edits are honored. */
1362
+ getVaults: () => VaultToml[], userOpSender?: UserOpSender | undefined,
1363
+ /** Only needed to size the paymaster's gas reserve; without it a send reserves nothing. */
1364
+ configService?: FillerConfigService | undefined);
1365
+ send(params: SendTokenParams): Promise<SendTokenResult>;
1366
+ /**
1367
+ * A `withdraw(shortfall, solver, solver)` call against a configured vault on
1368
+ * this chain whose underlying is the token being sent and that can cover the
1369
+ * shortfall, or null when no vault can.
1370
+ */
1371
+ /**
1372
+ * A withdrawal that funds the transfer and, where the vault can afford it,
1373
+ * leaves headroom in the wallet rather than nothing.
1374
+ *
1375
+ * `shortfall` is what the transfer cannot do without, and is zero or negative
1376
+ * when the wallet already covers it. The headroom is the larger of the
1377
+ * paymaster's gas `reserve` and the vault's `minBalance` — the first is what
1378
+ * validation will pull out of this wallet, the second is the operator's own
1379
+ * statement of the working balance this token keeps. A vault that can only
1380
+ * cover the shortfall still funds the send: headroom is a preference, not a
1381
+ * reason to refuse a transfer the operator asked for.
1382
+ */
1383
+ private vaultWithdrawalCall;
1384
+ /**
1385
+ * Executes a batch from the filler wallet: sponsored UserOp first, native tx
1386
+ * when no sponsor is available. Shared by every operator-initiated outbound flow.
1387
+ */
1388
+ submitCalls(chain: string, calls: ERC7821Call[]): Promise<{
1389
+ txHash: HexString;
1390
+ sponsored: boolean;
1391
+ }>;
1392
+ /**
1393
+ * The `approve(Permit2, max)` to run first when the wallet has never approved Permit2 for its fee
1394
+ * token on this chain, and that token has an EIP-2612 permit. The op then pays its gas with a permit
1395
+ * and installs the approval, so a wallet with no native can still send; later ops use Permit2.
1396
+ * Undefined otherwise, or when the check fails, and the send goes ahead as before.
1397
+ */
1398
+ private permit2Bootstrap;
1399
+ }
1400
+
1401
+ type TopUpPayToken = "USDC" | "USDT";
1402
+ /**
1403
+ * - `placing`: the placement batch is being sent.
1404
+ * - `awaiting_bids` / `filling`: the order is open on the gateway.
1405
+ * - `delivering`: filled on BSC; the BRIDGE is being relayed to Hyperbridge.
1406
+ * - `delivered`: the BRIDGE reached the filler's Hyperbridge account.
1407
+ * - `unfilled`: the order is past its auction without a fill; its input is still escrowed.
1408
+ * - `reclaiming` / `reclaimed`: the order is being, or has been, cancelled and refunded.
1409
+ * - `filled`: a record from before delivery was tracked; the BRIDGE was sent to Hyperbridge.
1410
+ * - `failed`: nothing was escrowed, or the outcome needs the operator's attention.
1411
+ */
1412
+ type BridgeTopUpStage = "placing" | "awaiting_bids" | "filling" | "delivering" | "delivered" | "unfilled" | "reclaiming" | "reclaimed" | "filled" | "failed";
1413
+ interface BridgeTopUpQuoteParams {
1414
+ /** Source state machine id the filler pays from, e.g. "EVM-8453". */
1415
+ chain: string;
1416
+ token: TopUpPayToken;
1417
+ /** Human-readable stablecoin amount. */
1418
+ amount: string;
1419
+ }
1420
+ interface BridgeTopUpQuote {
1421
+ chain: string;
1422
+ token: TopUpPayToken;
1423
+ tokenAddress: HexString;
1424
+ amountIn: string;
1425
+ /** BRIDGE the order requires, after slippage and nexus rounding. */
1426
+ bridgeOut: string;
1427
+ /** BRIDGE per one stablecoin unit, from the quote. */
1428
+ rate: string;
1429
+ fillable: boolean;
1430
+ /** Largest stablecoin input the route can fill, human-readable. */
1431
+ maxFillableIn: string;
1432
+ route: string;
1433
+ }
1434
+ /** What the dashboard sees of a top-up: the stored record without its order or session key. */
1435
+ type BridgeTopUpState = Omit<StoredBridgeTopUp, "order" | "sessionPrivateKey"> & {
1436
+ /** True while the top-up blocks a new one. */
1437
+ pending: boolean;
1438
+ };
1439
+ /** A stablecoin the filler wallet can pay a top-up with, on one network it fills on. */
1440
+ interface BridgeTopUpSource {
1441
+ chain: string;
1442
+ chainId: number;
1443
+ token: TopUpPayToken;
1444
+ address: HexString;
1445
+ /** Human-readable wallet balance, or null when the network could not be read. */
1446
+ balance: string | null;
1447
+ /** What a top-up can spend: the balance less {@link reserve}. Null when unread. */
1448
+ spendable: string | null;
1449
+ /** Held back for the paymaster, which pulls gas in this token. */
1450
+ reserve: string;
1451
+ }
1452
+ interface BridgeTopUpDeps {
1453
+ clientManager: ChainClientManager;
1454
+ /**
1455
+ * A top-up pays from a network the filler fills on: one whose account is delegated, so
1456
+ * the placement batch runs through it with gas paid in stablecoins.
1457
+ */
1458
+ delegation: Pick<DelegationService, "isDelegated">;
1459
+ configService: FillerConfigService;
1460
+ tokenSender: TokenSender;
1461
+ /** Where the top-up record is kept, so an open order is tracked across restarts. */
1462
+ state: StateStore;
1463
+ /** Directory for the SDK's order session keys and cancellation progress. */
1464
+ storagePath: string;
1465
+ /** Indexer the cross-chain reclaim streams its GET request status from. */
1466
+ indexerUrl: string;
1467
+ /** Filler EVM account that pays for the order. */
1468
+ solver: HexString;
1469
+ /** sr25519 public key of the filler's Hyperbridge account, the BRIDGE recipient. */
1470
+ substratePublicKey: HexString;
1471
+ /** Coprocessor the order's bids are read from. */
1472
+ hyperbridge?: Promise<IntentsCoprocessor>;
1473
+ loggers?: Parameters<typeof moduleLogger>[0];
1474
+ }
1475
+ /**
1476
+ * Operator top-up of the filler's Hyperbridge BRIDGE balance. Places an IntentGateway order
1477
+ * from any running chain that sells USDC/USDT for BRIDGE filled on BSC, with an output call
1478
+ * that sends the BRIDGE to the filler's Hyperbridge account through the HyperFungibleToken.
1479
+ *
1480
+ * One top-up is open at a time. Its record, order and session key are persisted, so a
1481
+ * restart resumes tracking it, and an order that goes unfilled can be reclaimed.
1482
+ */
1483
+ declare class BridgeTopUp {
1484
+ private readonly deps;
1485
+ private readonly logger;
1486
+ private readonly gateways;
1487
+ private dispatcher;
1488
+ private hyperbridgeChain;
1489
+ private record;
1490
+ private loaded;
1491
+ /** Serializes state writes so a later stage never lands before an earlier one. */
1492
+ private writes;
1493
+ private deliverySync;
1494
+ private bscClient;
1495
+ private syncing;
1496
+ /**
1497
+ * The top-up this process is driving, if any. Keyed by id: a finished top-up can still be
1498
+ * reading its last statuses when the operator starts the next one, which must not wait.
1499
+ */
1500
+ private driving;
1501
+ constructor(deps: BridgeTopUpDeps);
1502
+ get bridgeToken(): HexString;
1503
+ /** Loads the stored top-up and resumes driving it when it was left open. Never rejects. */
1504
+ restore(): Promise<void>;
1505
+ current(): Promise<BridgeTopUpState | undefined>;
1506
+ /** Networks the filler runs and has delegated its account on. */
1507
+ private sourceChains;
1508
+ /** USDC and USDT wallet balances on every network a top-up can be paid from. */
1509
+ sources(): Promise<BridgeTopUpSource[]>;
1510
+ quote(params: BridgeTopUpQuoteParams): Promise<BridgeTopUpQuote>;
1511
+ /** Re-quotes, then places and drives the order in the background. Returns its state. */
1512
+ start(params: BridgeTopUpQuoteParams): Promise<BridgeTopUpState>;
1513
+ /** Cancels an unfilled order and refunds its escrowed input to the filler wallet. */
1514
+ reclaim(): Promise<BridgeTopUpState>;
1515
+ /**
1516
+ * Places the order, then tracks it. The SDK builds the `placeOrder` calldata and the
1517
+ * session key; the receipt is read back here, through the filler's own multi-endpoint
1518
+ * client, rather than by the SDK from one endpoint, where a node that has not caught up
1519
+ * with the new block fails the read and loses track of an order that did land.
1520
+ */
1521
+ private place;
1522
+ /**
1523
+ * Reads the placement receipt and stores the order as the gateway committed it. Throws
1524
+ * when the transaction placed no order, so the top-up is marked failed.
1525
+ */
1526
+ private recordPlacement;
1527
+ /** Continues an order whose placement landed but was not recorded. */
1528
+ private recoverPlacement;
1529
+ private resumeDelivery;
1530
+ private resumeOrder;
1531
+ /** Follows a placed order through bids to a fill, or to its deadline. */
1532
+ private trackOrder;
1533
+ /**
1534
+ * Carries the bought BRIDGE to Hyperbridge. The order's output call dispatched it from
1535
+ * BSC with no relayer fee. Relayers may still deliver it; when none has shortly after BSC
1536
+ * is final on Hyperbridge, the filler relays it itself: a request into Hyperbridge goes
1537
+ * in as an unsigned extrinsic and costs nothing. Runs until the request is delivered.
1538
+ */
1539
+ private deliver;
1540
+ /** Delivered, but the indexer has yet to record the delivery, and not for too long. */
1541
+ private needsStatusBackfill;
1542
+ /**
1543
+ * Reads where the order and its BRIDGE are, from the indexer and the chains, and records
1544
+ * it. Shared by the driver and {@link sync}, and never runs twice at once. True once the
1545
+ * BRIDGE has reached Hyperbridge.
1546
+ */
1547
+ private syncDelivery;
1548
+ private readDelivery;
1549
+ /**
1550
+ * The HyperFungibleToken request the fill dispatched to Hyperbridge. The indexer finds it by
1551
+ * the fill's transaction, as the explorer's search does; the fill's own `Sent` log is the
1552
+ * fallback while the indexer catches up.
1553
+ */
1554
+ private deliveryRequest;
1555
+ /**
1556
+ * BSC, read through every endpoint there is: the filler need not run it, and a single public
1557
+ * endpoint refuses receipts it considers old.
1558
+ */
1559
+ private bsc;
1560
+ /**
1561
+ * The transaction that filled the order. The executor reports it only for a fill this
1562
+ * process selected, so it comes from the indexer, or, while the indexer has yet to reach
1563
+ * BSC's latest blocks, from the gateway's `OrderFilled` log around when the fill was seen.
1564
+ */
1565
+ private findFill;
1566
+ /** The order's statuses as the indexer has them: placed, filled, redeemed or refunded. */
1567
+ private indexedOrder;
1568
+ /**
1569
+ * Brings a pending top-up up to date from the chains and the indexer, for a dashboard that
1570
+ * is showing it: someone may have filled, delivered or refunded it while this process was
1571
+ * not looking. One sync at a time; a call made while one runs waits for that one.
1572
+ */
1573
+ sync(): Promise<void>;
1574
+ private reconcile;
1575
+ /**
1576
+ * Submits the request to Hyperbridge with a proof from BSC state. Public BSC endpoints
1577
+ * differ in whether they serve `eth_getProof` for recent blocks, so each is tried in turn.
1578
+ */
1579
+ private selfRelay;
1580
+ /** The operator's BSC endpoints first, then the free public ones. */
1581
+ private bscProofRpcUrls;
1582
+ /** Records an executor update. True when the order reached a final state. */
1583
+ private applyExecutionUpdate;
1584
+ /**
1585
+ * Recovers an unfilled order's escrow, following the Hyperbridge app's recovery: the SDK
1586
+ * cancel flow from the paying chain (BSC's expiry height finalized, the cancel sent, the
1587
+ * cancellation finalized on the source, verified and finalized on Hyperbridge), then the
1588
+ * refund. A relayer normally delivers that refund; if none has within a few minutes of
1589
+ * Hyperbridge finalizing, the filler submits Hyperbridge's response itself. Each step is
1590
+ * recorded as it lands, and a restart resumes from the SDK's persisted progress.
1591
+ */
1592
+ private driveReclaim;
1593
+ /**
1594
+ * Submits Hyperbridge's finalized response to the source chain's ISMP handler, which
1595
+ * refunds the escrow, as the Hyperbridge app's "Claim funds" does.
1596
+ */
1597
+ private claimRefund;
1598
+ /**
1599
+ * Whether the escrow is back with the filler, from the source gateway or the indexer's
1600
+ * REFUNDED status; records the refund as the recovery's last step when it is.
1601
+ */
1602
+ private refunded;
1603
+ private hasRecoveryStep;
1604
+ /** Records a recovery step once, at the time it landed (or now). */
1605
+ private recordRecoveryStep;
1606
+ /** Runs one background flow for the record; a failure is recorded rather than thrown. */
1607
+ private drive;
1608
+ /** Keeps what the executor reported, for the timeline's offers and its links. */
1609
+ private recordEvent;
1610
+ private update;
1611
+ private persist;
1612
+ private buildOrder;
1613
+ /**
1614
+ * Approvals for the gateway to pull the input and the order fee, then `placeOrder`.
1615
+ * The fee token is often the input token itself; one approval then covers both.
1616
+ */
1617
+ private placementCalls;
1618
+ private payToken;
1619
+ private walletBalance;
1620
+ /** What the paymaster pulls for gas on the sponsored path, which a top-up must leave. */
1621
+ private reserve;
1622
+ /** Spendable wallet balance: the paymaster's gas reserve is held back. */
1623
+ private balanceOf;
1624
+ private feeToken;
1625
+ /** The CallDispatcher the BSC gateway runs output calls through, read from the gateway itself. */
1626
+ private callDispatcher;
1627
+ private blockTimeSeconds;
1628
+ private evmChain;
1629
+ private gateway;
1630
+ private hyperbridge;
1631
+ /** Tracks requests from BSC into Hyperbridge, the route the bought BRIDGE takes. */
1632
+ private ismpClient;
1633
+ /** Tracks the reclaim's cancellation, which runs between the paying chain and BSC. */
1634
+ private cancelClient;
1635
+ }
1636
+
1637
+ /**
1638
+ * The persistence contract for a running filler.
1639
+ *
1640
+ * Everything simplex must remember across a restart lives behind these four
1641
+ * stores: submitted bids (so deposits can be reclaimed), the order-activity
1642
+ * feed, the operator's limit orders, and a scrap of operator state. Nothing
1643
+ * else in the filler touches a database, a file, or a data directory.
1644
+ *
1645
+ * Every method is async. The bundled SQLite adapter is synchronous underneath
1646
+ * and simply returns resolved promises — the async signature exists so a
1647
+ * consumer can back these with Postgres, Redis, or a hosted API. Making the
1648
+ * interface synchronous would have limited "bring your own backend" to backends
1649
+ * that are local and synchronous, which is nearly the same as not having the
1650
+ * interface at all.
1651
+ */
1652
+ interface SimplexDataStore {
1653
+ bids: BidStore;
1654
+ activity: ActivityStore;
1655
+ state: StateStore;
1656
+ limitOrders: LimitOrderStore;
1657
+ inventory: InventoryStore;
1658
+ /** Directory the store keeps its files in; absent for an in-memory store. */
1659
+ readonly dataDir?: string;
1660
+ /** Releases any underlying handles. Called by `Simplex.stop()`. */
1661
+ close?(): Promise<void>;
1662
+ }
1663
+ /** A bid submitted to Hyperbridge's solver-selection pallet. */
1664
+ interface StoredBid {
1665
+ id: number;
1666
+ commitment: string;
1667
+ /** The identifier Hyperbridge files the bid under; see {@link BidInsert.bid}. */
1668
+ bid: string | null;
1669
+ extrinsicHash: string | null;
1670
+ blockHash: string | null;
1671
+ success: boolean;
1672
+ /**
1673
+ * The submission timed out with the extrinsic still in Hyperbridge's pool. It
1674
+ * may yet land and reserve a deposit, so the sweep treats it as reclaimable
1675
+ * even though `success` is false.
1676
+ */
1677
+ pending: boolean;
1678
+ error: string | null;
1679
+ /** SQLite-style "YYYY-MM-DD HH:MM:SS" in UTC. Sorts lexicographically. */
1680
+ createdAt: string;
1681
+ retracted: boolean;
1682
+ retractedAt: string | null;
1683
+ retractExtrinsicHash: string | null;
1684
+ /** The order was seen filled on-chain, so this bid can never win — reclaim its deposit now. */
1685
+ dead: boolean;
1686
+ /**
1687
+ * What this bid holds against the operator's limit orders, at 1e18, in the
1688
+ * order the payout draws on them. Empty for a bid placed before limit orders
1689
+ * priced anything, and empty again once the hold has been settled, whether it
1690
+ * was released because the bid lost or converted because the bid filled.
1691
+ *
1692
+ * More than one when a swap drew on several levels: the orderbook quotes a
1693
+ * same-chain swapper across every level that can fill the trade together, so a
1694
+ * bid may be meeting several of the operator's own orders at once.
1695
+ */
1696
+ reservations: LimitOrderHold[];
1697
+ }
1698
+ /** One limit order and what a bid holds against it, at 1e18. */
1699
+ interface LimitOrderHold {
1700
+ limitOrderId: string;
1701
+ amount: string;
1702
+ /**
1703
+ * The take the bid signed beside `amount`, in the input token's raw units. With it, settlement
1704
+ * works out what the gateway charged: the escrow it released at `amount / take`. Holds recorded
1705
+ * before bids carried their own rate have none.
1706
+ */
1707
+ take?: string;
1708
+ /**
1709
+ * The input token's decimals on the order's source chain, which is where `take` and the escrow
1710
+ * a fill releases are counted. With it, settlement records what a fill took in at 1e18. Holds
1711
+ * recorded before fills kept their input have none.
1712
+ */
1713
+ takeDecimals?: number;
1714
+ }
1715
+ interface BidInsert {
1716
+ commitment: string;
1717
+ /**
1718
+ * The identifier Hyperbridge files the bid under, `keccak256` of its calldata. Bids on one
1719
+ * incoming order share a commitment, and this is what tells them apart. It is also what
1720
+ * retracting the bid names, so it is recorded here at placement rather than looked up later:
1721
+ * a bid still in Hyperbridge's pool is not in its storage yet, but its retraction, sent after it
1722
+ * from the same account, lands after it and reclaims its deposit all the same.
1723
+ */
1724
+ bid?: string;
1725
+ /** What this bid holds against the limit orders that priced it, best first. */
1726
+ reservations?: LimitOrderHold[];
1727
+ extrinsicHash?: string;
1728
+ blockHash?: string;
1729
+ success: boolean;
1730
+ /** Submission is still in the pool; see {@link StoredBid.pending}. */
1731
+ pending?: boolean;
1732
+ error?: string;
1733
+ }
1734
+ interface BidStats {
1735
+ total: number;
1736
+ successful: number;
1737
+ failed: number;
1738
+ retracted: number;
1739
+ /** Deposits still locked: successful or pending, and not yet retracted. */
1740
+ pendingRetraction: number;
1741
+ }
1742
+ /**
1743
+ * Persistent record of every bid submitted to Hyperbridge.
1744
+ *
1745
+ * This is the money-critical store: a successful bid locks a deposit that is
1746
+ * only reclaimable by retracting it, and the retraction sweep finds what to
1747
+ * reclaim by querying here. An implementation that loses writes leaks deposits,
1748
+ * so `store` must be durable before it resolves — do not buffer it in memory
1749
+ * and flush later.
1750
+ */
1751
+ interface BidStore {
1752
+ store(bid: BidInsert): Promise<void>;
1753
+ /** The most recent bid for a commitment, or null. */
1754
+ byCommitment(commitment: string): Promise<StoredBid | null>;
1755
+ /** Every bid for any of `commitments`, newest first. Empty input resolves empty. */
1756
+ byCommitments(commitments: string[]): Promise<StoredBid[]>;
1757
+ /** Unretracted bids whose deposit may still be locked: confirmed or pooled. */
1758
+ unretractedReclaimable(): Promise<StoredBid[]>;
1759
+ /**
1760
+ * Bids due for retraction: successful *or still pending*, unretracted, and
1761
+ * either older than `maxAgeMs` or flagged dead. Pending counts because a
1762
+ * pooled extrinsic that later lands reserves a deposit exactly like a
1763
+ * confirmed one. Dead bids ignore the age cut — their deposit is
1764
+ * reclaimable immediately, so a failed attempt retries on the next sweep
1765
+ * rather than waiting out the TTL.
1766
+ */
1767
+ expiredUnretracted(maxAgeMs: number): Promise<StoredBid[]>;
1768
+ /**
1769
+ * Marks a bid retracted. Also called when the chain reports `BidNotFound`:
1770
+ * bids only leave the pallet by retraction, so "no bid" means there is
1771
+ * nothing left to reclaim and no hash is available.
1772
+ *
1773
+ * Resolves false when no unretracted bid matched (already retracted, or never
1774
+ * stored) — the caller treats that as success, not an error.
1775
+ */
1776
+ markRetracted(commitment: string, retractExtrinsicHash: string | null): Promise<boolean>;
1777
+ /** Flags a bid dead (its order was filled on-chain). False when nothing matched. */
1778
+ markDead(commitment: string): Promise<boolean>;
1779
+ /**
1780
+ * Takes what this bid holds, exactly once, and returns it.
1781
+ *
1782
+ * A losing bid gives its holds back and a winning one converts them into
1783
+ * draw-downs, and both routes end at the same bid row — a bid that won is still
1784
+ * retracted eventually, by the stale sweep, so an unguarded release would undo
1785
+ * a conversion that already happened. Whichever settles first claims them here;
1786
+ * the other gets an empty list and does nothing.
1787
+ */
1788
+ /**
1789
+ * Takes the holds off a bid, exactly once, and hands them to the caller.
1790
+ *
1791
+ * With a `bid` it claims that one bid; without, every outstanding hold on the
1792
+ * commitment, which is what a filled or dead order needs so no bid's hold is
1793
+ * left behind.
1794
+ */
1795
+ claimReservation(commitment: string, bid?: string): Promise<LimitOrderHold[]>;
1796
+ /**
1797
+ * Every bid that drew on a limit order, newest first. What makes a `remaining`
1798
+ * explicable to the operator: which bids took the difference.
1799
+ */
1800
+ byLimitOrder(limitOrderId: string, limit?: number): Promise<StoredBid[]>;
1801
+ /** Newest first. Implementations should cap `limit` at a few hundred. */
1802
+ recent(limit?: number): Promise<StoredBid[]>;
1803
+ /** Failed bids, newest first — for debugging. */
1804
+ failed(limit?: number): Promise<StoredBid[]>;
1805
+ byDateRange(from: Date, to: Date): Promise<StoredBid[]>;
1806
+ stats(): Promise<BidStats>;
1807
+ }
1808
+ /**
1809
+ * `bid`: this filler's bid for the order was accepted by Hyperbridge (txHash is
1810
+ * the extrinsic hash). `filled`: this filler filled the order on chain. `lost`:
1811
+ * another filler did (reason carries its address). `executed`: a direct fill
1812
+ * attempt or a failed bid.
1813
+ */
1814
+ type ActivityType = "detected" | "bid" | "filled" | "lost" | "executed" | "skipped" | "rebalance";
1815
+ /** One leg of an order as the activity feed records it. */
1816
+ interface OrderLeg {
1817
+ /** 20-byte token address; the zero address is the chain's native asset. */
1818
+ token: string;
1819
+ /** Raw on-chain amount as a decimal string (bigint-safe). */
1820
+ amount: string;
1821
+ /** Registry symbol when the token is known to this filler, else null. */
1822
+ symbol: string | null;
1823
+ /** ERC-20 decimals when they could be read, else null (the UI then shows the raw amount). */
1824
+ decimals: number | null;
1825
+ }
1826
+ /**
1827
+ * What the activity feed knows about an order, captured when it was detected
1828
+ * and attached to every later event for that order so each row stands alone.
1829
+ */
1830
+ interface OrderSummary {
1831
+ user: string;
1832
+ /** Source chain state machine id, e.g. "EVM-8453". */
1833
+ source: string;
1834
+ destination: string;
1835
+ /** Hash of the transaction that placed the order on the source chain. */
1836
+ placedTxHash: string | null;
1837
+ /** 20-byte referrer from the order's graffiti tag; null when unattributed. */
1838
+ referrer: string | null;
1839
+ inputs: OrderLeg[];
1840
+ outputs: OrderLeg[];
1841
+ /** Order deadline (block number) as a decimal string. */
1842
+ deadline: string;
1843
+ }
1844
+ interface ActivityEvent {
1845
+ id: number;
1846
+ ts: number;
1847
+ type: ActivityType;
1848
+ orderId: string | null;
1849
+ chainId: number | null;
1850
+ strategy: string | null;
1851
+ success: boolean | null;
1852
+ /** Skip reason or execution error. */
1853
+ reason: string | null;
1854
+ volumeUsd: number | null;
1855
+ profitUsd: number | null;
1856
+ txHash: string | null;
1857
+ /** The order this event concerns, when known; null for rebalances and legacy rows. */
1858
+ order: OrderSummary | null;
1859
+ }
1860
+ type ActivityInsert = Partial<Omit<ActivityEvent, "id" | "ts" | "type">> & {
1861
+ type: ActivityType;
1862
+ };
1863
+ type WalletTxKind = "send" | "sweep" | "redeem";
1864
+ /** One outbound transaction from the filler wallet (operator send, vault sweep/redeem). */
1865
+ interface WalletTx {
1866
+ id: number;
1867
+ ts: number;
1868
+ kind: WalletTxKind;
1869
+ chainId: number | null;
1870
+ /** What left the wallet: symbol and decimal amount (sends, sweeps, redeems). */
1871
+ token: string | null;
1872
+ amount: string | null;
1873
+ to: string | null;
1874
+ txHash: string;
1875
+ sponsored: boolean | null;
1876
+ /** What came back: vault shares for a sweep, the underlying for a redeem. */
1877
+ tokenIn?: string | null;
1878
+ amountIn?: string | null;
1879
+ }
1880
+ /**
1881
+ * Append-only feed of what the filler did, backing the operator dashboard.
1882
+ *
1883
+ * Unlike {@link BidStore} this is observability, not correctness: dropping a
1884
+ * row costs a line in the activity view and nothing else, so an implementation
1885
+ * may prune, sample, or cap history freely.
1886
+ */
1887
+ interface ActivityStore {
1888
+ /** Appends an event and resolves the stored row (its `id` and `ts` assigned). */
1889
+ record(event: ActivityInsert): Promise<ActivityEvent>;
1890
+ /** Newest first; pass `beforeId` to page backwards. */
1891
+ recent(limit?: number, beforeId?: number): Promise<ActivityEvent[]>;
1892
+ /** Fill events carrying both a tx hash and a chain, newest first. */
1893
+ fills(limit?: number): Promise<ActivityEvent[]>;
1894
+ recordWalletTx(tx: Omit<WalletTx, "id" | "ts">): Promise<void>;
1895
+ walletTxs(limit?: number): Promise<WalletTx[]>;
1896
+ /** Sweep and redeem rows recorded without amounts (before the ledger carried them). Newest first. */
1897
+ walletTxsWithoutAmounts(limit?: number): Promise<WalletTx[]>;
1898
+ /** Fills in the amount fields of one ledger row. */
1899
+ updateWalletTx(id: number, patch: Pick<WalletTx, "token" | "amount" | "to" | "tokenIn" | "amountIn">): Promise<void>;
1900
+ /**
1901
+ * Distinct order ids among the newest rows that carry no order summary —
1902
+ * rows written before summaries existed. Newest first, at most `limit`.
1903
+ */
1904
+ orderIdsMissingSummary(limit?: number): Promise<string[]>;
1905
+ /** Sets the summary on every row for `orderId` that lacks one; resolves the rows it changed. */
1906
+ attachOrder(orderId: string, order: OrderSummary): Promise<ActivityEvent[]>;
1907
+ /**
1908
+ * Orders (rows sharing an order id) newest-activity first, one page at a
1909
+ * time. `page` is 1-based; `total` counts every distinct order.
1910
+ */
1911
+ orderHistory(page: number, pageSize: number): Promise<OrderHistoryPage>;
1912
+ /** Whether any row exists for `orderId` — i.e. this filler has seen the order. */
1913
+ knowsOrder(orderId: string): Promise<boolean>;
1914
+ /**
1915
+ * Orders this filler bid on that have no on-chain outcome recorded yet: a
1916
+ * `bid` row, or a legacy bid-time `filled` row (those carry `volumeUsd`), and
1917
+ * neither a `lost` row nor an observed `filled` row (no `volumeUsd`). Newest first.
1918
+ */
1919
+ unsettledOrders(limit?: number): Promise<string[]>;
1920
+ /** Retypes an order's legacy bid-time `filled` rows to `bid`; resolves the rows it changed. */
1921
+ retypeLegacyBid(orderId: string): Promise<ActivityEvent[]>;
1922
+ }
1923
+ /** One order's rows, newest first. */
1924
+ interface OrderHistoryEntry {
1925
+ orderId: string;
1926
+ events: ActivityEvent[];
1927
+ }
1928
+ interface OrderHistoryPage {
1929
+ page: number;
1930
+ pageSize: number;
1931
+ total: number;
1932
+ orders: OrderHistoryEntry[];
1933
+ }
1934
+ /** Which way round a limit order trades its book's pair. */
1935
+ type LimitOrderSide = "BID" | "ASK";
1936
+ /**
1937
+ * `open`: live, and bids may draw on it. `resizing`: a repost is in flight after
1938
+ * a fill, so the orderbook entry may be missing until it lands. `filled`: worked
1939
+ * down past the dust floor. `cancelled`: withdrawn by the operator. `expired`:
1940
+ * past the operator's own `expiresAt` and swept off the book. `rejected`: the
1941
+ * orderbook refused it and `lastError` says why.
1942
+ */
1943
+ type LimitOrderStatus = "open" | "resizing" | "filled" | "cancelled" | "expired" | "rejected";
1944
+ /**
1945
+ * One of the operator's limit orders.
1946
+ *
1947
+ * This record is what simplex prices against and draws down; the orderbook entry
1948
+ * is a derived copy that expires and is reposted. Amounts and prices are decimal
1949
+ * strings at 1e18, the unit the orderbook takes and returns, whatever decimals
1950
+ * the tokens use on their own chains.
1951
+ */
1952
+ interface LimitOrder {
1953
+ /** Stable across every repost, unlike `commitment`. */
1954
+ id: string;
1955
+ /** Orderbook book id, with the symbols it resolved to. */
1956
+ book: string;
1957
+ base: string;
1958
+ quote: string;
1959
+ side: LimitOrderSide;
1960
+ /** Where simplex fills, as a state machine id. */
1961
+ fillChain: string;
1962
+ /** Quote per 1 base: the rate simplex signs, before the orderbook's fee haircut. */
1963
+ price: string;
1964
+ /** The output simplex offered to pay when the order was created. */
1965
+ size: string;
1966
+ /** Output not yet delivered. */
1967
+ remaining: string;
1968
+ /** Output promised to bids that have neither filled nor been retracted. */
1969
+ reserved: string;
1970
+ /** Source chains this order accepts swaps from. Never empty. */
1971
+ acceptedSources: string[];
1972
+ /** TTL written into each posting. */
1973
+ ttlSecs: number;
1974
+ /** Operator expiry for the limit order itself, independent of the posting's. */
1975
+ expiresAt: string | null;
1976
+ status: LimitOrderStatus;
1977
+ /** The current posting's commitment, absent while nothing is live. */
1978
+ commitment: string | null;
1979
+ /** Bumped on every repost, so each posting hashes differently. */
1980
+ orderNonce: string;
1981
+ /**
1982
+ * The EntryPoint the current posting's op was signed for. Null while nothing is
1983
+ * posted, and on a posting stored before this was recorded, which was signed
1984
+ * for v0.8.
1985
+ */
1986
+ entryPoint: string | null;
1987
+ /** When the current posting expires, as the orderbook reported it. */
1988
+ bookExpiresAt: string | null;
1989
+ /** `Order.price` from the orderbook, which shades `price` by the protocol fee. */
1990
+ bookPrice: string | null;
1991
+ /** The last rejection, as "CODE: message". */
1992
+ lastError: string | null;
1993
+ /** SQLite-style "YYYY-MM-DD HH:MM:SS" in UTC. Sorts lexicographically. */
1994
+ createdAt: string;
1995
+ updatedAt: string;
1996
+ }
1997
+ /** What the operator supplies; everything else is derived or defaulted. */
1998
+ interface LimitOrderInsert {
1999
+ id: string;
2000
+ book: string;
2001
+ base: string;
2002
+ quote: string;
2003
+ side: LimitOrderSide;
2004
+ fillChain: string;
2005
+ price: string;
2006
+ size: string;
2007
+ acceptedSources: string[];
2008
+ ttlSecs: number;
2009
+ expiresAt?: string | null;
2010
+ /** Where the order's posted-op nonce starts. Defaults to "0". */
2011
+ orderNonce?: string;
2012
+ }
2013
+ interface LimitOrderFilter {
2014
+ status?: LimitOrderStatus;
2015
+ fillChain?: string;
2016
+ book?: string;
2017
+ }
2018
+ /** The fields a posting writes back, applied together so a half-posted row is never visible. */
2019
+ interface LimitOrderPosting {
2020
+ commitment: string | null;
2021
+ bookExpiresAt: string | null;
2022
+ bookPrice: string | null;
2023
+ orderNonce: string;
2024
+ entryPoint: string | null;
2025
+ status: LimitOrderStatus;
2026
+ lastError: string | null;
2027
+ }
2028
+ /**
2029
+ * Persistent record of the operator's limit orders.
2030
+ *
2031
+ * This is inventory, not a cache: `remaining` and `reserved` are what stop two
2032
+ * chains from paying out the same liability twice, so a store that loses writes
2033
+ * overcommits real money.
2034
+ */
2035
+ /**
2036
+ * One fill that drew a limit order down: kept apart from the bid that priced it, whose
2037
+ * holds are cleared on settlement, so an order keeps its history across every resize.
2038
+ */
2039
+ interface LimitOrderFill {
2040
+ id: number;
2041
+ limitOrderId: string;
2042
+ /** The swap order the fill settled. */
2043
+ commitment: string;
2044
+ /** The identifier Hyperbridge filed the executed bid under, when known. */
2045
+ bid: string | null;
2046
+ /** What the fill drew the limit order down by, at 1e18 in the token it pays. */
2047
+ amount: string;
2048
+ /**
2049
+ * The escrow the fill released to the solver, at 1e18 in the token the order takes in. Null
2050
+ * for a fill recorded before this was kept, and for one whose event carried no inputs.
2051
+ */
2052
+ amountIn: string | null;
2053
+ /** The fill's transaction on the order's fill chain, when the event carried it. */
2054
+ transactionHash: string | null;
2055
+ /** SQLite-style "YYYY-MM-DD HH:MM:SS" in UTC. */
2056
+ filledAt: string;
2057
+ }
2058
+ interface LimitOrderFillInsert {
2059
+ limitOrderId: string;
2060
+ commitment: string;
2061
+ bid?: string | null;
2062
+ amount: string;
2063
+ amountIn?: string | null;
2064
+ transactionHash?: string | null;
2065
+ }
2066
+ /**
2067
+ * A fill with the terms of the order it drew on: what profitability is worked out from. The
2068
+ * order's `price` stands in for the rate of a fill that kept no `amountIn`.
2069
+ */
2070
+ interface LimitOrderFillRecord {
2071
+ id: number;
2072
+ limitOrderId: string;
2073
+ book: string;
2074
+ base: string;
2075
+ quote: string;
2076
+ side: LimitOrderSide;
2077
+ price: string;
2078
+ amount: string;
2079
+ amountIn: string | null;
2080
+ filledAt: string;
2081
+ }
2082
+ interface LimitOrderStore {
2083
+ create(order: LimitOrderInsert): Promise<LimitOrder>;
2084
+ get(id: string): Promise<LimitOrder | null>;
2085
+ list(filter?: LimitOrderFilter): Promise<LimitOrder[]>;
2086
+ /** Every `open` order, which is what the matcher prices against. */
2087
+ open(): Promise<LimitOrder[]>;
2088
+ /**
2089
+ * Records what the orderbook did with the current posting.
2090
+ *
2091
+ * `only` guards the write on the status the row still holds, and answers null
2092
+ * when it has moved on. A posting is a slow round trip, and an operator's
2093
+ * cancel or an expiry sweep that lands first must not be undone by an answer
2094
+ * that was already in flight.
2095
+ */
2096
+ setPosting(id: string, posting: LimitOrderPosting, only?: readonly LimitOrderStatus[]): Promise<LimitOrder | null>;
2097
+ setStatus(id: string, status: LimitOrderStatus, lastError?: string | null, only?: readonly LimitOrderStatus[]): Promise<LimitOrder | null>;
2098
+ /**
2099
+ * Adds `amount` to `reserved`, but only while the order is `open` and
2100
+ * `remaining` covers this one hold. Resolves false when it does not.
2101
+ *
2102
+ * Other bids' holds do not count against it: a pending bid must not stop the
2103
+ * next one going out, so the limit order bounds each bid and what fills draw
2104
+ * down, not the sum of bids still waiting. The hold is bookkeeping, turned into
2105
+ * a draw-down if the bid wins and given back if it loses. The write is still
2106
+ * guarded on the `reserved` it read, so two holds landing together both count.
2107
+ */
2108
+ reserve(id: string, amount: string): Promise<boolean>;
2109
+ /** Gives a reservation back, after a bid was retracted, lost or found dead. */
2110
+ release(id: string, amount: string): Promise<void>;
2111
+ /**
2112
+ * Works the order down by output that has actually been delivered, floored at
2113
+ * zero. Returns the order as it now stands, or null when there is none.
2114
+ */
2115
+ drawDown(id: string, amount: string): Promise<LimitOrder | null>;
2116
+ /**
2117
+ * Lowers an `open` order's `remaining` to `room + reserved`, where `room` is
2118
+ * what the solver's account on chain will still pay for this order, at 1e18.
2119
+ * Never raises it. Returns the order when it was lowered, null otherwise.
2120
+ *
2121
+ * `reserved` is added back because a fill the chain has already counted, but
2122
+ * that has not been drawn down here yet, still holds its amount there until it
2123
+ * settles. Clamping to `room` alone would take that fill off twice.
2124
+ */
2125
+ clampRemaining(id: string, room: string): Promise<LimitOrder | null>;
2126
+ /** Records a fill against its limit order. Called alongside the draw-down it explains. */
2127
+ recordFill(fill: LimitOrderFillInsert): Promise<void>;
2128
+ /** Every fill of every limit order, oldest first, each with its order's terms. */
2129
+ fillHistory(): Promise<LimitOrderFillRecord[]>;
2130
+ /** A limit order's fills, newest first. */
2131
+ fills(limitOrderId: string, limit?: number): Promise<LimitOrderFill[]>;
2132
+ /**
2133
+ * Runs `settle` as one unit where the backend can.
2134
+ *
2135
+ * A fill claims what its bid held, works the orders down by what went out and
2136
+ * gives the rest back, and a crash between those leaves the hold released
2137
+ * against an order that was never drawn down. The bid rows live in the same
2138
+ * database as the limit orders, which is what lets one transaction cover both.
2139
+ *
2140
+ * Only store calls belong inside: they are synchronous underneath, so nothing
2141
+ * else interleaves on the connection, which would not hold for a network call.
2142
+ */
2143
+ transaction<T>(settle: () => Promise<T>): Promise<T>;
2144
+ }
2145
+ /** Operator state that must survive a restart. */
2146
+ interface RuntimeState {
2147
+ /** A pause set by the operator stays set across restarts. */
2148
+ paused?: boolean;
2149
+ /** Notification rules, browser subscriptions, and the per-installation VAPID identity. */
2150
+ notifications?: NotificationRuntimeState;
2151
+ /** The latest BRIDGE top-up, kept so an open order is resumed or reclaimed after a restart. */
2152
+ bridgeTopUp?: StoredBridgeTopUp;
2153
+ }
2154
+ interface StoredBridgeTopUp {
2155
+ id: string;
2156
+ /** Source state machine id the order was placed on. */
2157
+ chain: string;
2158
+ token: TopUpPayToken;
2159
+ amountIn: string;
2160
+ bridgeOut: string;
2161
+ stage: BridgeTopUpStage;
2162
+ createdAt: number;
2163
+ updatedAt: number;
2164
+ /** The order as placed, bigints encoded by `serializeOrder`. */
2165
+ order: string;
2166
+ /** Signs the order's bid selection; it authorizes nothing beyond this order. */
2167
+ sessionPrivateKey?: HexString;
2168
+ commitment?: HexString;
2169
+ placeTxHash?: HexString;
2170
+ fillTxHash?: HexString;
2171
+ reclaimTxHash?: HexString;
2172
+ /** The HyperFungibleToken request that carries the BRIDGE to Hyperbridge. */
2173
+ deliveryCommitment?: HexString;
2174
+ /** The Hyperbridge extrinsic that delivered it, when this filler relayed it. */
2175
+ deliveryTxHash?: HexString;
2176
+ /** Bids seen for the order. */
2177
+ bidCount?: number;
2178
+ /** When each stage was entered, in ms. */
2179
+ stageTimes?: Partial<Record<BridgeTopUpStage, number>>;
2180
+ /** What the order's executor reported, oldest first: bids, the selection and the fill. */
2181
+ events?: TopUpExecutionEvent[];
2182
+ /** The solver that filled the order, from the executor or the indexer. */
2183
+ filler?: HexString;
2184
+ /** The BRIDGE request's progress from BSC into Hyperbridge, from the indexer. */
2185
+ deliveryStatuses?: DeliveryStatus[];
2186
+ /** The unfilled order's recovery, step by step, as the SDK's cancel flow reported it. */
2187
+ recoverySteps?: RecoveryStepRecord[];
2188
+ /** Hyperbridge's finalized response, which refunds the escrow when submitted on the source. */
2189
+ claimCalldata?: HexString;
2190
+ /** The filler's own refund claim, when no relayer delivered it in time. */
2191
+ claimTxHash?: HexString;
2192
+ error?: string;
2193
+ }
2194
+ interface RecoveryStepRecord {
2195
+ step: "DESTINATION_FINALIZED" | "ORDER_CANCELED" | "SOURCE_FINALIZED" | "HYPERBRIDGE_DELIVERED" | "HYPERBRIDGE_FINALIZED" | "FUNDS_CLAIMED";
2196
+ /** When the step completed, in ms. */
2197
+ timestamp: number;
2198
+ txHash?: HexString;
2199
+ }
2200
+ interface DeliveryStatus {
2201
+ /** Dispatched on BSC, BSC final on Hyperbridge, delivered to Hyperbridge. */
2202
+ status: "SOURCE" | "SOURCE_FINALIZED" | "DESTINATION";
2203
+ /** In ms. */
2204
+ timestamp: number;
2205
+ txHash: HexString;
2206
+ }
2207
+ interface TopUpBidPreview {
2208
+ solverAddress: HexString;
2209
+ /** Output budgets as raw token units, the token as bytes32. */
2210
+ outputs: {
2211
+ token: HexString;
2212
+ amount: string;
2213
+ }[];
2214
+ }
2215
+ interface TopUpExecutionEvent {
2216
+ status: "NEW_BID" | "BIDS_RECEIVED" | "BID_SELECTED" | "FILLED";
2217
+ /** When the filler saw it, in ms. */
2218
+ timestamp: number;
2219
+ bidCount?: number;
2220
+ bids?: TopUpBidPreview[];
2221
+ selectedSolver?: HexString;
2222
+ userOpHash?: HexString;
2223
+ txHash?: HexString;
2224
+ }
2225
+ interface NotificationSettings {
2226
+ /** Alert once when aggregate spendable USD-stable liquidity crosses below this value. */
2227
+ lowLiquidityThresholdUsd: number | null;
2228
+ /** Alert after this filler completes a swap. */
2229
+ swaps: boolean;
2230
+ }
2231
+ /** Transport-safe alert payload shared by Web Push and the native desktop shell. */
2232
+ interface OperatorNotification {
2233
+ title: string;
2234
+ body: string;
2235
+ tag: string;
2236
+ /** Relative dashboard destination to open when the operator clicks the alert. */
2237
+ url: string;
2238
+ /** Opaque, short-lived receipt used only to confirm native desktop test delivery. */
2239
+ receiptId?: string;
2240
+ }
2241
+ interface StoredPushSubscription {
2242
+ endpoint: string;
2243
+ expirationTime?: number | null;
2244
+ keys: {
2245
+ p256dh: string;
2246
+ auth: string;
2247
+ };
2248
+ }
2249
+ interface NotificationRuntimeState {
2250
+ settings: NotificationSettings;
2251
+ vapid: {
2252
+ publicKey: string;
2253
+ privateKey: string;
2254
+ };
2255
+ subscriptions: StoredPushSubscription[];
2256
+ /** Prevents restart/poll spam while liquidity remains below the configured threshold. */
2257
+ lowLiquidityActive?: boolean;
2258
+ }
2259
+ interface StateStore {
2260
+ get(): Promise<RuntimeState>;
2261
+ /** Replaces the whole record. Keys absent from `state` are dropped. */
2262
+ set(state: RuntimeState): Promise<void>;
2263
+ /**
2264
+ * Merges `patch` into the stored record atomically, leaving keys it does not
2265
+ * name untouched. Optional: `patchRuntimeState` falls back to a read and a
2266
+ * `set` for stores that cannot do better, which loses a key written
2267
+ * concurrently. The bundled SQLite store implements it.
2268
+ */
2269
+ patch?(patch: Partial<RuntimeState>): Promise<RuntimeState>;
2270
+ }
2271
+ /**
2272
+ * What the solver held at one moment: whole tokens per symbol, counting the wallet and the
2273
+ * vaults on every chain.
2274
+ */
2275
+ interface InventorySnapshot {
2276
+ id: number;
2277
+ /** SQLite-style "YYYY-MM-DD HH:MM:SS" in UTC. */
2278
+ takenAt: string;
2279
+ balances: Record<string, number>;
2280
+ }
2281
+ /**
2282
+ * A record of what the solver held, taken about once a day.
2283
+ *
2284
+ * It is what profit is measured against: the inventory a period began with. The chain can say
2285
+ * what an account holds now but not what it held last week, so a snapshot that was never taken
2286
+ * cannot be recovered. Like {@link ActivityStore} it is observability rather than correctness:
2287
+ * a lost row costs a figure on the analytics page and nothing else.
2288
+ */
2289
+ interface InventoryStore {
2290
+ record(balances: Record<string, number>): Promise<void>;
2291
+ /** The most recent snapshot, or null when none has been taken. */
2292
+ latest(): Promise<InventorySnapshot | null>;
2293
+ /** Every snapshot taken at or after `from`, oldest first. */
2294
+ since(from: Date): Promise<InventorySnapshot[]>;
2295
+ }
2296
+
2297
+ export { type LimitOrderPosting as $, type ActivityStore as A, type BidStore as B, ChainClientManager as C, type DerivingSigner as D, type AuthorizationRequest as E, FillerConfigService as F, type BidInsert as G, type BinanceConfig as H, type InventoryStore as I, type CurveConfig as J, type CurvePoint as K, LoggerContext as L, type MpcVaultSignerConfig as M, type Eip7702Authorization$1 as N, type OrderHistoryPage as O, type FillerConfigFile as P, type InventorySnapshot as Q, type ResolvedChainConfig as R, type SignerConfig as S, type TurnkeySignerConfig as T, UserOpSender as U, type VaultToml as V, type WalletTx as W, type LimitOrderFillInsert as X, type LimitOrderFillRecord as Y, type LimitOrderHold as Z, type LimitOrderInsert as _, type Signer as a, type LimitOrderSide as a0, type LimitOrderStatus as a1, type Logger as a2, type OperatorNotification as a3, type PrivateKeySignerConfig as a4, type QueueConfig as a5, type RuntimeState as a6, type SignerTransaction as a7, SignerType as a8, type TypedDataPayload as a9, USD_STABLE_SYMBOLS as aa, type UserProvidedChainConfig as ab, type VaultTomlConfig as ac, type WalletTxKind as ad, addLogSink as ae, assertConfirmationCoverage as af, configureLogger as ag, formatChainKey as ah, getLogger as ai, isRegistrySymbol as aj, normalizeSymbol as ak, parseChainKey as al, registrySymbols as am, validateAssetDefinitions as an, validateConfig as ao, validateVaultToml as ap, type OrderSummary as aq, type Signature as b, type SecretPhraseSignerConfig as c, type ActivityInsert as d, type ActivityEvent as e, type LimitOrderStore as f, AssetRegistry as g, DelegationService as h, type LimitOrder as i, type LimitOrderFilter as j, type SimplexDataStore as k, ConfirmationPolicy as l, type FillerTomlConfig as m, TokenSender as n, BridgeTopUp as o, type AssetDefinition as p, type ChainConfirmationPolicy as q, type LimitOrderFill as r, type StoredBid as s, type RebalancingConfig as t, type LogSink as u, type BidStats as v, type AllowlistConfig as w, type LogLevel as x, type StateStore as y, type ActivityType as z };