@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.
package/dist/index.d.cts CHANGED
@@ -1,15 +1,15 @@
1
- import { EventEmitter as EventEmitter$1 } from 'node:events';
2
- import { HexString, ERC7821Call, ChainConfig, Order, TokenInfo, IntentsCoprocessor, ExecutionResult, IntentGateway, FillOptions, FillerConfig as FillerConfig$1 } from '@hyperbridge/sdk';
3
- import { PublicClient, Hash, AbiEvent, BlockNumber, BlockTag, GetLogsParameters, GetLogsReturnType, WalletClient, Transport, Chain } from 'viem';
4
- import { LocalAccount, Account } from 'viem/accounts';
5
- import { L as LoggerContext, a as LogLevel, b as Logger, A as ActivityStore, c as ActivityInsert, d as ActivityEvent, O as OrderHistoryPage, W as WalletTx, e as LimitOrderStore, f as LimitOrder, g as LimitOrderFilter, B as BidStore, S as SimplexDataStore, h as LimitOrderFill, i as StoredBid, j as LogSink, k as BidStats, l as StateStore, I as InventoryStore } from './types-BPyUX8qu.cjs';
6
- export { m as ActivityType, n as BidInsert, o as InventorySnapshot, p as LimitOrderFillInsert, q as LimitOrderFillRecord, r as LimitOrderHold, s as LimitOrderInsert, t as LimitOrderPosting, u as LimitOrderSide, v as LimitOrderStatus, w as OperatorNotification, R as RuntimeState, x as WalletTxKind, y as addLogSink, z as configureLogger, C as getLogger } from './types-BPyUX8qu.cjs';
7
- import { ChannelCredentials } from '@grpc/grpc-js';
8
- import { EventEmitter } from 'events';
9
- import Decimal$1, { Decimal } from 'decimal.js';
1
+ import { EventEmitter } from 'node:events';
2
+ import { HexString, ERC7821Call, IntentsCoprocessor, Order, TokenInfo, ChainConfig, ExecutionResult, IntentGateway, FillOptions, FillerConfig } from '@hyperbridge/sdk';
3
+ import { S as SignerConfig, a as Signer, b as Signature, D as DerivingSigner, c as SecretPhraseSignerConfig, M as MpcVaultSignerConfig, T as TurnkeySignerConfig, C as ChainClientManager, F as FillerConfigService, U as UserOpSender, L as LoggerContext, A as ActivityStore, d as ActivityInsert, e as ActivityEvent, O as OrderHistoryPage, W as WalletTx, f as LimitOrderStore, g as AssetRegistry, h as DelegationService, i as LimitOrder, j as LimitOrderFilter, B as BidStore, k as SimplexDataStore, l as ConfirmationPolicy, R as ResolvedChainConfig, m as FillerTomlConfig, V as VaultToml, n as TokenSender, o as BridgeTopUp, p as AssetDefinition, q as ChainConfirmationPolicy, r as LimitOrderFill, s as StoredBid, t as RebalancingConfig, u as LogSink, v as BidStats, w as AllowlistConfig, x as LogLevel, y as StateStore, I as InventoryStore } from './types-6MEltdkX.cjs';
4
+ export { z as ActivityType, E as AuthorizationRequest, G as BidInsert, H as BinanceConfig, J as CurveConfig, K as CurvePoint, N as Eip7702Authorization, P as FillerConfigFile, Q as InventorySnapshot, X as LimitOrderFillInsert, Y as LimitOrderFillRecord, Z as LimitOrderHold, _ as LimitOrderInsert, $ as LimitOrderPosting, a0 as LimitOrderSide, a1 as LimitOrderStatus, a2 as Logger, a3 as OperatorNotification, a4 as PrivateKeySignerConfig, a5 as QueueConfig, a6 as RuntimeState, a7 as SignerTransaction, a8 as SignerType, a9 as TypedDataPayload, aa as USD_STABLE_SYMBOLS, ab as UserProvidedChainConfig, ac as VaultTomlConfig, ad as WalletTxKind, ae as addLogSink, af as assertConfirmationCoverage, ag as configureLogger, ah as formatChainKey, ai as getLogger, aj as isRegistrySymbol, ak as normalizeSymbol, al as parseChainKey, am as registrySymbols, an as validateAssetDefinitions, ao as validateConfig, ap as validateVaultToml } from './types-6MEltdkX.cjs';
5
+ import { EventEmitter as EventEmitter$1 } from 'events';
6
+ import { Decimal } from 'decimal.js';
10
7
  import * as _circle_fin_bridge_kit from '@circle-fin/bridge-kit';
11
8
  import { BridgeResult, EstimateResult } from '@circle-fin/bridge-kit';
9
+ import { LocalAccount } from 'viem/accounts';
10
+ import 'viem';
12
11
  import 'pino';
12
+ import '@grpc/grpc-js';
13
13
 
14
14
  /**
15
15
  * A liquidity source that can atomically withdraw tokens and make them
@@ -102,626 +102,6 @@ interface VaultOutputFundingConfig {
102
102
  sweepIntervalMs?: number;
103
103
  }
104
104
 
105
- interface UserProvidedChainConfig {
106
- /** One or more RPC URLs. When multiple are provided, event scans use quorum consensus. */
107
- rpcUrls: string[];
108
- bundlerUrl: string;
109
- }
110
- interface ResolvedChainConfig {
111
- chainId: number;
112
- /** One or more RPC URLs for this chain. When multiple are provided, event scans use quorum consensus. */
113
- rpcUrls: string[];
114
- bundlerUrl?: string;
115
- }
116
- interface GasFeeBumpConfig {
117
- maxPriorityFeePerGasBumpPercent?: number;
118
- maxFeePerGasBumpPercent?: number;
119
- }
120
- interface RebalancingConfig$1 {
121
- triggerPercentage: number;
122
- baseBalances: {
123
- USDC?: Record<string, string>;
124
- USDT?: Record<string, string>;
125
- };
126
- }
127
- interface OverfillProtectionConfig {
128
- /** Ceiling bps above user-requested output; filler clamps its computed output to this. Default 500 (5%). */
129
- maxOverfillBps?: number;
130
- /** Consecutive clamped evaluations before the strategy halts itself. Default 3. */
131
- maxConsecutiveClamps?: number;
132
- }
133
- interface AllowlistConfig {
134
- /** Hex addresses eligible across all chains. */
135
- users?: string[];
136
- /** Per-source-chain overrides, keyed by state machine id (e.g. "EVM-1"). Merged with the global list. */
137
- bySource?: Record<string, string[]>;
138
- }
139
- interface FillerConfig {
140
- maxConcurrentOrders: number;
141
- logging?: LogLevel;
142
- hyperbridgeWsUrl?: string;
143
- substratePrivateKey?: string;
144
- entryPointAddress?: string;
145
- /**
146
- * Optional gas fee bump configuration for UserOperation gas estimation.
147
- * If not provided, defaults will be used (8% for priority fee, 10% for max fee).
148
- */
149
- gasFeeBump?: GasFeeBumpConfig;
150
- rebalancing?: RebalancingConfig$1;
151
- /**
152
- * Target gas units the EntryPoint deposit should cover per chain.
153
- * Defaults to 3,000,000 if not set.
154
- */
155
- targetGasUnits?: number;
156
- /**
157
- * Seconds a signed bid stays executable. Defaults to 300 (5 minutes).
158
- * See `bidValiditySeconds` in filler-toml for why this bound matters.
159
- */
160
- bidValiditySeconds?: number;
161
- /**
162
- * Order size in USD below which `order.fees` must cover the execution cost.
163
- * Defaults to 20. See `minOrderSizeUsd` in filler-toml.
164
- */
165
- minOrderSizeUsd?: number;
166
- /**
167
- * Overfill protection knobs. If omitted, defaults are used
168
- * (maxOverfillBps=500, maxConsecutiveClamps=3).
169
- */
170
- overfillProtection?: OverfillProtectionConfig;
171
- /**
172
- * Restricts which order `user` addresses the filler processes. When omitted, all
173
- * users are accepted. When present, only listed users (global ∪ per-source) are
174
- * accepted; a chain whose merged set is empty rejects every order.
175
- */
176
- allowlist?: AllowlistConfig;
177
- /**
178
- * How often the block scanner polls each chain, in seconds. Defaults to 3.
179
- * Each tick costs one `eth_blockNumber` plus one `eth_getLogs` per chain per
180
- * endpoint, so raising it is the main lever for staying inside a
181
- * rate-limited RPC's budget — at the cost of seeing orders that much later.
182
- */
183
- blockScanIntervalSeconds?: number;
184
- }
185
- /**
186
- * Simplified configuration service for the filler that wraps ChainConfigService
187
- * and only requires minimal user configuration (RPC URLs, private keys, etc.)
188
- */
189
- declare class FillerConfigService {
190
- /**
191
- * This filler's logging destination. Services resolve their module loggers
192
- * from here rather than from the process-wide context, which is what keeps
193
- * two fillers in one process from writing into each other's sinks.
194
- */
195
- readonly loggers: LoggerContext;
196
- private chainConfigService;
197
- private rpcOverrides;
198
- private bundlerUrls;
199
- private fillerConfig?;
200
- /** Lowercased global allowlist users, or undefined when no global list is configured. */
201
- private allowlistGlobal?;
202
- /** Lowercased per-source allowlist users keyed by state machine id, or undefined when no per-source list is configured. */
203
- private allowlistBySource?;
204
- constructor(chainConfigs: ResolvedChainConfig[], fillerConfig?: FillerConfig, loggers?: LoggerContext);
205
- getAllowlist(): AllowlistConfig | undefined;
206
- /**
207
- * Registers a chain at runtime. `getConfiguredChainIds` derives from the RPC
208
- * override map, so this is what makes a chain "configured" — every address
209
- * lookup already resolves through the SDK's chain registry, which knows the
210
- * chain whether or not this filler was started with it.
211
- *
212
- * Rejects a chain that is already registered: silently replacing its
213
- * endpoints would leave the event monitor scanning the old ones. Use
214
- * {@link setRpcUrls} for that.
215
- */
216
- addChain(chain: ResolvedChainConfig): void;
217
- /** Drops a chain from the configured set. */
218
- removeChain(chainId: number): void;
219
- /**
220
- * Replaces a chain's RPC endpoints. Callers must invalidate the cached viem
221
- * clients (`ChainClientManager.invalidate`) and rebuild the chain's scanner
222
- * afterwards — a client bakes its transport in at construction, so this
223
- * change is invisible to anything already holding one.
224
- */
225
- setRpcUrls(chainId: number, rpcUrls: string[]): void;
226
- setBundlerUrl(chainId: number, bundlerUrl: string): void;
227
- /** Replaces the rebalancing config at runtime; trigger checks read it live. */
228
- setRebalancing(rebalancing: RebalancingConfig$1 | undefined): void;
229
- /** Replaces the allowlist at runtime; takes effect on the next order. */
230
- setAllowlist(allowlist: AllowlistConfig | undefined): void;
231
- /**
232
- * Whether an order from `user` on `chain` (a source state machine id, e.g. "EVM-1")
233
- * may be processed. Returns true when no allowlist is configured. Otherwise the user
234
- * must appear in the global list or the per-source override for that chain; an empty
235
- * merged set rejects every order on that chain.
236
- */
237
- isUserAllowed(user: string, chain: string): boolean;
238
- getChainConfig(chain: string): ChainConfig;
239
- getIntentGatewayAddress(chain: string): `0x${string}`;
240
- getHostAddress(chain: string): `0x${string}`;
241
- getWrappedNativeAssetWithDecimals(chain: string): {
242
- asset: HexString;
243
- decimals: number;
244
- };
245
- getDaiAsset(chain: string): HexString;
246
- getUsdtAsset(chain: string): HexString;
247
- getUsdcAsset(chain: string): HexString;
248
- getUsdcDecimals(chain: string): number;
249
- getSimplexPaymasterAddress(chain: string): HexString | undefined;
250
- getUsdtDecimals(chain: string): number;
251
- /**
252
- * Curated decimals for a token, looked up by address in the SDK's per-chain
253
- * asset table. Returns `undefined` for any token not in that table.
254
- *
255
- * This is the safety net for `ContractInteractionService.getTokenDecimals`
256
- * when the on-chain `decimals()` read fails: the registry already carries the
257
- * correct value for every supported asset (verified on-chain at curation
258
- * time), so a failed read has no business guessing.
259
- */
260
- getAssetDecimalsByAddress(chain: string, address: HexString): number | undefined;
261
- getChainId(chain: string): number;
262
- getConsensusStateId(chain: string): string;
263
- getHyperbridgeChainId(): number;
264
- getHyperbridgeRpcUrl(): string;
265
- getRpcUrl(chain: string): string;
266
- /**
267
- * Returns every user-configured RPC URL for a chain. When multiple URLs are present,
268
- * consumers (e.g. the event monitor) run queries as a quorum — every provider must
269
- * return the same result for the batch to succeed.
270
- */
271
- getRpcUrls(chain: string): string[];
272
- private getChainIdFromStateMachineId;
273
- getUniswapRouterV2Address(chain: string): HexString;
274
- getUniswapV2FactoryAddress(chain: string): HexString;
275
- getUniversalRouterAddress(chain: string): HexString;
276
- getUniswapV3QuoterAddress(chain: string): HexString;
277
- getUniswapV4QuoterAddress(chain: string): HexString;
278
- getUniswapV4PositionManagerAddress(chain: string): HexString;
279
- getUniswapV4PoolManagerAddress(chain: string): HexString;
280
- getUniswapV4StateViewAddress(chain: string): HexString;
281
- getPermit2Address(chain: string): HexString;
282
- getCoingeckoId(chain: string): string | undefined;
283
- getCNgnAsset(chain: string): HexString | undefined;
284
- getAssetBySymbol(chain: string, symbol: string): HexString | undefined;
285
- getCNgnDecimals(chain: string): number | undefined;
286
- getExtAsset(chain: string): HexString | undefined;
287
- getExtDecimals(chain: string): number | undefined;
288
- getConfiguredChainIds(): number[];
289
- getLoggingConfig(): LogLevel | undefined;
290
- getHyperbridgeAddress(): string;
291
- getHyperbridgeWsUrl(): string | undefined;
292
- getSubstratePrivateKey(): string | undefined;
293
- getEntryPointAddress(chain: string): HexString | undefined;
294
- getSolverAccountContractAddress(chain: string): HexString | undefined;
295
- getBundlerUrl(chain: string): string | undefined;
296
- /**
297
- * Get the maxPriorityFeePerGas bump percentage.
298
- * @returns The configured percentage or undefined if not set (default 8% will be used)
299
- */
300
- getMaxPriorityFeePerGasBumpPercent(): number | undefined;
301
- /**
302
- * Get the maxFeePerGas bump percentage.
303
- * @returns The configured percentage or undefined if not set (default 10% will be used)
304
- */
305
- getMaxFeePerGasBumpPercent(): number | undefined;
306
- /**
307
- * Get the full gas fee bump configuration.
308
- * @returns The gas fee bump config or undefined if not set
309
- */
310
- getGasFeeBumpConfig(): GasFeeBumpConfig | undefined;
311
- /**
312
- * Get the LayerZero Endpoint ID for the chain
313
- * Used for USDT0 cross-chain transfers via LayerZero OFT
314
- */
315
- getLayerZeroEid(chain: string): number | undefined;
316
- /**
317
- * Get the USDT0 OFT contract address for the chain
318
- * On Ethereum: OFT Adapter (locks/unlocks USDT)
319
- * On other chains: OFT contract (mints/burns USDT0)
320
- */
321
- getUsdt0OftAddress(chain: string): HexString | undefined;
322
- /**
323
- * Get rebalancing configuration
324
- */
325
- getRebalancingConfig(): RebalancingConfig$1 | undefined;
326
- /**
327
- * Get base balance for a specific chain and asset
328
- * @param chainId Chain ID as number
329
- * @param asset "USDC" or "USDT"
330
- * @returns Base balance as Decimal, or undefined if not configured
331
- */
332
- getBaseBalance(chainId: number, asset: "USDC" | "USDT"): number | undefined;
333
- /**
334
- * Get trigger percentage for rebalancing
335
- * @returns Trigger percentage (0-1), or undefined if not configured
336
- */
337
- getTriggerPercentage(): number | undefined;
338
- /**
339
- * Get target gas units for EntryPoint deposits.
340
- * Defaults to 3,000,000 if not configured.
341
- */
342
- getTargetGasUnits(): bigint;
343
- /**
344
- * How long a signed bid remains executable, in seconds. Defaults to 300 (5 minutes).
345
- *
346
- * A bid is a firm price the placer may take up at a moment of their choosing; this is the
347
- * only thing bounding that window, since the order deadline is placer-controlled and
348
- * Hyperbridge-side retraction does not reach the destination chain.
349
- */
350
- getBidValiditySeconds(): number;
351
- /**
352
- * The order size, in USD, below which `order.fees` must cover the execution cost.
353
- * Defaults to 20. An order at or above it is filled whatever fees it carries.
354
- */
355
- getMinOrderSizeUsd(): number;
356
- /** Ceiling bps above user-requested output. Default 500 (5%). */
357
- getMaxOverfillBps(): bigint;
358
- /** Consecutive clamped evaluations before the strategy halts. Default 3. */
359
- getMaxConsecutiveClamps(): number;
360
- }
361
-
362
- /**
363
- * What one quorum call is allowed to spend.
364
- *
365
- * `deadlineMs` is the ceiling on the call itself: past it, the endpoints still
366
- * outstanding are treated as failures, so they stop voting and the call is
367
- * decided over the endpoints that answered, or fails with a QuorumError naming
368
- * them. Early exit decides as soon as the endpoints still out could not change
369
- * the outcome; the deadline decides when one of them never answers.
370
- *
371
- * `timeoutMs` bounds one endpoint's attempt, and there is only ever one: the
372
- * transports are built with `retryCount: 0`, so an endpoint's whole
373
- * contribution to a call is a single request. That is what keeps the two
374
- * numbers independent — a task cannot approach `deadlineMs` by retrying inside
375
- * itself, so a deadline that fires means something outside the budget went
376
- * wrong rather than an endpoint using the budget as designed.
377
- */
378
- interface QuorumBudget {
379
- timeoutMs: number;
380
- deadlineMs: number;
381
- }
382
- /**
383
- * Wraps multiple viem `PublicClient`s — one per configured RPC URL — and runs
384
- * selected read paths (`getLogs`, `getBlockNumber`, `getTransactionConfirmations`)
385
- * as a **BFT quorum**: a result is accepted only when `quorumThreshold(m)` of the
386
- * `m` endpoints that answered agree on it.
387
- *
388
- * A provider that *answers with divergent data* is never special-cased away — it
389
- * simply fails to join the agreeing group, which is what makes a lying or reorged
390
- * endpoint detectable rather than authoritative.
391
- *
392
- * A provider that *fails* does not vote. A failed request says nothing about the
393
- * chain, so it must not outvote the endpoints that agree: the threshold guards
394
- * against endpoints that answer wrongly, and every answer still counts. Six
395
- * endpoints with two failing decide over the other four, three of which must
396
- * agree. The price, worth stating plainly, is that a call can be decided by fewer
397
- * endpoints than the operator configured; at the limit, one endpoint answering
398
- * while the rest fail decides alone. The voters are still exclusively the
399
- * operator's own.
400
- *
401
- * The bench is the one state this class keeps, and {@link benchFor} decides who
402
- * goes on it: a request-rate limit ({@link isSuspendableRateLimit}), a response
403
- * that is not JSON ({@link isMalformedResponse}), or a head behind the quorum's
404
- * ({@link isStaleHead}). All three share the property that earns it — the
405
- * endpoint will keep failing the same way for a while — so a benched endpoint is
406
- * not asked for its window: asking would spend its quota, or hold the call open
407
- * for an answer already known to fail. When every endpoint is benched, all are
408
- * queried again; there is nobody left to prefer. Every other failure is per
409
- * call, and the endpoint is asked again next time.
410
- *
411
- * The constructor validates that URLs resolve to distinct hostnames so a "quorum"
412
- * isn't secretly the same upstream in disguise.
413
- */
414
- declare class QuorumPublicClient {
415
- readonly clients: PublicClient[];
416
- readonly rpcUrls: string[];
417
- /**
418
- * The quorum bar when every endpoint answers: `quorumThreshold(n)` over the
419
- * full set. Each call's bar is `quorumThreshold` over the endpoints that answer
420
- * it, so this is the most it can be.
421
- */
422
- readonly threshold: number;
423
- private readonly logger?;
424
- /**
425
- * Rate-limit bench state, shared across every instance and keyed by endpoint
426
- * URL. The bench describes the ENDPOINT, not the wrapper: the scanner and the
427
- * confirmation poller construct separate clients over the same URLs, and a
428
- * throttle one of them observed is a throttle for both. Instance state would
429
- * halve the relief — and evaporate whenever `setRpcUrls` rebuilds a client.
430
- */
431
- private static benchedUntil;
432
- /** Test hook: forgets every bench. Production never calls it. */
433
- static clearAllSuspensions(): void;
434
- /** What this client's calls may spend; see {@link QuorumBudget}. */
435
- private readonly deadlineMs;
436
- constructor(chainId: number, rpcUrls: string[], loggers?: LoggerContext, budget?: QuorumBudget);
437
- get size(): number;
438
- /** Endpoints currently benched, by URL. See {@link benchFor} for the reasons. */
439
- suspended(): string[];
440
- /**
441
- * The endpoints this call queries: everyone not benched. A benched endpoint is
442
- * dropped outright: it answers nothing while throttled, so asking it could only
443
- * cost a request. With every endpoint benched, everyone is queried again; there
444
- * is nobody left to prefer.
445
- */
446
- private participants;
447
- /**
448
- * Records a task failure against its endpoint. Only the failures
449
- * {@link benchFor} names carry state: everything else stays per-call, as the
450
- * class doc promises.
451
- * The bench is the one silent state transition this class has, so it warns —
452
- * an operator whose bar just dropped from 2-of-2 to 1-of-1 should not need a
453
- * QuorumError to find out.
454
- */
455
- private noteFailure;
456
- /**
457
- * Highest block a quorum of endpoints have all indexed: the highest B such
458
- * that `threshold` endpoints report head ≥ B. Returns null when fewer than
459
- * `threshold` endpoints have responded.
460
- */
461
- private quorumHead;
462
- /**
463
- * Settles per-client tasks incrementally and resolves the moment the
464
- * evaluator can decide from the responses seen so far — the hot path never
465
- * blocks on the slowest provider (a hung endpoint with a 30s timeout × 3
466
- * retries would otherwise stall every confirmation poll). Stragglers settle
467
- * out of band and are ignored. `tryDecide` returns undefined to keep
468
- * waiting; once every task has settled, `finalize` must decide (it throws
469
- * the QuorumError when the full response set still has no quorum).
470
- *
471
- * Both are handed the bar: the BFT threshold over the endpoints that have not
472
- * failed, answered or still out. A failure lowers it rather than counting
473
- * against it. Counting the pending endpoints keeps an early decision to a bar
474
- * no lower than the full response set would get, so a vote it did not wait for
475
- * could never have reversed it.
476
- *
477
- * `deadlineMs` bounds the other direction: when the bar is NOT reachable
478
- * from the fast responders, the call used to wait out every straggler, and
479
- * the caller's own retry loop multiplied that into minutes of a chain not
480
- * being scanned at all, silently. At the deadline the outstanding endpoints
481
- * are treated as failures — named in the QuorumError, so the operator sees
482
- * which ones held the call up — and `finalize` decides over the endpoints
483
- * that answered.
484
- */
485
- private settleUntilQuorum;
486
- /**
487
- * Highest block head backed by a quorum of the endpoints that answer. Throws
488
- * {@link QuorumError} when none do.
489
- */
490
- getBlockNumber(): Promise<bigint>;
491
- /**
492
- * Confirmation count for a transaction under the quorum.
493
- *
494
- * Every endpoint is asked for the receipt and its head. A "receipt not found"
495
- * answer is a valid **no** vote — the endpoint is responsive but does not see
496
- * the transaction (not yet propagated, or reorged out) — so it counts toward
497
- * responsiveness but joins no inclusion group. The transaction is confirmed
498
- * only when a quorum agrees on the same `(blockHash, blockNumber)`; the depth
499
- * is then the quorum head of that agreeing group. A minority still serving a
500
- * reorged/fabricated receipt can never reach the quorum.
501
- *
502
- * Throws {@link QuorumError} when no inclusion reaches the quorum — including
503
- * the ordinary window where the tx is not yet mined on enough endpoints.
504
- */
505
- getTransactionConfirmations({ hash }: {
506
- hash: Hash;
507
- }): Promise<bigint>;
508
- /**
509
- * Fetches logs from every endpoint in parallel and returns the result once a
510
- * quorum agrees. Fails with {@link QuorumError} otherwise.
511
- *
512
- * Generics mirror `PublicClient.getLogs` so caller-side inference (event decoding,
513
- * strict mode, pending vs mined) is preserved end-to-end.
514
- */
515
- 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>>;
516
- /**
517
- * `skipped` is the participants() snapshot for THIS call, not the suspension
518
- * state at throw time — a call that rides out its deadline can outlive a
519
- * bench window, and the message must explain
520
- * who was excluded when the call started, not who would be excluded now.
521
- */
522
- private describeResponders;
523
- private formatFailures;
524
- }
525
-
526
- /**
527
- * The signing contract a solver runs on, and the config shapes the bundled
528
- * implementations take.
529
- *
530
- * `Signer` is the whole surface: implement it and Simplex signs with your
531
- * backend. Nothing here is viem-shaped, so a consumer never has to match this
532
- * package's viem version to satisfy it — `viemSigner` adapts a viem account into
533
- * one, and simplex builds the account its wallet clients need from whatever it
534
- * is given.
535
- */
536
-
537
- interface MpcVaultSignerConfig {
538
- apiToken: string;
539
- vaultUuid: string;
540
- accountAddress: HexString;
541
- callbackClientSignerPublicKey: string;
542
- /**
543
- * gRPC target address. Defaults to "api.mpcvault.com:443".
544
- * Replaces the previous REST `baseUrl` field.
545
- */
546
- grpcTarget?: string;
547
- /**
548
- * Destination for signing lifecycle logs. Defaults to the process-wide
549
- * context, which an embedded filler's `SimplexOptions.logger` never sees —
550
- * pass your own to capture retries and failures in that setup.
551
- */
552
- logger?: Logger;
553
- /** Channel credentials override for tests against a plaintext server. Defaults to TLS. */
554
- credentials?: ChannelCredentials;
555
- }
556
- declare enum SignerType {
557
- PrivateKey = "privateKey",
558
- MpcVault = "mpcVault",
559
- Turnkey = "turnkey",
560
- SecretPhrase = "secretPhrase"
561
- }
562
- interface TurnkeySignerConfig {
563
- organizationId: string;
564
- apiPublicKey: string;
565
- apiPrivateKey: string;
566
- signWith: string;
567
- }
568
- interface PrivateKeySignerConfig {
569
- key: HexString;
570
- }
571
- interface SecretPhraseSignerConfig {
572
- /** BIP-39 mnemonic from the English wordlist. */
573
- phrase: string;
574
- /**
575
- * Which wallet under the phrase is the hot wallet: the last component of
576
- * `m/44'/60'/0'/0/i`. Defaults to 0.
577
- */
578
- accountIndex?: number;
579
- }
580
- /**
581
- * The `[simplex.signer]` block of the TOML file the binary reads — a tagged
582
- * union over the bundled backends. `createSigner` turns one into a {@link Signer};
583
- * `Simplex.start` takes the `Signer`, not this.
584
- */
585
- type SignerConfig = ({
586
- type: SignerType.PrivateKey;
587
- } & PrivateKeySignerConfig) | ({
588
- type: SignerType.MpcVault;
589
- } & MpcVaultSignerConfig) | ({
590
- type: SignerType.Turnkey;
591
- } & TurnkeySignerConfig) | ({
592
- type: SignerType.SecretPhrase;
593
- } & SecretPhraseSignerConfig);
594
- /**
595
- * A secp256k1 signature in split form, which is how EIP-7702 and raw digests
596
- * consume it: `r` and `s` as 32-byte 0x-hex, and `yParity` strictly 0 or 1.
597
- * A backend that hands back the legacy `v` (27/28) should subtract 27 — the
598
- * delegation path rejects anything else rather than letting viem mis-encode it.
599
- */
600
- interface Signature {
601
- r: HexString;
602
- s: HexString;
603
- yParity: number;
604
- }
605
- /**
606
- * An EIP-712 payload, as `eth_signTypedData_v4` defines it.
607
- *
608
- * `domain.chainId` is the only field a signing backend is likely to read for
609
- * itself: the digest covers it, and backends that scope a signing request to a
610
- * chain (MPCVault) take it from here rather than from a separate argument.
611
- *
612
- * **List `EIP712Domain` in `types` on any payload you build.** viem ignores it
613
- * when hashing locally, so a payload without it looks correct against a private
614
- * key. Backends that hash server-side from the JSON derive the domain type from
615
- * that entry — MPCVault and Turnkey both do — and without it they sign a
616
- * different digest than the one your verifier checks. The failure is quiet: the
617
- * signature is well-formed and recovers to an address that is not yours.
618
- * `CryptoUtils.packedUserOpTypedData` in the sdk sets it for this reason.
619
- */
620
- interface TypedDataPayload {
621
- domain?: {
622
- name?: string;
623
- version?: string;
624
- chainId?: number | bigint;
625
- verifyingContract?: HexString;
626
- salt?: HexString;
627
- };
628
- types: Record<string, readonly {
629
- name: string;
630
- type: string;
631
- }[]>;
632
- primaryType: string;
633
- message: Record<string, unknown>;
634
- }
635
- /** EIP-7702 authorization tuple, signed, as a set-code transaction carries it. */
636
- interface Eip7702Authorization$1 extends Signature {
637
- chainId: number;
638
- address: HexString;
639
- nonce: number;
640
- }
641
- /**
642
- * A transaction to sign, handed to backends that implement
643
- * {@link Signer.signTransaction}. Field names follow the JSON-RPC transaction
644
- * object; simplex only ever sends EIP-1559 transfers and EIP-7702 set-code
645
- * transactions, so those are the fields modelled here.
646
- */
647
- interface SignerTransaction {
648
- chainId: number;
649
- type?: string;
650
- to?: HexString;
651
- value?: bigint;
652
- data?: HexString;
653
- nonce?: number;
654
- gas?: bigint;
655
- maxFeePerGas?: bigint;
656
- maxPriorityFeePerGas?: bigint;
657
- /** Present on EIP-7702 set-code transactions, and on nothing else. */
658
- authorizationList?: Eip7702Authorization$1[];
659
- }
660
- /**
661
- * Everything a solver asks of the key that owns its funds. Pass an
662
- * implementation to `Simplex.start({ signer })`.
663
- *
664
- * Three operations, one identity. Each operation names what is being authorised
665
- * — a typed-data payload, an EIP-7702 authorization, a transaction — rather than
666
- * a digest, so a custody backend's policy engine can read what it is signing.
667
- *
668
- * A backend that only signs digests does not implement this by hand: pass its
669
- * `sign(hash)` to {@link digestSigner}, which does the hashing and serialising
670
- * and returns a `Signer`.
671
- */
672
- interface Signer {
673
- /** The address every fill, bid and delegation is attributed to. */
674
- readonly address: HexString;
675
- /** Names the backend in logs — `"privateKey"`, `"turnkey"`, or whatever names yours. */
676
- readonly mode: string;
677
- /**
678
- * EIP-712. Signs every bid UserOperation and every paymaster permit.
679
- *
680
- * Returns the 65-byte `r ‖ s ‖ v` signature as 0x-hex (132 characters), with
681
- * `v` 27 or 28 — exactly what `eth_signTypedData_v4` and viem's
682
- * `signTypedData` produce. On-chain validation recovers against this form.
683
- */
684
- signTypedData(typedData: TypedDataPayload): Promise<HexString>;
685
- /**
686
- * Signs an EIP-7702 authorization tuple — the delegation that makes this
687
- * account a solver. Backends with a structured encoding for it (Turnkey) use
688
- * that; others sign `keccak256(0x05 ‖ rlp([chainId, contractAddress, nonce]))`.
689
- *
690
- * Returns the SPLIT signature — see {@link Signature}: `yParity` must be 0 or
691
- * 1, not the legacy `v`. This is the one method that does not return hex,
692
- * because the authorization is embedded field-by-field in the transaction.
693
- */
694
- signAuthorization(auth: AuthorizationRequest): Promise<Signature>;
695
- /**
696
- * Signs and serialises a transaction. Backends that take transactions
697
- * (MPCVault's vault API, Turnkey's transaction payloads) keep the transaction
698
- * legible to their policy engine; others serialise it and sign the digest.
699
- *
700
- * Returns the WHOLE SIGNED TRANSACTION — typed-envelope RLP as
701
- * `eth_sendRawTransaction` takes it (`0x02…` for EIP-1559, `0x04…` for
702
- * set-code), not a bare signature. viem's `serializeTransaction(tx, signature)`
703
- * produces it from a signed digest.
704
- */
705
- signTransaction(tx: SignerTransaction): Promise<HexString>;
706
- }
707
- /**
708
- * A {@link Signer} backed by a key tree rather than a single key, so it can hand
709
- * out signers for sibling wallets.
710
- */
711
- interface DerivingSigner extends Signer {
712
- /** Index of the wallet this signer itself signs with. */
713
- readonly accountIndex: number;
714
- /** The signer for the wallet at `index`, which may be this signer's own. */
715
- derive(index: number): Signer;
716
- }
717
- /** The tuple an EIP-7702 authorization signature covers. */
718
- interface AuthorizationRequest {
719
- chainId: number;
720
- /** The contract the account delegates to. */
721
- contractAddress: HexString;
722
- nonce: number;
723
- }
724
-
725
105
  /**
726
106
  * Builds the signer a `[simplex.signer]` TOML block describes.
727
107
  *
@@ -813,140 +193,223 @@ declare function mpcVaultSigner(config: MpcVaultSignerConfig): Signer;
813
193
  */
814
194
  declare function turnkeySigner(config: TurnkeySignerConfig): Promise<Signer>;
815
195
 
816
- /**
817
- * Manages chain clients for different operations
818
- */
819
- declare class ChainClientManager {
820
- private signer;
821
- /**
822
- * The viem account every wallet client is built on, derived from the signer
823
- * once. `Signer` is viem-free by design, so this is where the two meet.
824
- */
825
- private account;
826
- private configService;
827
- private clientFactory;
828
- private quorumClients;
829
- /** This filler's logging destination, relayed from the config service. */
830
- get loggers(): LoggerContext;
831
- constructor(configService: FillerConfigService, signer?: Signer);
832
- /**
833
- * Drops every cached client for a chain. Call after the chain's RPC URLs
834
- * change (or when the chain is removed) so the next lookup rebuilds against
835
- * the current endpoints — transports are baked into a client at construction.
836
- *
837
- * Callers that hold a client reference across the invalidation keep talking to
838
- * the old endpoints; `EventMonitor` re-reads its quorum client per chain
839
- * rebuild for exactly this reason.
840
- */
841
- invalidate(chain: string): void;
842
- getPublicClient(chain: string): PublicClient;
843
- /**
844
- * Quorum client for consensus-critical reads (cross-chain confirmation
845
- * counting; the scanner builds its own instance over its own chain list).
846
- * Built from the operator's configured endpoints and cached per chain. The
847
- * instances are separate but their rate-limit bench is shared state keyed by
848
- * endpoint URL, so a throttle either observes benches the endpoint for both.
849
- */
850
- getQuorumClient(chain: string): QuorumPublicClient;
851
- getWalletClient(chain: string): WalletClient<Transport, Chain, Account>;
852
- getAccount(): Account;
853
- getSigner(): Signer;
854
- getClientsForOrder(order: Order): {
855
- destClient: PublicClient;
856
- sourceClient: PublicClient;
857
- walletClient: WalletClient<Transport, Chain, Account>;
858
- };
196
+ /** Native gas a chain needs before its paymaster can sponsor the wallet's transactions. */
197
+ interface NativeSetupNeed {
198
+ /** Native to send, in whole units: twice the current cost, so a gas-price rise does not leave it short. */
199
+ required: number;
200
+ /** The fee token whose one-time Permit2 approval has to be paid in native. */
201
+ feeToken: "USDC" | "USDT";
859
202
  }
860
203
 
861
- /**
862
- * Raw EIP-7702 authorization, as produced by the delegation flow. Attached to the
863
- * bundler UserOp so a not-yet-delegated EOA can be delegated in the same op.
864
- */
865
- interface Eip7702Authorization {
204
+ interface ChainBalanceRow {
866
205
  chainId: number;
867
- address: HexString;
868
- nonce: number;
869
- r: HexString;
870
- s: HexString;
871
- yParity: number;
206
+ native?: {
207
+ symbol: string;
208
+ amount: number;
209
+ };
210
+ usdc?: number;
211
+ usdt?: number;
212
+ exotics?: Array<{
213
+ symbol: string;
214
+ amount: number;
215
+ }>;
216
+ /** Canonical token balances, including funds held in configured ERC-4626 vaults. */
217
+ assets: AssetBalanceRow[];
218
+ /** Set when the paymaster cannot sponsor this chain until the wallet holds some native gas. */
219
+ nativeSetup?: NativeSetupNeed;
872
220
  }
873
- interface UserOpGasLimits {
874
- verificationGasLimit: bigint;
875
- callGasLimit: bigint;
876
- preVerificationGas: bigint;
221
+ type AssetBalanceStatus = "fresh" | "partial" | "unavailable";
222
+ interface VaultBalanceRow {
223
+ address: string;
224
+ position: number;
225
+ available: number;
226
+ /** False while the vault reports `maxDeposit(solver) == 0`; sweeps into it are idle until it reopens. */
227
+ acceptsDeposits: boolean;
877
228
  }
878
- interface SponsoredUserOpRequest {
879
- chain: string;
880
- /** ERC-7821 batch (or "0x" for a no-op, e.g. delegation). */
881
- callData: HexString;
882
- /**
883
- * Attach when the EOA still needs delegating in this op. Pass a factory rather
884
- * than a pre-signed tuple: building paymaster data can send an approve tx from
885
- * the authority EOA (the Simplex bootstrap approve to Permit2), and an authorization signed before
886
- * that tx embeds a stale nonce the bundler will reject. The factory runs only
887
- * after paymaster data is built.
888
- */
889
- eip7702Auth?: Eip7702Authorization | (() => Promise<Eip7702Authorization>);
890
- /** EntryPoint nonce key. Defaults to 0. */
891
- nonceKey?: bigint;
229
+ interface AssetBalanceRow {
230
+ address: string;
231
+ symbol: string;
232
+ /** Direct underlying balance held by the solver account. Null when its RPC read failed. */
233
+ wallet: number | null;
234
+ /** Configured wallet floor that must remain liquid. */
235
+ walletReserve: number;
236
+ /** Total solver-owned underlying represented by vault shares. */
237
+ vaultPosition: number;
238
+ /** Vault assets that can be withdrawn now, after pending-fill reservations. */
239
+ vaultAvailable: number;
240
+ /** Wallet plus vault position. Null when any required source was unavailable. */
241
+ total: number | null;
242
+ /** Spendable wallet above reserve plus currently withdrawable vault assets. */
243
+ available: number | null;
244
+ vaults: VaultBalanceRow[];
245
+ status: AssetBalanceStatus;
246
+ }
247
+ type BalanceIssueSource = "chain" | "native" | "token" | "vault";
248
+ interface BalanceIssue {
249
+ chainId?: number;
250
+ source: BalanceIssueSource;
251
+ asset?: string;
252
+ message: string;
253
+ }
254
+ type BalanceSnapshotStatus = "loading" | "fresh" | "partial" | "unavailable";
255
+ interface HyperbridgeBalance {
256
+ address: string;
257
+ free: number;
258
+ reserved: number;
259
+ }
260
+ interface BalanceSnapshot {
261
+ /** null until the first refresh attempt completes. */
262
+ updatedAt: number | null;
263
+ status: BalanceSnapshotStatus;
264
+ chains: ChainBalanceRow[];
265
+ issues: BalanceIssue[];
266
+ hyperbridge?: HyperbridgeBalance;
267
+ }
268
+ /** Narrow read-only seam implemented by the vault funding planner. */
269
+ interface VaultBalanceSource {
270
+ getBalanceSnapshot(chain?: string): Promise<VaultBalancePosition[]>;
271
+ }
272
+ interface BalanceProviderOptions {
273
+ chainClientManager: ChainClientManager;
274
+ configService: FillerConfigService;
275
+ fillerAddress: string;
892
276
  /**
893
- * Explicit gas limits. Provide them to skip bundler estimation — required for
894
- * the not-yet-delegated EIP-7702 case, where the account has no code to
895
- * simulate so estimation is unreliable. When omitted, limits are estimated.
277
+ * Token addresses to read beyond USDC and USDT, by state machine id: every token of the
278
+ * orderbook's books. Asked on each refresh, so a book the orderbook adds is picked up
279
+ * without a restart.
896
280
  */
897
- gas?: UserOpGasLimits;
281
+ tokens: () => Promise<Record<string, string[]>>;
898
282
  /**
899
- * Lets the paymaster fall back to an EIP-2612 permit when the fee token has no
900
- * Permit2 allowance yet. Only a first-time delegation sets this — it carries the
901
- * `approve(Permit2, max)` in its own callData, so the permit buys the one op that
902
- * makes every later Permit2 op possible without native.
283
+ * The filler's Hyperbridge connection, reused rather than dialled again. A second socket to the
284
+ * same node doubles the reconnect traffic during an outage for a balance read that is only
285
+ * needed once a minute.
903
286
  */
904
- permitBootstrap?: boolean;
287
+ hyperbridge?: Promise<IntentsCoprocessor>;
288
+ substratePrivateKey?: string;
289
+ refreshIntervalMs?: number;
290
+ vaultBalances?: VaultBalanceSource;
905
291
  }
906
292
  /**
907
- * Submits self-initiated, paymaster-sponsored UserOperations through the bundler,
908
- * so the solver pays gas in stablecoins rather than native token. Shared by the
909
- * delegation setup and the vault sweep/redeem paths.
910
- *
911
- * Submission contract (so callers can safely fall back to a native tx without
912
- * risking a double-execution):
913
- * - Returns `null` when the op was **never submitted** — paymaster/bundler not
914
- * configured, insufficient USDC, preparation failed (approve tx or signing), or
915
- * the bundler rejected `eth_sendUserOperation` outright (it never entered the
916
- * mempool). The caller may fall back to native.
917
- * - Returns `{ txHash }` once a receipt is observed.
918
- * - **Throws** only when the op **was submitted** but no receipt arrived. The
919
- * caller must NOT fall back (the op may still land) — retry on the next cycle.
293
+ * Periodically collects wallet balances (native, USDC, USDT and the other tokens
294
+ * of the orderbook's books per chain, plus the BRIDGE balance of the substrate
295
+ * account) into a plain snapshot,
296
+ * consumed by the UI JSON API and `wallet.balances()`.
920
297
  */
921
- declare class UserOpSender {
922
- private readonly clientManager;
923
- private readonly configService;
924
- private readonly signer;
298
+ declare class BalanceProvider extends EventEmitter {
299
+ private snapshot;
300
+ private stopped;
301
+ private refreshInterval?;
302
+ private hyperbridgeInterval?;
925
303
  private logger;
926
- constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer);
927
- /** True when a sponsored UserOp is even possible on this chain (paymaster + bundler configured). */
928
- canSponsor(chain: string): boolean;
929
- trySendSponsored(req: SponsoredUserOpRequest): Promise<{
930
- txHash: HexString;
931
- } | null>;
932
- /**
933
- * Estimates account gas limits via the bundler. The op is signed first so the
934
- * estimation simulation passes the account's ECDSA validation. Falls back to
935
- * generous constants when the bundler estimate is unavailable.
936
- */
937
- private estimateGas;
938
- private buildSignedUserOp;
939
- /**
940
- * Mirrors the gas-price selection used by the delegation and fill paths: Pimlico's and
941
- * Alchemy's own pricing, otherwise the chain's gas price raised to the priority fee a
942
- * rundler bundler requires, when the bundler gives one.
943
- */
944
- private getGasPrice;
945
- private sendBundlerRpc;
946
- private waitForUserOpReceipt;
304
+ private options;
305
+ private intervalMs;
306
+ constructor(options: BalanceProviderOptions);
307
+ start(): Promise<void>;
308
+ stop(): void;
309
+ getSnapshot(): BalanceSnapshot;
310
+ refresh(): Promise<BalanceSnapshot>;
311
+ private collectChain;
312
+ private addConfiguredStable;
313
+ private trackHyperbridgeBalance;
947
314
  }
948
315
 
949
- /** One vault's share of a transaction, with the metadata the ledger row needs. */
316
+ /** The stretch of time a summary covers. Each is cut into buckets of one size. */
317
+ type ProfitPeriod = "7d" | "30d" | "12w" | "12m" | "all";
318
+ type ProfitBucket = "day" | "week" | "month" | "year";
319
+ /** What a stretch of fills came to, in dollars at the latest rate. Unpriced tokens are left out. */
320
+ interface ProfitFigures {
321
+ /** What the stretch's fills added to inventory, less what they took out of it. */
322
+ profitUsd: number;
323
+ /** The inventory held when the stretch began. Null when nothing within reach of then records it. */
324
+ startInventoryUsd: number | null;
325
+ /** `profitUsd` over `startInventoryUsd`, as a percentage. Null without a starting inventory. */
326
+ returnPct: number | null;
327
+ /**
328
+ * What moved in and out over the stretch other than through fills, net: deposits, withdrawals,
329
+ * fees and vault yield. Null when the indexer has no reading for both ends of the stretch.
330
+ */
331
+ transfersUsd: number | null;
332
+ boughtUsd: number;
333
+ soldUsd: number;
334
+ buys: number;
335
+ sells: number;
336
+ }
337
+ interface ProfitBucketFigures extends ProfitFigures {
338
+ /** When the bucket starts, in milliseconds since the epoch. */
339
+ start: number;
340
+ }
341
+ /** One book's buys against its sells over the period, in the book's own tokens. */
342
+ interface BookProfit {
343
+ book: string;
344
+ base: string;
345
+ quote: string;
346
+ /** Base bought and sold in the period. */
347
+ bought: number;
348
+ sold: number;
349
+ /** Quote per base, averaged over the period's buys and its sells. Null for a side with none. */
350
+ averageBuy: number | null;
351
+ averageSell: number | null;
352
+ /** How far the average sell sits above the average buy, as a percentage. Null without both. */
353
+ spreadPct: number | null;
354
+ /** What the period's fills did to the base and the quote held: positive is more than before. */
355
+ baseChange: number;
356
+ quoteChange: number;
357
+ /** The rate the book's tokens are valued at: midway between its latest buy and latest sell. */
358
+ rate: number | null;
359
+ /** Both changes in dollars. Null when the book's tokens have no dollar price. */
360
+ profitUsd: number | null;
361
+ }
362
+ /** The inventory a period began with, token by token. */
363
+ interface StartingInventory {
364
+ /** The instant it describes, in milliseconds since the epoch. */
365
+ at: number;
366
+ /** A stored snapshot, or the balances as they stand now. Either is adjusted by what happened between. */
367
+ source: "snapshot" | "balances";
368
+ /** When that record was taken. */
369
+ recordedAt: number;
370
+ usd: number;
371
+ tokens: Array<{
372
+ symbol: string;
373
+ amount: number;
374
+ usd: number | null;
375
+ }>;
376
+ }
377
+ interface Profitability {
378
+ period: ProfitPeriod;
379
+ bucket: ProfitBucket;
380
+ /** The start of the first bucket and the moment the summary was taken, in milliseconds. */
381
+ from: number;
382
+ to: number;
383
+ totals: ProfitFigures;
384
+ /** One entry per bucket, oldest first, including buckets nothing happened in. */
385
+ series: ProfitBucketFigures[];
386
+ books: BookProfit[];
387
+ /** Null when there is no snapshot, and the period began more than {@link BALANCES_REACH_MS} ago. */
388
+ startInventory: StartingInventory | null;
389
+ /** Fills in the period priced at their order's rate, because they kept no record of what they took in. */
390
+ estimatedFills: number;
391
+ /** Tokens the period touched that have no dollar price, and so are in none of the dollar figures. */
392
+ unpricedTokens: string[];
393
+ }
394
+
395
+ /**
396
+ * Wire contracts between the UI server and the browser bundle. The server
397
+ * types its response literals with these and `ui/src` imports them type-only,
398
+ * so drift on either side is a compile error. Browser-safe: no runtime
399
+ * dependencies beyond constants.
400
+ */
401
+
402
+ /** Signer work that must finish before the desktop may replace the bundled solver. */
403
+ interface SolverWork {
404
+ queuedEvaluations: number;
405
+ evaluating: number;
406
+ queuedFills: number;
407
+ activeFills: number;
408
+ retractions: number;
409
+ rebalancing: number;
410
+ }
411
+
412
+ /** One vault's share of a transaction, with the metadata the ledger row needs. */
950
413
  interface DescribedMovement {
951
414
  kind: "sweep" | "redeem";
952
415
  vault: string;
@@ -1270,738 +733,110 @@ interface OrderScanner$1 {
1270
733
 
1271
734
  /**
1272
735
  * Per-instance event bus for one filler.
1273
- *
1274
- * This used to own a block-scan loop per chain. It no longer does: scanning is
1275
- * identical work for every filler on a chain, so it moved to a shared
1276
- * {@link OrderSource} and this class subscribes to it. Everything else stays —
1277
- * the monitor is the filler's event bus, not merely a source. `IntentFiller`
1278
- * emits `orderTiming`, `orderSkipped`, `orderFilled`, `orderExecuted` and
1279
- * `rebalanceExecuted` on it, `ActivityRecorder` attaches to it, and `Simplex`
1280
- * re-exposes it as the public event surface.
1281
- *
1282
- * Three things must stay on this side of the boundary:
1283
- * - the `OrderFilled` filler-address filter. `filler` is `indexed: false` on
1284
- * both `OrderFilled` and `PartialFill`, so it can never be a topic filter;
1285
- * every consumer receives every fill and narrows it locally.
1286
- * - the leg filter. An order reaches the filler only when its inputs and
1287
- * outputs pair up leg by leg; the strategy then bids on each leg one of its
1288
- * limit orders serves.
1289
- * - de-duplication. Exactly-once used to be emergent — a private monotonic
1290
- * cursor made a repeat impossible. A shared feed can replay from a cursor
1291
- * after a reconnect, so the seen-set below is what preserves the property
1292
- * the filler has always assumed.
1293
- */
1294
- declare class EventMonitor extends EventEmitter {
1295
- private listening;
1296
- private configService;
1297
- private fillerAddress;
1298
- private logger;
1299
- private orderScanner;
1300
- private subscription?;
1301
- /** Chain ids this filler is configured for; the scanner may carry more. */
1302
- private chains;
1303
- /** Order ids already delivered, newest last. Bounds the memory a replay can cost. */
1304
- private seen;
1305
- private seenOrder;
1306
- constructor(chainConfigs: ChainConfig[], configService: FillerConfigService, fillerAddress: HexString, orderScanner: OrderScanner$1);
1307
- /** Marks a chain as one this filler cares about. */
1308
- private registerChain;
1309
- /**
1310
- * Attaches to the scanner. One subscription covers every chain it carries — a
1311
- * scanner shared with other fillers may cover more chains than this one is
1312
- * configured for, so events are matched on `chainId` here.
1313
- */
1314
- startListening(): Promise<void>;
1315
- private handleOrder;
1316
- private handleFill;
1317
- /**
1318
- * Starts matching events for a chain added after boot.
1319
- *
1320
- * The scanner must already carry the chain. `Simplex` adds it to a scanner it
1321
- * owns; when the scanner was supplied by the caller it stays the caller's to
1322
- * manage, so that another filler reading from it is never surprised by a
1323
- * chain appearing or vanishing underneath it.
1324
- */
1325
- addChain(chainId: number): Promise<void>;
1326
- /** Stops matching events for a chain. The scanner keeps scanning it for others. */
1327
- removeChain(chainId: number): Promise<void>;
1328
- /**
1329
- * No-op against a scanner: endpoints belong to the scanner, not to a filler
1330
- * reading from it. `Simplex.chains.setRpcUrls` swaps the endpoints on a scanner
1331
- * it owns and rejects the call on one it does not.
1332
- */
1333
- rebuildChain(chainId: number): Promise<void>;
1334
- /** Events dropped because this filler could not keep up with the scanner. */
1335
- droppedEvents(): number;
1336
- stopListening(): Promise<void>;
1337
- }
1338
-
1339
- /**
1340
- * An execution outcome that can also report a still-pooled bid.
1341
- *
1342
- * `submitBid` can time out with the extrinsic still in Hyperbridge's pool. That
1343
- * is neither success nor failure: the extrinsic may yet land and reserve a
1344
- * deposit, so the bid record has to be reclaimable even though `success` is
1345
- * false. Widened here rather than on the SDK's `ExecutionResult` so simplex does
1346
- * not need an unreleased SDK.
1347
- */
1348
- interface FillResult extends ExecutionResult {
1349
- pending?: boolean;
1350
- /**
1351
- * The identifier Hyperbridge files the bid under, once its op was built:
1352
- * `keccak256` of its calldata. It tells the bids on one order apart, and retracting the bid names it.
1353
- */
1354
- bid?: HexString;
1355
- }
1356
-
1357
- interface FillerStrategy {
1358
- name: string;
1359
- canFill(order: Order): Promise<boolean>;
1360
- calculateProfitability(order: Order): Promise<number>;
1361
- executeOrder(order: Order, hyperbridge?: IntentsCoprocessor): Promise<FillResult>;
1362
- /**
1363
- * Optional hook for strategies to provide a USD value for the full input basket.
1364
- * Returns null when the strategy cannot or does not want to price the order.
1365
- */
1366
- getOrderUsdValue?(order: Order): Promise<{
1367
- inputUsd: Decimal;
1368
- } | null>;
1369
- /**
1370
- * Optional confirmation policy for this strategy.
1371
- * If absent, no confirmation waiting is required (e.g. same-chain strategies).
1372
- */
1373
- confirmationPolicy?: {
1374
- getConfirmationBlocks: (chainId: number, amountUsd: number) => number;
1375
- };
1376
- }
1377
-
1378
- /**
1379
- * A user-supplied asset entry in the `[assets]` TOML table: symbol → chain
1380
- * state machine id → contract address.
1381
- *
1382
- * ```toml
1383
- * [assets.BRZ]
1384
- * "EVM-1" = "0x..."
1385
- * "EVM-137" = "0x..."
1386
- * ```
1387
- */
1388
- type AssetDefinition = Record<string, HexString>;
1389
- /**
1390
- * The subset of `FillerConfigService` used to resolve built-in symbols to
1391
- * per-chain addresses from the SDK chain registry.
1392
- */
1393
- interface BuiltinAssetResolver {
1394
- getUsdcAsset(chain: string): HexString;
1395
- getUsdtAsset(chain: string): HexString;
1396
- getDaiAsset(chain: string): HexString;
1397
- getCNgnAsset(chain: string): HexString | undefined;
1398
- /** Generic per-chain lookup into the SDK chain registry's asset table. */
1399
- getAssetBySymbol(chain: string, symbol: string): HexString | undefined;
1400
- }
1401
- /**
1402
- * Symbols pegged to 1 USD. They are pinned at $1 when an order's USD value is
1403
- * derived from the limit orders' rates (see `usdFactorsFrom`), which is what
1404
- * sizes confirmation depth. Trade pricing never uses this as a price.
1405
- */
1406
- declare const USD_STABLE_SYMBOLS: ReadonlySet<string>;
1407
- /** Normalises a symbol for lookups: trimmed, uppercased. "cNGN" ≡ "CNGN". */
1408
- declare function normalizeSymbol(symbol: string): string;
1409
- /** Every symbol the registry ships without user configuration. */
1410
- declare function registrySymbols(): string[];
1411
- /** Whether `symbol` ships with the registry (built-in or curated). Pure — no chain resolution. */
1412
- declare function isRegistrySymbol(symbol: string): boolean;
1413
- /**
1414
- * Validates the `[assets]` table; throws a descriptive error on the first
1415
- * invalid entry. Side-effect free — safe to call at config-parse time and again
1416
- * at registry construction.
1417
- */
1418
- declare function validateAssetDefinitions(assets: Record<string, AssetDefinition>): void;
1419
- /**
1420
- * Symbol → contract address registry backing the `[[pairs]]` configuration.
1421
- *
1422
- * Resolution merges two layers, most specific winning:
1423
- * 1. the user's `[assets]` table — an *escape hatch* for assets the registry
1424
- * doesn't ship (or per-deployment overrides), never required for shipped
1425
- * symbols;
1426
- * 2. shipped symbols (USDC, USDT, DAI, CNGN, USDR, ZARP, EURC, XSGD, TRYB,
1427
- * BRIDGE) resolved per chain from the SDK chain registry (`chain.ts`) —
1428
- * the single source of truth shared with the rest of the SDK, so an
1429
- * address correction there is never shadowed by a parallel table here.
1430
- *
1431
- * Address lookups are per `(symbol, chain)` — a chain where no layer knows the
1432
- * asset simply doesn't trade pairs involving it. The registry holds addresses
1433
- * only; all pricing (including risk sizing) is derived from the pair curves.
1434
- */
1435
- declare class AssetRegistry {
1436
- private readonly resolver;
1437
- private readonly userAssets;
1438
- private readonly addressCache;
1439
- constructor(resolver: BuiltinAssetResolver, userAssets?: Record<string, AssetDefinition>);
1440
- /** Whether `symbol` is known (user-defined, curated, or built-in). */
1441
- hasSymbol(symbol: string): boolean;
1442
- /**
1443
- * Registers new user-defined assets on the live registry (runtime market
1444
- * adds). Existing symbols may not be redefined — an address swap under a
1445
- * running market would silently repoint its fills.
1446
- */
1447
- addAssets(defs: Record<string, AssetDefinition>): void;
1448
- /**
1449
- * Contract address of `symbol` on `chain`, or `null` when the asset is not
1450
- * deployed/known there. User `[assets]` addresses win over the SDK chain
1451
- * registry, per chain.
1452
- */
1453
- getAddress(symbol: string, chain: string): HexString | null;
1454
- /**
1455
- * The symbol `address` is known by on `chain`, or null when nothing in the
1456
- * registry resolves to it.
1457
- *
1458
- * Every symbol this registry can name is tried, user `[assets]` first so an
1459
- * overridden address reports the name the operator gave it. Two symbols
1460
- * resolving to one address would be a registry mistake; the first wins.
1461
- */
1462
- symbolFor(address: string, chain: string): string | null;
1463
- }
1464
-
1465
- /**
1466
- * One market from the top-level `[[pairs]]` TOML array:
1467
- *
1468
- * ```toml
1469
- * [[pairs]]
1470
- * token0 = "USDC" # the book's base — any symbol in the asset registry
1471
- * token1 = "CNGN" # the book's quote — any symbol in the asset registry
1472
- * ```
1473
- *
1474
- * A pair declares that a market exists and nothing more. What the filler will
1475
- * pay on it comes from the operator's limit orders, which carry the rate and the
1476
- * size they are good for.
1477
- *
1478
- * The orientation follows the orderbook's books, which the setup wizard copies:
1479
- * `token0` is the book's base and `token1` its quote, so the `USDC-cNGN` book
1480
- * (priced in cNGN per USDC) becomes `token0 = "USDC"`, `token1 = "CNGN"`. One
1481
- * orientation only: `A/B` and `B/A` are the same market and declaring both is
1482
- * refused. `token0 == token1` is the same-asset cross-chain market.
1483
- */
1484
- interface PairConfig {
1485
- /** The book's base symbol (e.g. "USDC", "USDT"). Any symbol in the registry. */
1486
- token0: string;
1487
- /** The book's quote symbol, which the book prices the base in (e.g. "CNGN"). Any symbol in the registry. */
1488
- token1: string;
1489
- }
1490
- /**
1491
- * The USD stable to quote a reference feed against for an unanchored symbol:
1492
- * the first stable (registry preference order) with no existing orientation
1493
- * against it — a pair and its reverse are the same market, so a stable that
1494
- * already has any market against the symbol cannot also carry the feed.
1495
- * Returns null when every stable is taken. Shared by both wizards.
1496
- */
1497
- declare function pickAnchorStable(pairs: Array<{
1498
- token0: string;
1499
- token1: string;
1500
- }>, symbol: string): string | null;
1501
- /**
1502
- * Validates the `[[pairs]]` array against the `[assets]` table and built-in
1503
- * symbols. Pure — throws a descriptive error on the first invalid pair.
1504
- */
1505
- declare function validatePairConfigs(pairs: PairConfig[], userAssets?: Record<string, AssetDefinition>): void;
1506
-
1507
- interface ChainBalanceRow {
1508
- chainId: number;
1509
- native?: {
1510
- symbol: string;
1511
- amount: number;
1512
- };
1513
- usdc?: number;
1514
- usdt?: number;
1515
- exotics?: Array<{
1516
- symbol: string;
1517
- amount: number;
1518
- }>;
1519
- /** Canonical token balances, including funds held in configured ERC-4626 vaults. */
1520
- assets: AssetBalanceRow[];
1521
- }
1522
- type AssetBalanceStatus = "fresh" | "partial" | "unavailable";
1523
- interface VaultBalanceRow {
1524
- address: string;
1525
- position: number;
1526
- available: number;
1527
- /** False while the vault reports `maxDeposit(solver) == 0`; sweeps into it are idle until it reopens. */
1528
- acceptsDeposits: boolean;
1529
- }
1530
- interface AssetBalanceRow {
1531
- address: string;
1532
- symbol: string;
1533
- /** Direct underlying balance held by the solver account. Null when its RPC read failed. */
1534
- wallet: number | null;
1535
- /** Configured wallet floor that must remain liquid. */
1536
- walletReserve: number;
1537
- /** Total solver-owned underlying represented by vault shares. */
1538
- vaultPosition: number;
1539
- /** Vault assets that can be withdrawn now, after pending-fill reservations. */
1540
- vaultAvailable: number;
1541
- /** Wallet plus vault position. Null when any required source was unavailable. */
1542
- total: number | null;
1543
- /** Spendable wallet above reserve plus currently withdrawable vault assets. */
1544
- available: number | null;
1545
- vaults: VaultBalanceRow[];
1546
- status: AssetBalanceStatus;
1547
- }
1548
- type BalanceIssueSource = "chain" | "native" | "token" | "vault";
1549
- interface BalanceIssue {
1550
- chainId?: number;
1551
- source: BalanceIssueSource;
1552
- asset?: string;
1553
- message: string;
1554
- }
1555
- type BalanceSnapshotStatus = "loading" | "fresh" | "partial" | "unavailable";
1556
- interface HyperbridgeBalance {
1557
- address: string;
1558
- free: number;
1559
- reserved: number;
1560
- }
1561
- interface BalanceSnapshot {
1562
- /** null until the first refresh attempt completes. */
1563
- updatedAt: number | null;
1564
- status: BalanceSnapshotStatus;
1565
- chains: ChainBalanceRow[];
1566
- issues: BalanceIssue[];
1567
- hyperbridge?: HyperbridgeBalance;
1568
- }
1569
- /** Narrow read-only seam implemented by the vault funding planner. */
1570
- interface VaultBalanceSource {
1571
- getBalanceSnapshot(chain?: string): Promise<VaultBalancePosition[]>;
1572
- }
1573
- interface BalanceProviderOptions {
1574
- chainClientManager: ChainClientManager;
1575
- configService: FillerConfigService;
1576
- fillerAddress: string;
1577
- /** Exotic token addresses per state machine id — every cross-asset pair's token1 on that chain. */
1578
- token1: Record<string, string[]>;
1579
- /**
1580
- * The filler's Hyperbridge connection, reused rather than dialled again. A second socket to the
1581
- * same node doubles the reconnect traffic during an outage for a balance read that is only
1582
- * needed once a minute.
1583
- */
1584
- hyperbridge?: Promise<IntentsCoprocessor>;
1585
- substratePrivateKey?: string;
1586
- refreshIntervalMs?: number;
1587
- vaultBalances?: VaultBalanceSource;
1588
- }
1589
- /**
1590
- * Periodically collects wallet balances (native, USDC, USDT, exotic per chain,
1591
- * plus the BRIDGE balance of the substrate account) into a plain snapshot,
1592
- * consumed by the UI JSON API and `wallet.balances()`.
1593
- */
1594
- declare class BalanceProvider extends EventEmitter$1 {
1595
- private snapshot;
1596
- private stopped;
1597
- private refreshInterval?;
1598
- private hyperbridgeInterval?;
1599
- private logger;
1600
- private options;
1601
- private intervalMs;
1602
- constructor(options: BalanceProviderOptions);
1603
- start(): Promise<void>;
1604
- stop(): void;
1605
- getSnapshot(): BalanceSnapshot;
1606
- refresh(): Promise<BalanceSnapshot>;
1607
- private collectChain;
1608
- private addConfiguredStable;
1609
- private trackHyperbridgeBalance;
1610
- }
1611
-
1612
- /** The stretch of time a summary covers. Each is cut into buckets of one size. */
1613
- type ProfitPeriod = "7d" | "30d" | "12w" | "12m" | "all";
1614
- type ProfitBucket = "day" | "week" | "month" | "year";
1615
- /** What a stretch of fills came to, in dollars at the latest rate. Unpriced tokens are left out. */
1616
- interface ProfitFigures {
1617
- /** What the stretch's fills added to inventory, less what they took out of it. */
1618
- profitUsd: number;
1619
- /** The inventory held when the stretch began. Null when nothing within reach of then records it. */
1620
- startInventoryUsd: number | null;
1621
- /** `profitUsd` over `startInventoryUsd`, as a percentage. Null without a starting inventory. */
1622
- returnPct: number | null;
1623
- /**
1624
- * What moved in and out over the stretch other than through fills, net: deposits, withdrawals,
1625
- * fees and vault yield. Null when the indexer has no reading for both ends of the stretch.
1626
- */
1627
- transfersUsd: number | null;
1628
- boughtUsd: number;
1629
- soldUsd: number;
1630
- buys: number;
1631
- sells: number;
1632
- }
1633
- interface ProfitBucketFigures extends ProfitFigures {
1634
- /** When the bucket starts, in milliseconds since the epoch. */
1635
- start: number;
1636
- }
1637
- /** One book's buys against its sells over the period, in the book's own tokens. */
1638
- interface BookProfit {
1639
- book: string;
1640
- base: string;
1641
- quote: string;
1642
- /** Base bought and sold in the period. */
1643
- bought: number;
1644
- sold: number;
1645
- /** Quote per base, averaged over the period's buys and its sells. Null for a side with none. */
1646
- averageBuy: number | null;
1647
- averageSell: number | null;
1648
- /** How far the average sell sits above the average buy, as a percentage. Null without both. */
1649
- spreadPct: number | null;
1650
- /** What the period's fills did to the base and the quote held: positive is more than before. */
1651
- baseChange: number;
1652
- quoteChange: number;
1653
- /** The rate the book's tokens are valued at: midway between its latest buy and latest sell. */
1654
- rate: number | null;
1655
- /** Both changes in dollars. Null when the book's tokens have no dollar price. */
1656
- profitUsd: number | null;
1657
- }
1658
- /** The inventory a period began with, token by token. */
1659
- interface StartingInventory {
1660
- /** The instant it describes, in milliseconds since the epoch. */
1661
- at: number;
1662
- /** A stored snapshot, or the balances as they stand now. Either is adjusted by what happened between. */
1663
- source: "snapshot" | "balances";
1664
- /** When that record was taken. */
1665
- recordedAt: number;
1666
- usd: number;
1667
- tokens: Array<{
1668
- symbol: string;
1669
- amount: number;
1670
- usd: number | null;
1671
- }>;
1672
- }
1673
- interface Profitability {
1674
- period: ProfitPeriod;
1675
- bucket: ProfitBucket;
1676
- /** The start of the first bucket and the moment the summary was taken, in milliseconds. */
1677
- from: number;
1678
- to: number;
1679
- totals: ProfitFigures;
1680
- /** One entry per bucket, oldest first, including buckets nothing happened in. */
1681
- series: ProfitBucketFigures[];
1682
- books: BookProfit[];
1683
- /** Null when there is no snapshot, and the period began more than {@link BALANCES_REACH_MS} ago. */
1684
- startInventory: StartingInventory | null;
1685
- /** Fills in the period priced at their order's rate, because they kept no record of what they took in. */
1686
- estimatedFills: number;
1687
- /** Tokens the period touched that have no dollar price, and so are in none of the dollar figures. */
1688
- unpricedTokens: string[];
1689
- }
1690
-
1691
- /**
1692
- * Wire contracts between the UI server and the browser bundle. The server
1693
- * types its response literals with these and `ui/src` imports them type-only,
1694
- * so drift on either side is a compile error. Browser-safe: no runtime
1695
- * dependencies beyond constants.
1696
- */
1697
-
1698
- /** Signer work that must finish before the desktop may replace the bundled solver. */
1699
- interface SolverWork {
1700
- queuedEvaluations: number;
1701
- evaluating: number;
1702
- queuedFills: number;
1703
- activeFills: number;
1704
- retractions: number;
1705
- rebalancing: number;
1706
- }
1707
-
1708
- /** `[simplex.tunnel]` in the config file. */
1709
- interface TunnelConfig {
1710
- /** Off by default: enabling it makes the embedded SSH server reachable from the internet. */
1711
- enabled?: boolean;
1712
- /** `host[:port]`; port defaults to 443. */
1713
- relay?: string;
1714
- /** `SHA256:…` fingerprint of the relay host key. Without it the key seen on first contact is pinned. */
1715
- relayHostKey?: string;
1716
- }
1717
-
1718
- /**
1719
- * A coordinate point on a curve
1720
- * @property amount - The input threshold (e.g., USD amount)
1721
- * @property value - The output value at this threshold (e.g., confirmations, bps)
1722
- */
1723
- interface CurvePoint {
1724
- amount: string;
1725
- value: number;
1726
- }
1727
- /**
1728
- * Configuration for a curve
1729
- */
1730
- interface CurveConfig {
1731
- points: CurvePoint[];
1732
- }
1733
- /**
1734
- * Manages confirmation block requirements per chain.
1735
- * Each chain has its own curve mapping order value to required confirmations.
1736
- */
1737
- /**
1738
- * Parses a per-chain config key. Every other table in the config writes chains
1739
- * as state machine ids ("EVM-1"), so both "EVM-1" and bare "1" are accepted;
1740
- * anything else returns null and callers MUST fail loudly — a silently
1741
- * discarded chain key has meant committing capital on a chain configured as
1742
- * watch-only, or hardening a confirmation curve that never applied.
736
+ *
737
+ * This used to own a block-scan loop per chain. It no longer does: scanning is
738
+ * identical work for every filler on a chain, so it moved to a shared
739
+ * {@link OrderSource} and this class subscribes to it. Everything else stays —
740
+ * the monitor is the filler's event bus, not merely a source. `IntentFiller`
741
+ * emits `orderTiming`, `orderSkipped`, `orderFilled`, `orderExecuted` and
742
+ * `rebalanceExecuted` on it, `ActivityRecorder` attaches to it, and `Simplex`
743
+ * re-exposes it as the public event surface.
744
+ *
745
+ * Three things must stay on this side of the boundary:
746
+ * - the `OrderFilled` filler-address filter. `filler` is `indexed: false` on
747
+ * both `OrderFilled` and `PartialFill`, so it can never be a topic filter;
748
+ * every consumer receives every fill and narrows it locally.
749
+ * - the leg filter. An order reaches the filler only when its inputs and
750
+ * outputs pair up leg by leg; the strategy then bids on each leg one of its
751
+ * limit orders serves.
752
+ * - de-duplication. Exactly-once used to be emergent — a private monotonic
753
+ * cursor made a repeat impossible. A shared feed can replay from a cursor
754
+ * after a reconnect, so the seen-set below is what preserves the property
755
+ * the filler has always assumed.
1743
756
  */
1744
- declare function parseChainKey(key: string): number | null;
1745
- /** The canonical "EVM-<id>" state machine id `parseChainKey` accepts. */
1746
- declare function formatChainKey(chainId: number | string): string;
1747
- declare class ConfirmationPolicy {
1748
- private policies;
1749
- constructor(policyConfig: Record<string, CurveConfig>);
1750
- getConfirmationBlocks(chainId: number, amountUsd: Decimal$1): number;
1751
- /**
1752
- * Installs a curve for a chain added at runtime. Validates before it mutates,
1753
- * so a rejected curve leaves the policy set untouched — a chain that started
1754
- * scanning without a curve would drop every cross-chain order sourced on it
1755
- * with a per-order throw.
1756
- */
1757
- add(chainId: number, config: CurveConfig): void;
1758
- /** Whether a chain has a confirmation curve. */
1759
- has(chainId: number): boolean;
1760
- remove(chainId: number): void;
1761
- /**
1762
- * Copies one chain's curve across from another policy set.
1763
- *
1764
- * Used when a chain is added at runtime and covered by a built-in default: the
1765
- * defaults are only materialised inside a freshly constructed policy, so the
1766
- * live one adopts the entry rather than re-deriving it.
757
+ declare class EventMonitor extends EventEmitter$1 {
758
+ private listening;
759
+ private configService;
760
+ private fillerAddress;
761
+ private logger;
762
+ private orderScanner;
763
+ private subscription?;
764
+ /** Chain ids this filler is configured for; the scanner may carry more. */
765
+ private chains;
766
+ /** Order ids already delivered, newest last. Bounds the memory a replay can cost. */
767
+ private seen;
768
+ private seenOrder;
769
+ constructor(chainConfigs: ChainConfig[], configService: FillerConfigService, fillerAddress: HexString, orderScanner: OrderScanner$1);
770
+ /** Marks a chain as one this filler cares about. */
771
+ private registerChain;
772
+ /**
773
+ * Attaches to the scanner. One subscription covers every chain it carries — a
774
+ * scanner shared with other fillers may cover more chains than this one is
775
+ * configured for, so events are matched on `chainId` here.
1767
776
  */
1768
- adopt(chainId: number, from: ConfirmationPolicy): void;
777
+ startListening(): Promise<void>;
778
+ private handleOrder;
779
+ private handleFill;
1769
780
  /**
1770
- * Startup guard: every configured chain must have a confirmation curve.
1771
- * Without this, a missing policy only surfaces as a per-order throw at
1772
- * fill time — cross-chain orders sourced on that chain silently dropped.
781
+ * Starts matching events for a chain added after boot.
782
+ *
783
+ * The scanner must already carry the chain. `Simplex` adds it to a scanner it
784
+ * owns; when the scanner was supplied by the caller it stays the caller's to
785
+ * manage, so that another filler reading from it is never surprised by a
786
+ * chain appearing or vanishing underneath it.
1773
787
  */
1774
- assertCovers(chainIds: number[]): void;
1775
- }
1776
-
1777
- interface PaymasterKeeperConfig {
1778
- /** Chains to monitor. Defaults to every configured chain with a SimplexPaymaster. */
1779
- chains?: string[];
1780
- intervalMinutes?: number;
1781
- /** Minimum accrued balance (in whole stablecoin units) worth recycling. */
1782
- minSwapUsd?: number;
1783
- }
1784
-
1785
- interface ChainConfirmationPolicy {
788
+ addChain(chainId: number): Promise<void>;
789
+ /** Stops matching events for a chain. The scanner keeps scanning it for others. */
790
+ removeChain(chainId: number): Promise<void>;
1786
791
  /**
1787
- * Array of (amount, value) coordinates defining the confirmation curve.
1788
- * value = number of confirmations at that order amount
792
+ * No-op against a scanner: endpoints belong to the scanner, not to a filler
793
+ * reading from it. `Simplex.chains.setRpcUrls` swaps the endpoints on a scanner
794
+ * it owns and rejects the call on one it does not.
1789
795
  */
1790
- points: Array<{
1791
- amount: string;
1792
- value: number;
1793
- }>;
1794
- }
1795
- /**
1796
- * TOML row for an ERC-4626 vault entry. `threshold` (absolute human units) is the
1797
- * high-water mark that triggers a sweep down to `minBalance`; omit both for
1798
- * withdraw-only sourcing.
1799
- */
1800
- interface VaultToml {
1801
- chain: string;
1802
- vault: HexString;
1803
- threshold?: string;
1804
- minBalance?: string;
1805
- redeemOnShutdown?: boolean;
1806
- }
1807
- /** Top-level `[vault]` config: ERC-4626 treasury vaults, for withdraw sourcing and threshold sweeping. */
1808
- interface VaultTomlConfig {
1809
- vaults?: VaultToml[];
1810
- sweepIntervalMs?: number;
1811
- }
1812
- interface QueueConfig {
1813
- maxRechecks: number;
1814
- recheckDelayMs: number;
1815
- }
1816
- interface RebalancingConfig {
1817
- triggerPercentage: number;
1818
- baseBalances: {
1819
- USDC?: Record<string, string>;
1820
- USDT?: Record<string, string>;
1821
- };
1822
- }
1823
- interface BinanceConfig {
1824
- apiKey: string;
1825
- apiSecret: string;
1826
- basePath?: string;
1827
- timeout?: number;
1828
- depositTimeoutMs?: number;
1829
- pollIntervalMs?: number;
1830
- withdrawTimeoutMs?: number;
1831
- }
1832
- interface FillerTomlConfig {
1833
- /**
1834
- * Optional asset-registry escape hatch: symbol → { chain → address }. Only
1835
- * needed for assets the built-in registry does not ship, or to override a
1836
- * shipped address for a deployment. Shipped symbols: USDC, USDT, DAI, CNGN
1837
- * (SDK chain registry) + curated USDR, ZARP, EURC, XSGD, TRYB and BRIDGE.
1838
- */
1839
- assets?: Record<string, AssetDefinition>;
1840
- /**
1841
- * Markets the operator dashboard lists, as `token0`/`token1` symbol pairs.
1842
- * They carry no prices and do not gate matching: the filler prices every
1843
- * fill from the operator's limit orders. Optional; the browser setup wizard
1844
- * writes one per book the orderbook lists. Keys left over from the curve
1845
- * era (`bidPriceCurve`, `askPriceCurve`, `maxOrderSize`, `referenceOnly`)
1846
- * are ignored.
1847
- */
1848
- pairs?: PairConfig[];
1849
- /**
1850
- * Per-chain confirmation policies for cross-chain orders, keyed by chain id.
1851
- * Merged over built-in defaults (ETH, BSC, Polygon, Base, Arbitrum,
1852
- * Unichain); every configured chain must be covered or startup fails. The
1853
- * policy's amount axis is the order's USD value: USD stables count at $1 and
1854
- * other tokens are valued through the open limit orders' rates. An order no
1855
- * limit order connects to a dollar waits for the policy's maximum depth.
1856
- */
1857
- confirmationPolicies?: Record<string, ChainConfirmationPolicy>;
1858
- simplex: {
1859
- /** Orders evaluated at once. Defaults to 5. */
1860
- maxConcurrentOrders?: number;
1861
- /**
1862
- * Accepted and ignored. Older configs and the wizard used to write it; the
1863
- * engine never read it. Kept in the type so those configs still load.
1864
- */
1865
- queue?: QueueConfig;
1866
- logging?: string;
1867
- watchOnly?: boolean | Record<string, boolean>;
1868
- substratePrivateKey: string;
1869
- hyperbridgeWsUrl: string;
1870
- /**
1871
- * Hyperbridge indexer GraphQL endpoint, used to backfill order details on
1872
- * activity rows recorded before they were captured. Defaults per network
1873
- * (nexus for mainnet, gargantua for testnet).
1874
- */
1875
- indexerUrl?: string;
1876
- /** Accepted and ignored. Contract addresses come from the SDK chain registry. */
1877
- entryPointAddress?: string;
1878
- /** Accepted and ignored. Contract addresses come from the SDK chain registry. */
1879
- solverAccountContractAddress?: string;
1880
- /** Target gas units for EntryPoint deposits per chain. Defaults to 3,000,000. */
1881
- targetGasUnits?: number;
1882
- /**
1883
- * Block-scanner poll period per chain in seconds. Defaults to 3, and
1884
- * fractional values are allowed (0.5 = twice a second). Each tick costs
1885
- * one `eth_blockNumber` + one `eth_getLogs` per chain per endpoint, so
1886
- * raising this is the lever for fitting inside a rate-limited RPC's
1887
- * budget; the cost is seeing new orders that much later.
1888
- */
1889
- blockScanIntervalSeconds?: number;
1890
- /** Gas fee bump (percentages added to base gasPrice). Defaults: priority=8%, max=10%. */
1891
- gasFeeBump?: {
1892
- maxPriorityFeePerGasBumpPercent?: number;
1893
- maxFeePerGasBumpPercent?: number;
1894
- };
1895
- /**
1896
- * Overfill protection knobs. Defaults: maxOverfillBps=500, maxConsecutiveClamps=3.
1897
- * `maxOverfillBps` is warn-only: FXFiller logs a limit order whose offer exceeds
1898
- * the swapper's ask by more than this, and clamps nothing. `maxConsecutiveClamps`
1899
- * is accepted but dormant: the clamp that fed it was for Uniswap V4 venue pricing,
1900
- * which is gone, so FXFiller never halts.
1901
- */
1902
- overfillProtection?: {
1903
- maxOverfillBps?: number;
1904
- maxConsecutiveClamps?: number;
1905
- };
1906
- /**
1907
- * How long a signed bid stays executable, in seconds. Defaults to 300 (5 minutes).
1908
- *
1909
- * Written into `FillOptions.validUntil` and enforced by `fillOrder`, which reverts
1910
- * `FillExpired` past it. This is how long the quoted price stands as a firm commitment:
1911
- * the order's `deadline` is chosen by the placer with no ceiling, and retracting the bid
1912
- * on Hyperbridge does not reach the destination chain, so without it a signed bid stays
1913
- * executable indefinitely and is taken up only once the rate has moved against us.
1914
- *
1915
- * Configured in seconds but written on-chain in blocks, converted per destination chain.
1916
- */
1917
- bidValiditySeconds?: number;
1918
- /**
1919
- * The order size, in USD, below which `order.fees` must cover the execution cost
1920
- * (fill gas plus, cross-chain, the relayer fee). Defaults to 20.
1921
- *
1922
- * An order worth at least this much is filled whatever fees it carries: the margin
1923
- * in the limit order that prices it pays for the gas. On a smaller order that
1924
- * margin is too thin to, so the fees have to. Set to 0 to never check fees. An
1925
- * order nothing can put a dollar figure on is treated as below it.
1926
- */
1927
- minOrderSizeUsd?: number;
1928
- /**
1929
- * Remote access: an outbound SSH tunnel to a rendezvous relay so a phone's
1930
- * SSH client can reach the local web UI. Off unless `enabled = true`; the
1931
- * relay defaults to the hosted one. Devices are paired from the UI.
1932
- */
1933
- tunnel?: TunnelConfig;
1934
- };
1935
- chains: UserProvidedChainConfig[];
1936
- rebalancing?: RebalancingConfig;
1937
- binance?: BinanceConfig;
1938
- /** Filler-wide vault config: stablecoin sourcing for fills + threshold sweeping. */
1939
- vault?: VaultTomlConfig;
1940
- /** Restricts order processing to listed user addresses. Omit to accept all users. */
1941
- allowlist?: AllowlistConfig;
1942
- /** SimplexPaymaster fee-recycling keeper (`paymaster-keeper` subcommand). */
1943
- keeper?: PaymasterKeeperConfig;
1944
- /** The HyperFX orderbook simplex posts its limit orders to. */
1945
- orderbook?: OrderbookConfig;
796
+ rebuildChain(chainId: number): Promise<void>;
797
+ /** Events dropped because this filler could not keep up with the scanner. */
798
+ droppedEvents(): number;
799
+ stopListening(): Promise<void>;
1946
800
  }
801
+
1947
802
  /**
1948
- * Where the operator's limit orders are advertised.
803
+ * An execution outcome that can also report a still-pooled bid.
1949
804
  *
1950
- * The limit orders themselves are not configured here: they live in `bids.db`
1951
- * and are created over the API, because they are inventory the operator opens
1952
- * and closes while the filler runs rather than startup settings.
805
+ * `submitBid` can time out with the extrinsic still in Hyperbridge's pool. That
806
+ * is neither success nor failure: the extrinsic may yet land and reserve a
807
+ * deposit, so the bid record has to be reclaimable even though `success` is
808
+ * false. Widened here rather than on the SDK's `ExecutionResult` so simplex does
809
+ * not need an unreleased SDK.
1953
810
  */
1954
- interface OrderbookConfig {
1955
- /** GraphQL endpoint. */
1956
- url: string;
1957
- /**
1958
- * How long a limit order lives, in seconds, and the TTL written into its posting,
1959
- * when the create request names none. Defaults to 365 days; at least 900, the
1960
- * orderbook's floor. The order and its posting expire together: there is one
1961
- * clock, and nothing renews it.
1962
- */
1963
- defaultTtlSecs?: number;
1964
- /** How often to reconcile local limit orders against the orderbook, in seconds. */
1965
- reconcileIntervalSecs?: number;
1966
- requestTimeoutMs?: number;
811
+ interface FillResult extends ExecutionResult {
812
+ pending?: boolean;
813
+ /**
814
+ * The identifier Hyperbridge files the bid under, once its op was built:
815
+ * `keccak256` of its calldata. It tells the bids on one order apart, and retracting the bid names it.
816
+ */
817
+ bid?: HexString;
1967
818
  }
1968
- /**
1969
- * The TOML file the binary reads: a {@link FillerTomlConfig} plus the
1970
- * `[simplex.signer]` block, which is the CLI's way of naming a signing backend.
1971
- *
1972
- * The block is not part of the library's config — `Simplex.start` takes a
1973
- * `Signer` instance — so it lives on this type and nowhere else. The binary
1974
- * resolves it with `signerFromToml` and passes the parsed file straight through;
1975
- * the extra key rides along untouched so the dashboard's config writer can put
1976
- * it back in the file it came from.
1977
- */
1978
- interface FillerConfigFile extends FillerTomlConfig {
1979
- simplex: FillerTomlConfig["simplex"] & {
1980
- /** Omitted by watch-only configs, which sign nothing. */
1981
- signer?: SignerConfig;
819
+
820
+ interface FillerStrategy {
821
+ name: string;
822
+ canFill(order: Order): Promise<boolean>;
823
+ calculateProfitability(order: Order): Promise<number>;
824
+ executeOrder(order: Order, hyperbridge?: IntentsCoprocessor): Promise<FillResult>;
825
+ /**
826
+ * Optional hook for strategies to provide a USD value for the full input basket.
827
+ * Returns null when the strategy cannot or does not want to price the order.
828
+ */
829
+ getOrderUsdValue?(order: Order): Promise<{
830
+ inputUsd: Decimal;
831
+ } | null>;
832
+ /**
833
+ * Optional confirmation policy for this strategy.
834
+ * If absent, no confirmation waiting is required (e.g. same-chain strategies).
835
+ */
836
+ confirmationPolicy?: {
837
+ getConfirmationBlocks: (chainId: number, amountUsd: number) => number;
1982
838
  };
1983
839
  }
1984
- /**
1985
- * Boot-parity confirmation coverage: every configured chain id needs a
1986
- * confirmation curve (built-in defaults or [confirmationPolicies]), or orders
1987
- * sourced on it would be silently dropped. Boot and both wizard write gates
1988
- * run the same construction; the gates know the chain ids the wizard selected,
1989
- * boot the ids resolved from the RPCs.
1990
- */
1991
- declare function assertConfirmationCoverage(confirmationPolicies: FillerTomlConfig["confirmationPolicies"], chainIds: number[]): ConfirmationPolicy;
1992
- /**
1993
- * Validates raw TOML `[vault].vaults` entries. Pure and browser-safe — shared
1994
- * by `validateConfig`, the vault-update endpoint and the funding planner.
1995
- * Throws on missing/invalid required fields.
1996
- */
1997
- declare function validateVaultToml(vaults: {
1998
- chain?: string;
1999
- vault?: string;
2000
- threshold?: string;
2001
- minBalance?: string;
2002
- redeemOnShutdown?: boolean;
2003
- }[]): void;
2004
- declare function validateConfig(config: FillerTomlConfig, cliWatchOnly?: boolean): void;
2005
840
 
2006
841
  /** Symbol and decimals for a token on a chain, as far as the filler knows them. */
2007
842
  interface TokenDescription {
@@ -2030,7 +865,7 @@ interface ActivityRecorderOptions {
2030
865
  * stored row is re-emitted as `event` for live consumers (the UI's SSE stream,
2031
866
  * and `Simplex`'s `activity` event).
2032
867
  */
2033
- declare class ActivityRecorder extends EventEmitter$1 {
868
+ declare class ActivityRecorder extends EventEmitter {
2034
869
  private store;
2035
870
  private logger;
2036
871
  private monitor?;
@@ -2070,19 +905,6 @@ declare class ActivityRecorder extends EventEmitter$1 {
2070
905
  walletTxs(limit?: number): Promise<WalletTx[]>;
2071
906
  }
2072
907
 
2073
- /** One market the engine serves, as the operator's market list shows it. */
2074
- interface AdminStrategy {
2075
- /** Stable identifier for the API. */
2076
- index: number;
2077
- /** Position in the TOML `[[pairs]]` array. */
2078
- pairIndex: number;
2079
- /** Pair label, e.g. "USDC/CNGN" (display-only). */
2080
- exotic?: string;
2081
- token0: string;
2082
- token1: string;
2083
- /** Same-asset cross-chain market, where the spread is realized in kind. */
2084
- sameToken?: boolean;
2085
- }
2086
908
  /** Self-halt visibility/reset for one FX strategy (overfill protection). */
2087
909
  interface HaltControl {
2088
910
  index: number;
@@ -2488,111 +1310,6 @@ declare class ContractInteractionService {
2488
1310
  buildApprovalAndFillCalldata(order: Order, fillerOutputs: TokenInfo[], fillOptions: FillOptions, requiredFeeTokenAmount: bigint, settlement?: BudgetSettlement): Promise<HexString>;
2489
1311
  }
2490
1312
 
2491
- /**
2492
- * Service for managing EIP-7702 delegation of the filler's EOA to the SolverAccount contract.
2493
- * This enables the filler to participate in solver selection mode.
2494
- *
2495
- * When the Simplex paymaster is configured and the filler holds stablecoins already
2496
- * approved to Permit2, delegation is performed via a no-op UserOp sent through the
2497
- * bundler — the paymaster pays gas in stablecoins. Falls back to a direct type-0x04 tx
2498
- * if the bundler path is unavailable.
2499
- *
2500
- * The paymaster prefunds through Permit2, which needs a standing token approval to the
2501
- * Permit2 contract. On a chain the solver has never used, the delegation op installs
2502
- * that approval itself and pays for the privilege with an EIP-2612 permit, so a
2503
- * permit-capable fee token bootstraps with no native at all. A token without a permit
2504
- * (BNB Chain) still needs native dust once, for the type-0x04 tx that batches the
2505
- * approve in. Every op after that is native-free either way.
2506
- */
2507
- declare class DelegationService {
2508
- private clientManager;
2509
- private configService;
2510
- private signer;
2511
- private logger;
2512
- private readonly userOpSender;
2513
- constructor(clientManager: ChainClientManager, configService: FillerConfigService, signer: Signer);
2514
- /**
2515
- * @param viaBundler When true, uses the current nonce (bundler submits the tx).
2516
- * When false, uses nonce+1 (EOA submits the type-0x04 tx itself).
2517
- */
2518
- private buildAuthorization;
2519
- private sendDelegationTransaction;
2520
- /**
2521
- * Best-effort resolution of a Permit2 approval to fold into a native delegation. Never
2522
- * throws: a resolution failure (RPC error, misconfig) yields null and the delegation
2523
- * proceeds as a plain self-call.
2524
- */
2525
- private resolvePendingPermit2Approval;
2526
- /**
2527
- * Checks if the filler's EOA is already delegated to the SolverAccount contract on a specific chain.
2528
- */
2529
- isDelegated(chain: string): Promise<boolean>;
2530
- /**
2531
- * Sets up EIP-7702 delegation via the bundler. Uses the Simplex paymaster when
2532
- * configured and the filler holds a sufficient stablecoin balance.
2533
- *
2534
- * The op is a no-op once the account has a Permit2 allowance. On a chain it has
2535
- * never used, and only when the fee token implements EIP-2612 (`permitCapable`),
2536
- * the op instead carries `approve(Permit2, max)` as its callData and asks the
2537
- * paymaster for PERMIT mode: the permit covers this op's gas, and the op installs
2538
- * the allowance every later op authorizes against. That is the whole zero-native
2539
- * bootstrap — one sponsored op, after which the account never needs a permit again.
2540
- */
2541
- private setupDelegationViaBundler;
2542
- /**
2543
- * Installs `approve(Permit2, max)` on an account that is ALREADY delegated, using the same
2544
- * permit-funded op the first-time delegation carries — minus the authorization, which it
2545
- * does not need.
2546
- *
2547
- * This exists because delegation and bootstrap are separate facts. An account delegated by
2548
- * an earlier release has no Permit2 allowance (that release charged EIP-2612 permits and
2549
- * never touched Permit2), and `setupDelegation` returns before the bootstrap once
2550
- * `isDelegated` holds. Leaving it there strands every upgraded solver on the native-funded
2551
- * approve path.
2552
- *
2553
- * Best-effort and never throws: on failure the first sponsored op still falls back to
2554
- * `sendFundedApprove`, which works for a solver holding native.
2555
- */
2556
- private ensurePermit2Allowance;
2557
- /**
2558
- * Sets up EIP-7702 delegation from the filler's EOA to the SolverAccount contract.
2559
- *
2560
- * A fee token with no Permit2 allowance yet cannot be charged through Permit2, so the
2561
- * approve has to be installed by the very first operation. Which carrier does that
2562
- * depends on the token:
2563
- *
2564
- * - EIP-2612 token: the bundler goes first and the op pays for itself with a permit
2565
- * while carrying the approve in its callData. No native is spent, so this is
2566
- * preferred even on an EOA that holds some.
2567
- * - No permit (BSC pegged stables): only a native type-0x04 tx can carry the approve,
2568
- * so that goes first when the EOA covers it.
2569
- *
2570
- * With nothing pending — the allowance is already in place — the bundler path goes
2571
- * first as usual. A plain direct tx is the last fallback in every case.
2572
- */
2573
- setupDelegation(chain: string): Promise<boolean>;
2574
- /** Whether the EOA can pay for one direct set-code tx at the current gas price. */
2575
- private nativeCoversDirectTx;
2576
- /**
2577
- * Sends one native EIP-7702 delegation tx (optionally batching a Permit2 approve) and
2578
- * waits for its receipt. Never throws: a revert or send error is logged and returns
2579
- * false, so the caller can fall back. Rebuilds the authorization each call, so a fresh
2580
- * nonce is used after a prior attempt consumed one.
2581
- */
2582
- private trySendDelegation;
2583
- /**
2584
- * Sets up delegation on the specified chains where solver selection is active.
2585
- */
2586
- setupDelegationOnChains(chains: string[]): Promise<{
2587
- success: boolean;
2588
- results: Record<string, boolean>;
2589
- }>;
2590
- /**
2591
- * Revokes delegation by delegating to the zero address.
2592
- */
2593
- revokeDelegation(chain: string): Promise<boolean>;
2594
- }
2595
-
2596
1313
  interface BinanceCexConfig {
2597
1314
  apiKey: string;
2598
1315
  apiSecret: string;
@@ -3432,7 +2149,7 @@ declare class IntentFiller {
3432
2149
  private signer;
3433
2150
  private fillerAddress;
3434
2151
  private logger;
3435
- constructor(chainConfigs: ChainConfig[], strategies: FillerStrategy[], config: FillerConfig$1, configService: FillerConfigService, chainClientManager: ChainClientManager, contractService: ContractInteractionService, signer: Signer, scanners: {
2152
+ constructor(chainConfigs: ChainConfig[], strategies: FillerStrategy[], config: FillerConfig, configService: FillerConfigService, chainClientManager: ChainClientManager, contractService: ContractInteractionService, signer: Signer, scanners: {
3436
2153
  orders: OrderScanner$1;
3437
2154
  }, rebalancingService?: RebalancingService, bidStorage?: BidStore, limitOrders?: LimitOrderStore, assetRegistry?: AssetRegistry);
3438
2155
  /**
@@ -3640,278 +2357,11 @@ declare class IntentFiller {
3640
2357
  private retractStoredBids;
3641
2358
  }
3642
2359
 
3643
- /**
3644
- * One market the engine will quote, as a pair of registry symbols.
3645
- *
3646
- * A pair declares that the market exists and nothing more: what the filler pays
3647
- * on it comes from the operator's limit orders. `token0 == token1` is the
3648
- * same-asset cross-chain market, where the spread is realized in kind.
3649
- */
3650
- interface TradingPair {
3651
- token0: string;
3652
- token1: string;
3653
- }
3654
- /**
3655
- * Strategy for swaps priced by the operator's limit orders. Supports both
3656
- * same-chain and cross-chain orders.
3657
- *
3658
- * Every leg is matched against the open limit orders (`matchLimitOrders`): an
3659
- * order serves a leg when it fills on the destination chain, takes the leg's
3660
- * input, pays out its output, accepts its source chain and, at its own rate,
3661
- * pays at least what the swapper asked for. Each matching order gets its own
3662
- * bid, best offer first, sized `min(offer, remaining)`; there is no fallback
3663
- * price, so a leg nothing matches is not bid on. No external price feed is
3664
- * consulted. Confirmation sizing is the only place a dollar value is needed,
3665
- * and it is derived from the limit orders' own rates (see `usdFactorsFrom`).
3666
- *
3667
- * Payouts are bounded by the filler's real balances plus funding-venue
3668
- * withdrawals. Because the IntentGateway releases inputs proportionally to the
3669
- * fraction of outputs provided, partial fills need no extra on-chain logic.
3670
- *
3671
- * `[[pairs]]` markets do not gate matching; they name the markets the operator
3672
- * dashboard lists.
3673
- */
3674
- declare class FXFiller implements FillerStrategy {
3675
- name: string;
3676
- private clientManager;
3677
- private contractService;
3678
- private configService;
3679
- /** Markets the dashboard lists. Informational only: matching reads the limit orders. */
3680
- private pairs;
3681
- /** Symbol → per-chain address resolution (built-ins + curated + user `[assets]`). */
3682
- private registry;
3683
- private signer;
3684
- private logger;
3685
- /**
3686
- * Consecutive orders where the overfill clamp activated. Dormant: the clamp
3687
- * existed for venue-priced legs, which went with Uniswap V4 pricing, so every
3688
- * outcome is now recorded unclamped and this never climbs. See `recordOrderOutcome`.
3689
- */
3690
- private consecutiveClamps;
3691
- /** Once set, the filler refuses all orders until reset. Unreachable while the clamp is dormant. */
3692
- private halted;
3693
- /** Bps above the swapper's ask past which a limit order's offer is logged as suspicious. Warn only. */
3694
- private readonly maxOverfillBps;
3695
- /** Consecutive clamped evaluations before halting. Dormant, as `consecutiveClamps` is. */
3696
- private readonly maxConsecutiveClamps;
3697
- confirmationPolicy?: {
3698
- getConfirmationBlocks: (chainId: number, amountUsd: number) => number;
3699
- };
3700
- private fundingVenues;
3701
- /** The operator's resting orders: the only thing that prices a fill. */
3702
- private limitOrders?;
3703
- /**
3704
- * @param signer Filler's signing account for UserOp signatures.
3705
- * @param configService Network/config provider for addresses and decimals.
3706
- * @param clientManager Used to get viem PublicClients for chains.
3707
- * @param contractService Shared contract interaction service.
3708
- * @param pairs Trading pairs with their bid/ask price policies and per-order caps.
3709
- * @param registry Asset symbol registry resolving pair symbols per chain.
3710
- * @param options.confirmationPolicy Optional per-chain confirmation policy for cross-chain orders.
3711
- * @param options.fundingVenues Optional funding venues for on-chain liquidity sourcing.
3712
- */
3713
- constructor(signer: Signer, configService: FillerConfigService, clientManager: ChainClientManager, contractService: ContractInteractionService, pairs: TradingPair[], registry: AssetRegistry, options?: {
3714
- confirmationPolicy?: ConfirmationPolicy;
3715
- fundingVenues?: FundingVenue[];
3716
- /** Where prices come from. Without it the engine matches nothing and fills nothing. */
3717
- limitOrders?: LimitOrderStore;
3718
- });
3719
- /** Per-pair invariants, shared by the constructor and addPair. Adds the accepted pair's label to `seenPairs`. */
3720
- private static assertPairValid;
3721
- /**
3722
- * Adds a market to the running engine. All-or-nothing: the pair passes the
3723
- * same duplicate and reverse-orientation checks the constructor enforces
3724
- * before it is pushed.
3725
- */
3726
- addPair(pair: TradingPair): void;
3727
- /**
3728
- * Removes a market from the running engine. At least one market must remain.
3729
- * Matching reads the limit orders rather than the pairs, so removing a market
3730
- * changes what the dashboard lists, not what the filler fills.
3731
- */
3732
- removePair(pair: TradingPair): void;
3733
- /**
3734
- * Call once at startup after construction.
3735
- * Hydrates all funding venue state before any fill sources from it.
3736
- */
3737
- initialise(): Promise<void>;
3738
- canFill(order: Order): Promise<boolean>;
3739
- /**
3740
- * Evaluates whether an order is profitable to fill against the operator's
3741
- * limit orders and the filler's current token balances.
3742
- *
3743
- * High-level flow:
3744
- * - Match each (input, output) leg against the open limit orders.
3745
- * - Build one bid per matching order, best offer first, each paying the
3746
- * order's whole offer for the input at its own rate, capped by what the
3747
- * order has left. The gateway credits the swapper the ask and splits
3748
- * anything above it between the swapper and the protocol.
3749
- * - Treat a payout below the ask as a partial fill, which only orders
3750
- * without output calldata and without an earlier partial fill allow.
3751
- * - Cap each payout by the filler's balance plus funding-venue withdrawals,
3752
- * then gate it on fees (full fills of orders below `minOrderSizeUsd`) and the
3753
- * same-asset spread.
3754
- * - Cache the resulting bids for later use in `executeOrder`.
3755
- */
3756
- calculateProfitability(order: Order): Promise<number>;
3757
- /**
3758
- * Executes an order by submitting a bid via the IntentsCoprocessor.
3759
- *
3760
- * Assumes that `calculateProfitability` has already been called for the
3761
- * given order so that filler outputs are cached in `contractService`.
3762
- * This method only orchestrates the bid construction and submission; the
3763
- * actual token movements are handled on-chain by the IntentGateway.
3764
- */
3765
- executeOrder(order: Order, intentsCoprocessor?: IntentsCoprocessor): Promise<ExecutionResult>;
3766
- /**
3767
- * Prepares and submits a bid UserOp to Hyperbridge for the given order.
3768
- *
3769
- * Uses the filler outputs previously cached by `calculateProfitability`.
3770
- * Approval bundling and UserOp construction are handled by
3771
- * `ContractInteractionService.prepareBidUserOp`.
3772
- */
3773
- private submitBid;
3774
- /**
3775
- * Update consecutive-clamp counter after a successful order evaluation.
3776
- *
3777
- * Dormant. Only venue-priced legs ever fed a clamped outcome here: a streak
3778
- * of those meant a live market source had gone off (stale pool, manipulated
3779
- * venue). Venue pricing went with Uniswap V4, and limit orders are prices the
3780
- * operator set, so the only caller records `false` and the filler never
3781
- * halts. `isHalted`, `resetHalt` and `[simplex.overfillProtection]
3782
- * maxConsecutiveClamps` are kept so existing configs and the halt API still
3783
- * load; they can go once nothing reads them.
3784
- */
3785
- private recordOrderOutcome;
3786
- isHalted(): boolean;
3787
- /** Operator acknowledgement after investigating a self-halt; resumes filling. */
3788
- resetHalt(): void;
3789
- /**
3790
- * Whether any solver has already delivered output against this order.
3791
- *
3792
- * Fails closed: a read that errors returns true, so an order we cannot price
3793
- * confidently is left alone rather than bid on with stale escrow assumptions.
3794
- */
3795
- private hasExistingPartialFill;
3796
- /**
3797
- * Reads and caches the filler's balance for a token on the destination chain.
3798
- *
3799
- * Normalizes the token address, checks an in-memory cache, and only hits
3800
- * the chain (native `getBalance` or ERC20 `balanceOf`) on a cache miss.
3801
- * This allows multiple legs within a single profitability evaluation to
3802
- * share the same balance pool.
3803
- */
3804
- private getAndCacheBalance;
3805
- /**
3806
- * Resolves every (input, output) leg of an order to a configured pair in one
3807
- * pass. Returns null if any leg matches no pair (or a disabled direction).
3808
- *
3809
- * The address-level classification is cached per order id in the shared
3810
- * cache (as `CachedPairClassification`, one entry per leg) so repeated
3811
- * evaluations skip re-derivation; pair resolution itself is re-run per
3812
- * strategy since pair sets differ between engine instances.
3813
- */
3814
- /** Whether any leg of the order matches a limit order: what `canFill` asks. */
3815
- private matchOrder;
3816
- /**
3817
- * The limit orders one leg of an order is priced against, best offer first, or
3818
- * none when nothing serves it.
3819
- *
3820
- * Amounts cross into the matcher at 1e18, the unit limit orders are kept in,
3821
- * and the payout comes back in the same unit for the caller to bring down to
3822
- * the output token's own decimals.
3823
- */
3824
- private matchLeg;
3825
- /** Why no limit order serves the order, leg by leg, for the line that says it was passed over. */
3826
- private explainUnmatched;
3827
- /**
3828
- * One leg as the matcher sees it, or null when its input token is not one the asset
3829
- * registry knows on the source chain.
3830
- *
3831
- * Amounts cross into the matcher at 1e18, the unit limit orders are kept in.
3832
- */
3833
- private incomingFor;
3834
- /**
3835
- * The order's input in **USD**, or null when no limit order connects its input
3836
- * token to a dollar.
3837
- *
3838
- * The core filler feeds this to the per-chain confirmation curves, whose
3839
- * amount axis is dollars. Null is the honest answer when the route is missing:
3840
- * the caller waits on this figure, and inventing one would under-wait a large
3841
- * order against a source chain that has not finalised.
3842
- */
3843
- getOrderUsdValue(order: Order): Promise<{
3844
- inputUsd: Decimal;
3845
- } | null>;
3846
- }
3847
-
3848
- interface SendTokenParams {
3849
- /** State machine id, e.g. "EVM-8453". */
3850
- chain: string;
3851
- /** "native" or an ERC-20 address. Configured vault shares are redeemed to the recipient instead of transferred. */
3852
- token: string;
3853
- /** Human-readable amount, parsed against the token's decimals. */
3854
- amount: string;
3855
- to: HexString;
3856
- }
3857
- interface SendTokenResult {
3858
- txHash: HexString;
3859
- sponsored: boolean;
3860
- /** Set when vault holdings were redeemed as part of the send. */
3861
- redeemed: boolean;
3862
- }
3863
- /**
3864
- * Operator-initiated outbound transfers from the filler wallet. Uses the same
3865
- * sponsored UserOp path as fills (gas paid by the paymaster) with a native-tx
3866
- * fallback. When the wallet balance of an ERC-20 falls short and a configured
3867
- * ERC-4626 vault holds it as underlying, the shortfall is withdrawn from the
3868
- * vault in the same batch as the transfer. Sending a vault's share token
3869
- * directly issues `redeem(shares, recipient, solver)` — shares only ever
3870
- * leave as the underlying asset.
3871
- */
3872
- declare class TokenSender {
3873
- private readonly clientManager;
3874
- private readonly solver;
3875
- /** Live view of the configured vaults so runtime vault edits are honored. */
3876
- private readonly getVaults;
3877
- private readonly userOpSender?;
3878
- /** Only needed to size the paymaster's gas reserve; without it a send reserves nothing. */
3879
- private readonly configService?;
3880
- private readonly logger;
3881
- constructor(clientManager: ChainClientManager, solver: HexString,
3882
- /** Live view of the configured vaults so runtime vault edits are honored. */
3883
- getVaults: () => VaultToml[], userOpSender?: UserOpSender | undefined,
3884
- /** Only needed to size the paymaster's gas reserve; without it a send reserves nothing. */
3885
- configService?: FillerConfigService | undefined);
3886
- send(params: SendTokenParams): Promise<SendTokenResult>;
3887
- /**
3888
- * A `withdraw(shortfall, solver, solver)` call against a configured vault on
3889
- * this chain whose underlying is the token being sent and that can cover the
3890
- * shortfall, or null when no vault can.
3891
- */
3892
- /**
3893
- * A withdrawal that funds the transfer and, where the vault can afford it,
3894
- * leaves headroom in the wallet rather than nothing.
3895
- *
3896
- * `shortfall` is what the transfer cannot do without, and is zero or negative
3897
- * when the wallet already covers it. The headroom is the larger of the
3898
- * paymaster's gas `reserve` and the vault's `minBalance` — the first is what
3899
- * validation will pull out of this wallet, the second is the operator's own
3900
- * statement of the working balance this token keeps. A vault that can only
3901
- * cover the shortfall still funds the send: headroom is a preference, not a
3902
- * reason to refuse a transfer the operator asked for.
3903
- */
3904
- private vaultWithdrawalCall;
3905
- private submit;
3906
- }
3907
-
3908
2360
  /** Everything a running filler exposes to the UI server and the CLI. */
3909
2361
  interface FillerRuntime {
3910
2362
  intentFiller: IntentFiller;
3911
2363
  balanceProvider: BalanceProvider;
3912
2364
  vaultVenue?: VaultFundingPlanner;
3913
- /** The markets this filler serves, for the operator UI. */
3914
- adminStrategies: AdminStrategy[];
3915
2365
  /** Self-halt visibility/reset for the trading engine (overfill protection). */
3916
2366
  haltControls: HaltControl[];
3917
2367
  /** Live order-activity bridge; emits `event` per stored row. */
@@ -3944,12 +2394,6 @@ interface FillerRuntime {
3944
2394
  assetRegistry: AssetRegistry;
3945
2395
  /** Creates and posts the operator's limit orders. */
3946
2396
  limitOrders?: LimitOrderService;
3947
- /** The live trading engine. It runs whether or not the config names any pairs. */
3948
- engine: FXFiller;
3949
- /** The engine's live pair array (same instance), indexed 1:1 with config.pairs. */
3950
- tradingPairs: TradingPair[];
3951
- /** BalanceProvider's live token1 map (chain name to exotic addresses); mutations apply on its next refresh. */
3952
- balanceTokens: Record<string, string[]>;
3953
2397
  rebalancingService?: RebalancingService;
3954
2398
  resolvedChains: ResolvedChainConfig[];
3955
2399
  fillerAddress: HexString;
@@ -3969,6 +2413,8 @@ interface FillerRuntime {
3969
2413
  vaultPreflight(vaults: VaultToml[]): Promise<void>;
3970
2414
  /** Operator-initiated outbound transfers (dashboard Send). */
3971
2415
  tokenSender: TokenSender;
2416
+ /** Buys BRIDGE on BSC for the filler's Hyperbridge account. Absent without a substrate key. */
2417
+ bridgeTopUp?: BridgeTopUp;
3972
2418
  startedAt: number;
3973
2419
  /** Stops everything bootFiller started. Idempotent; does NOT process.exit. */
3974
2420
  shutdown(signal: string): Promise<void>;
@@ -4118,23 +2564,16 @@ interface SimplexEvents {
4118
2564
  order: LimitOrder;
4119
2565
  };
4120
2566
  }
4121
- interface PairView {
4122
- index: number;
4123
- token0: string;
4124
- token1: string;
4125
- /** token0 === token1: the same-asset cross-chain market. */
4126
- sameToken: boolean;
4127
- }
4128
2567
  interface ChainView {
4129
2568
  chainId: number;
4130
2569
  stateMachineId: string;
4131
2570
  rpcUrls: string[];
2571
+ /** Hyperbridge's bundler the chain fills through, empty where it runs none. */
4132
2572
  bundlerUrl: string;
4133
2573
  watchOnly: boolean;
4134
2574
  }
4135
2575
  interface ChainInput {
4136
2576
  rpcUrls: string[];
4137
- bundlerUrl: string;
4138
2577
  watchOnly?: boolean;
4139
2578
  /**
4140
2579
  * Required for chains with no built-in confirmation curve (testnets and
@@ -4152,9 +2591,7 @@ interface SimplexStatus {
4152
2591
  uptimeSec: number;
4153
2592
  chains: number[];
4154
2593
  watchOnly: Record<number, boolean>;
4155
- pairs: number;
4156
2594
  }
4157
- /** Trading pairs of the running engine. Every mutation binds on the next order. */
4158
2595
  /**
4159
2596
  * The operator's limit orders: what simplex offers to pay, and what the
4160
2597
  * orderbook advertises on its behalf.
@@ -4198,7 +2635,7 @@ declare class LimitOrderController {
4198
2635
  profitability(period: ProfitPeriod, tzOffsetMinutes?: number): Promise<Profitability>;
4199
2636
  /** One reader for the life of the filler, so the readings it has already fetched are kept. */
4200
2637
  private indexerInventory;
4201
- /** The pairs the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
2638
+ /** The books the orderbook lists, the smallest payout each token may carry, and its tokens per chain. */
4202
2639
  books(): Promise<{
4203
2640
  books: Book[];
4204
2641
  minOrderSizes: TokenMinSize[];
@@ -4214,26 +2651,6 @@ declare class LimitOrderController {
4214
2651
  }
4215
2652
  /** How the controller publishes an outcome on the `Simplex` it belongs to. */
4216
2653
  type LimitOrderEmitter = <E extends "limit-order:posted" | "limit-order:rejected" | "limit-order:cancelled" | "limit-order:resized" | "limit-order:filled">(event: E, payload: SimplexEvents[E]) => void;
4217
- declare class PairController {
4218
- private runtime;
4219
- private persist;
4220
- constructor(runtime: FillerRuntime, persist: () => void | Promise<void>);
4221
- private get pairs();
4222
- list(): PairView[];
4223
- /**
4224
- * Opens a new market on the running engine.
4225
- *
4226
- * Validated against the same rules boot applies, so a pair accepted here is
4227
- * one a restart would also accept. New `assets` are registered first, because
4228
- * the pair's symbols have to resolve before the market can be hydrated.
4229
- */
4230
- add(pair: PairConfig, options?: {
4231
- assets?: Record<string, AssetDefinition>;
4232
- }): Promise<PairView>;
4233
- /** Closes a market. Orders already in flight against it still complete. */
4234
- remove(index: number): Promise<void>;
4235
- private livePair;
4236
- }
4237
2654
  /** The chain set of the running filler. */
4238
2655
  declare class ChainController {
4239
2656
  private runtime;
@@ -4282,7 +2699,6 @@ declare class ChainController {
4282
2699
  * leaves no gap in the scanned block range.
4283
2700
  */
4284
2701
  setRpcUrls(chainId: number, rpcUrls: string[]): Promise<void>;
4285
- setBundlerUrl(chainId: number, bundlerUrl: string): Promise<void>;
4286
2702
  /** Monitor without filling. Takes effect on the next order. */
4287
2703
  setWatchOnly(chainId: number, watchOnly: boolean): Promise<void>;
4288
2704
  /** A global boolean is left alone — expanding it per chain changes how the config validates. */
@@ -4358,7 +2774,7 @@ declare class RebalanceController {
4358
2774
  *
4359
2775
  * Start one with {@link Simplex.start}, which boots every service, delegates the
4360
2776
  * filler account where solver selection is active, and begins scanning the
4361
- * configured chains. The instance is the whole control surface: trading pairs,
2777
+ * configured chains. The instance is the whole control surface: limit orders,
4362
2778
  * chains, vaults, assets, wallet and rebalancing are reachable as controllers,
4363
2779
  * and everything the filler does is observable through {@link SimplexEvents}.
4364
2780
  *
@@ -4369,12 +2785,11 @@ declare class RebalanceController {
4369
2785
  *
4370
2786
  * More than one instance can run in one process. They share no state.
4371
2787
  */
4372
- declare class Simplex extends EventEmitter$1 {
2788
+ declare class Simplex extends EventEmitter {
4373
2789
  private runtime;
4374
2790
  private options;
4375
2791
  /** The scanner this filler built for itself, and must therefore close. */
4376
2792
  private ownedScanners;
4377
- readonly pairs: PairController;
4378
2793
  readonly chains: ChainController;
4379
2794
  readonly vaults: VaultController;
4380
2795
  readonly assets: AssetController;
@@ -4521,4 +2936,4 @@ declare class OrderScanner implements OrderScanner$1 {
4521
2936
  private closeNow;
4522
2937
  }
4523
2938
 
4524
- export { ActivityEvent, ActivityInsert, ActivityStore, type AllowlistConfig, type AssetBalanceRow, AssetController, type AssetDefinition, type AuthorizationRequest, type BalanceIssue, type BalanceSnapshot, BidStats, BidStore, type BinanceConfig, type BookProfit, type CancelledLimitOrder, type ChainBalanceRow, type ChainConfirmationPolicy, ChainController, type ChainInput, type ChainView, type CreateLimitOrderRequest, type CurveConfig, type CurvePoint, type DerivingSigner, type Eip7702Authorization$1 as Eip7702Authorization, type FillerConfigFile, type FillerTomlConfig, type HyperbridgeBalance, InventoryStore, LimitOrder, LimitOrderController, LimitOrderFill, LimitOrderFilter, LimitOrderStore, LimitOrderValidationError, LogLevel, LogSink, Logger, LoggerContext, MemoryDataStore, type MpcVaultSignerConfig, OrderScanner, type OrderScanner$1 as OrderScannerContract, type OrderScannerHandlers, type OrderScannerOptions, type PairConfig, PairController, type PairView, type PostedLimitOrder, type PostingOutcome, type PrivateKeySignerConfig, type ProfitBucket, type ProfitBucketFigures, type ProfitFigures, type ProfitPeriod, type Profitability, type QueueConfig, RebalanceController, type RebalancingConfig, type ResolvedChainConfig, type ScannedFill, type ScannedOrder, type ScannerChainConfig, type SecretPhraseSignerConfig, type Signature, type Signer, type SignerConfig, type SignerTransaction, SignerType, Simplex, type SimplexConfig, SimplexDataStore, type SimplexEvents, type SimplexOptions, type SimplexStatus, type SolverWork, type StartingInventory, StateStore, StoredBid, type Subscription, type TurnkeySignerConfig, type TypedDataPayload, USD_STABLE_SYMBOLS, type UserProvidedChainConfig, type VaultBalanceRow, VaultController, type VaultSweepDeposit, type VaultSweepResult, type VaultSweepSkip, type VaultSweepSkipReason, type VaultSweepSubmission, type VaultToml, type VaultTomlConfig, WalletController, WalletTx, assertConfirmationCoverage, canDerive, createSigner, digestSigner, formatChainKey, isRegistrySymbol, mpcVaultSigner, normalizeSymbol, parseChainKey, pickAnchorStable, privateKeySigner, registrySymbols, secretPhraseSigner, turnkeySigner, validateAssetDefinitions, validateConfig, validatePairConfigs, validateSecretPhrase, validateSignerConfig, validateVaultToml, viemSigner };
2939
+ export { ActivityEvent, ActivityInsert, ActivityStore, AllowlistConfig, type AssetBalanceRow, AssetController, AssetDefinition, type BalanceIssue, type BalanceSnapshot, BidStats, BidStore, type BookProfit, type CancelledLimitOrder, type ChainBalanceRow, ChainConfirmationPolicy, ChainController, type ChainInput, type ChainView, type CreateLimitOrderRequest, DerivingSigner, FillerTomlConfig, type HyperbridgeBalance, InventoryStore, LimitOrder, LimitOrderController, LimitOrderFill, LimitOrderFilter, LimitOrderStore, LimitOrderValidationError, LogLevel, LogSink, LoggerContext, MemoryDataStore, MpcVaultSignerConfig, OrderScanner, type OrderScanner$1 as OrderScannerContract, type OrderScannerHandlers, type OrderScannerOptions, type PostedLimitOrder, type PostingOutcome, type ProfitBucket, type ProfitBucketFigures, type ProfitFigures, type ProfitPeriod, type Profitability, RebalanceController, RebalancingConfig, ResolvedChainConfig, type ScannedFill, type ScannedOrder, type ScannerChainConfig, SecretPhraseSignerConfig, Signature, Signer, SignerConfig, Simplex, type SimplexConfig, SimplexDataStore, type SimplexEvents, type SimplexOptions, type SimplexStatus, type SolverWork, type StartingInventory, StateStore, StoredBid, type Subscription, TurnkeySignerConfig, type VaultBalanceRow, VaultController, type VaultSweepDeposit, type VaultSweepResult, type VaultSweepSkip, type VaultSweepSkipReason, type VaultSweepSubmission, VaultToml, WalletController, WalletTx, canDerive, createSigner, digestSigner, mpcVaultSigner, privateKeySigner, secretPhraseSigner, turnkeySigner, validateSecretPhrase, validateSignerConfig, viemSigner };