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