@quaivault/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2271 @@
1
+ import * as quais from 'quais';
2
+ import { Provider, Signer, Contract, Interface } from 'quais';
3
+ export { Provider, Signer } from 'quais';
4
+ import * as _supabase_postgrest_js from '@supabase/postgrest-js';
5
+ import { SupabaseClient } from '@supabase/supabase-js';
6
+ import { z } from 'zod';
7
+ export { Erc1155Abi, Erc20Abi, Erc721Abi, MultiSendCallOnlyAbi, QuaiVaultAbi, QuaiVaultFactoryAbi, QuaiVaultProxyAbi, QuaiVaultProxyBytecode, SocialRecoveryModuleAbi } from './abi/index.js';
8
+
9
+ declare const mainnet: NetworkConfig;
10
+ declare const testnet: NetworkConfig;
11
+ declare const networks: {
12
+ readonly mainnet: NetworkConfig;
13
+ readonly testnet: NetworkConfig;
14
+ };
15
+ type NetworkName = keyof typeof networks;
16
+ declare function isNetworkName(value: unknown): value is NetworkName;
17
+
18
+ type Address = string;
19
+ type Hex = string;
20
+ type Bytes32 = string;
21
+
22
+ /** Zodiac IAvatar operation type. Mirrors `Enum.Operation` in the contracts. */
23
+ declare enum Operation {
24
+ Call = 0,
25
+ DelegateCall = 1
26
+ }
27
+ interface ContractAddresses {
28
+ implementation: Address;
29
+ factory: Address;
30
+ socialRecovery?: Address;
31
+ multiSendCallOnly?: Address;
32
+ }
33
+ interface IndexerConfig {
34
+ /** Supabase project URL. */
35
+ url: string;
36
+ /** Public anon key. Read-only by RLS policy — safe to distribute. */
37
+ anonKey: string;
38
+ /** Postgres schema for this network (`mainnet`, `testnet`, `dev`). */
39
+ schema: string;
40
+ /** Base URL of the indexer health server, used for lag/liveness checks. */
41
+ healthUrl?: string;
42
+ }
43
+ interface NetworkConfig {
44
+ name: string;
45
+ chainId: number;
46
+ rpcUrl: string;
47
+ explorerUrl?: string;
48
+ contracts: ContractAddresses;
49
+ indexer?: IndexerConfig;
50
+ }
51
+ /**
52
+ * How reads resolve between the indexer and the chain.
53
+ *
54
+ * - `indexed` — indexer only. Fastest; may be stale; fails if the indexer is down.
55
+ * - `chain` — RPC only. Authoritative; slower; some history queries are unavailable.
56
+ * - `auto` — indexer when it is fresh enough (see {@link ClientOptions.maxIndexerLagBlocks}),
57
+ * otherwise the chain. This is the default.
58
+ *
59
+ * Write paths always re-validate preconditions against the chain regardless of this
60
+ * setting — indexed approval counts can diverge from on-chain reality after an owner
61
+ * is removed (approval epochs), so they must never gate a signature.
62
+ */
63
+ type Consistency = 'auto' | 'indexed' | 'chain';
64
+ interface ClientOptions {
65
+ network?: NetworkConfig | keyof typeof networks;
66
+ provider?: Provider;
67
+ signer?: Signer;
68
+ /** Private key for a local signer. Prefer the `QUAIVAULT_PRIVATE_KEY` env var. */
69
+ privateKey?: string;
70
+ rpcUrl?: string;
71
+ indexer?: Partial<IndexerConfig>;
72
+ contracts?: Partial<ContractAddresses>;
73
+ consistency?: Consistency;
74
+ /** Beyond this many blocks behind, `auto` reads fall through to the chain. Default 50. */
75
+ maxIndexerLagBlocks?: number;
76
+ /** Read env vars for anything not passed explicitly. Default true. */
77
+ useEnv?: boolean;
78
+ /**
79
+ * Retry policy for transient RPC and indexer failures. Applies to reads only —
80
+ * writes are never retried, since a resubmit risks a double broadcast.
81
+ */
82
+ retry?: {
83
+ maxAttempts?: number;
84
+ baseDelayMs?: number;
85
+ maxDelayMs?: number;
86
+ onRetry?: (info: {
87
+ attempt: number;
88
+ delayMs: number;
89
+ error: unknown;
90
+ }) => void;
91
+ };
92
+ }
93
+ interface VaultInfo {
94
+ address: Address;
95
+ owners: Address[];
96
+ threshold: number;
97
+ /** Vault-level minimum timelock for external calls, in seconds. 0 = simple quorum. */
98
+ minExecutionDelay: number;
99
+ nonce: number;
100
+ balance: bigint;
101
+ moduleCount: number;
102
+ }
103
+ type TransactionKind = 'transfer' | 'erc20_transfer' | 'erc721_transfer' | 'erc1155_transfer' | 'wallet_admin' | 'module_config' | 'module_execution' | 'message_signing' | 'recovery_setup' | 'batched_call' | 'external_call' | 'unknown';
104
+ /**
105
+ * Lifecycle state of a vault transaction.
106
+ *
107
+ * `ready` and `timelocked` are SDK-derived refinements of the contract's "pending":
108
+ * both mean not-yet-executed with quorum reached, differing only on whether the
109
+ * execution delay has elapsed.
110
+ */
111
+ type TransactionStatus = 'pending' | 'timelocked' | 'ready' | 'executed' | 'failed' | 'cancelled' | 'expired';
112
+ interface ApprovalRecord {
113
+ owner: Address;
114
+ /** Whether this approval currently counts toward the threshold. */
115
+ active: boolean;
116
+ }
117
+ interface DecodedCall {
118
+ /** Function name, e.g. `addOwner`. */
119
+ name: string;
120
+ signature: string;
121
+ args: Record<string, unknown>;
122
+ /** Contract the selector was resolved against. */
123
+ target: 'vault' | 'socialRecovery' | 'multiSend' | 'erc20' | 'erc721' | 'erc1155';
124
+ }
125
+ interface VaultTransaction {
126
+ hash: Bytes32;
127
+ vault: Address;
128
+ to: Address;
129
+ value: bigint;
130
+ data: Hex;
131
+ proposer: Address;
132
+ /** Unix seconds when the proposal was recorded on chain. */
133
+ proposedAt: number;
134
+ kind: TransactionKind;
135
+ decoded?: DecodedCall;
136
+ /** One-line human-readable description. */
137
+ summary: string;
138
+ status: TransactionStatus;
139
+ approvals: ApprovalRecord[];
140
+ /** Count of approvals that currently count toward the threshold. */
141
+ approvalCount: number;
142
+ threshold: number;
143
+ /** Unix seconds after which the tx can no longer execute. 0 = never expires. */
144
+ expiration: number;
145
+ /** Seconds of timelock locked in at proposal time. */
146
+ executionDelay: number;
147
+ /** Unix seconds when quorum was first reached. 0 = never reached. */
148
+ approvedAt: number;
149
+ /** `approvedAt + executionDelay`, or 0 if the clock has not started. */
150
+ executableAfter: number;
151
+ /** Raw revert data from a `TransactionFailed` event. */
152
+ failedReturnData?: Hex;
153
+ decodedRevert?: DecodedRevert;
154
+ /** Where this record came from. */
155
+ source: 'indexer' | 'chain';
156
+ /** Indexer head at read time; absent for chain reads. */
157
+ indexedAtBlock?: number;
158
+ }
159
+ interface DecodedRevert {
160
+ /** Custom error name, or `Error` / `Panic` for the builtins. */
161
+ name: string;
162
+ args: unknown[];
163
+ selector: Hex;
164
+ /** Human-readable rendering. */
165
+ message: string;
166
+ }
167
+ interface ProposeOptions {
168
+ /** Unix seconds after which the tx can no longer execute. 0 / omitted = no expiry. */
169
+ expiration?: number;
170
+ /** Extra delay beyond the vault floor, in seconds. */
171
+ executionDelay?: number;
172
+ /** Build and return the call without signing. */
173
+ dryRun?: boolean;
174
+ }
175
+ interface ProposeResult {
176
+ /** The vault-transaction hash (bytes32), not the Quai transaction hash. */
177
+ txHash: Bytes32;
178
+ /** The on-chain Quai transaction hash that carried the proposal. */
179
+ chainTxHash: Hex;
180
+ to: Address;
181
+ value: bigint;
182
+ data: Hex;
183
+ }
184
+ /** Result of a dry-run write: everything needed to inspect or submit later. */
185
+ interface DryRunResult {
186
+ dryRun: true;
187
+ to: Address;
188
+ data: Hex;
189
+ value: bigint;
190
+ /** Estimated gas, or null if estimation reverted. */
191
+ gasEstimate: bigint | null;
192
+ /** Decoded revert if estimation failed. */
193
+ wouldRevert?: DecodedRevert;
194
+ description: string;
195
+ }
196
+ /**
197
+ * Outcome of an execute attempt.
198
+ *
199
+ * A successful Quai transaction receipt does NOT imply the vault transaction ran.
200
+ * See `docs/execution-outcomes.md`.
201
+ */
202
+ type ExecuteOutcome = 'executed' | 'failed' | 'timelock_started' | 'approved_only';
203
+ interface ExecuteResult {
204
+ outcome: ExecuteOutcome;
205
+ txHash: Bytes32;
206
+ chainTxHash: Hex;
207
+ blockNumber: number;
208
+ gasUsed: bigint;
209
+ /** Set when `outcome === 'failed'`. */
210
+ returnData?: Hex;
211
+ decodedRevert?: DecodedRevert;
212
+ /** Set when `outcome === 'timelock_started'`. Unix seconds. */
213
+ executableAfter?: number;
214
+ /** Set when `outcome === 'approved_only'`. */
215
+ approvalsNeeded?: number;
216
+ /** Human-readable explanation of what happened and what to do next. */
217
+ message: string;
218
+ }
219
+ type VaultAction = 'approve' | 'revokeApproval' | 'execute' | 'approveAndExecute' | 'cancel' | 'expire' | 'proposeCancelByConsensus';
220
+ type AffordanceBlocker = 'not_owner' | 'already_approved' | 'not_approved' | 'threshold' | 'timelock' | 'expired' | 'terminal_state' | 'quorum_locked' | 'not_proposer' | 'no_expiration';
221
+ interface Affordance {
222
+ action: VaultAction;
223
+ allowed: boolean;
224
+ /** Plain-language explanation, suitable for surfacing directly to a user. */
225
+ reason: string;
226
+ /** Unix seconds when a time gate lifts, if the only blocker is time. */
227
+ availableAt?: number;
228
+ blockedBy?: AffordanceBlocker;
229
+ }
230
+ interface CreateVaultParams {
231
+ owners: Address[];
232
+ threshold: number;
233
+ /** Vault-level minimum timelock in seconds. Max 30 days. Default 0. */
234
+ minExecutionDelay?: number;
235
+ /** Modules enabled at deploy time. */
236
+ initialModules?: Address[];
237
+ /** DelegateCall whitelist entries. Empty (default) means DelegateCall is disabled. */
238
+ initialDelegatecallTargets?: Address[];
239
+ /** Pre-mined salt. Omit to mine one automatically. */
240
+ salt?: Bytes32;
241
+ }
242
+ interface MinedSalt {
243
+ salt: Bytes32;
244
+ predictedAddress: Address;
245
+ attempts: number;
246
+ durationMs: number;
247
+ }
248
+ interface CreateVaultResult {
249
+ address: Address;
250
+ chainTxHash: Hex;
251
+ salt: Bytes32;
252
+ /** Whether the deployed address matched the mined prediction. */
253
+ predictionMatched: boolean;
254
+ }
255
+ interface RecoveryConfig {
256
+ guardians: Address[];
257
+ /** Guardian approvals required to execute a recovery. */
258
+ threshold: number;
259
+ /** Delay in seconds between initiation and executability. */
260
+ recoveryPeriod: number;
261
+ /** False when the vault has no recovery configured. */
262
+ configured: boolean;
263
+ }
264
+ /**
265
+ * Lifecycle state of a recovery request.
266
+ *
267
+ * `cancelled` is only ever reported from the indexer: `cancelRecovery` deletes the
268
+ * on-chain struct, so a chain read cannot distinguish a cancelled recovery from one
269
+ * that never existed.
270
+ */
271
+ type RecoveryStatus = 'pending' | 'timelocked' | 'ready' | 'executed' | 'cancelled' | 'expired';
272
+ interface RecoveryRequest {
273
+ hash: Bytes32;
274
+ vault: Address;
275
+ newOwners: Address[];
276
+ newThreshold: number;
277
+ approvalCount: number;
278
+ /** Threshold captured at initiation — config changes mid-recovery do not move it. */
279
+ requiredThreshold: number;
280
+ /** Unix seconds after which execution is permitted. */
281
+ executionTime: number;
282
+ /** Unix seconds after which the recovery is dead and can be cleaned up. */
283
+ expiration: number;
284
+ status: RecoveryStatus;
285
+ executed: boolean;
286
+ /** Guardians who have approved. Only populated on indexer reads. */
287
+ approvedBy?: Address[];
288
+ initiator?: Address;
289
+ source: 'indexer' | 'chain';
290
+ }
291
+ type RecoveryAction = 'approve' | 'revokeApproval' | 'execute' | 'cancel' | 'expire';
292
+ interface RecoveryAffordance {
293
+ action: RecoveryAction;
294
+ allowed: boolean;
295
+ reason: string;
296
+ availableAt?: number;
297
+ blockedBy?: 'not_guardian' | 'not_owner' | 'already_approved' | 'not_approved' | 'threshold' | 'timelock' | 'expired' | 'not_expired' | 'terminal_state' | 'module_disabled';
298
+ }
299
+ interface Pagination {
300
+ limit?: number;
301
+ offset?: number;
302
+ }
303
+ interface Page<T> {
304
+ data: T[];
305
+ total: number;
306
+ hasMore: boolean;
307
+ }
308
+ interface IndexerHealth {
309
+ available: boolean;
310
+ lastIndexedBlock: number;
311
+ chainHead?: number;
312
+ blocksBehind?: number;
313
+ isSyncing: boolean;
314
+ /** Reason the indexer was judged unavailable. */
315
+ error?: string;
316
+ }
317
+
318
+ /**
319
+ * Retry policy for transient RPC and indexer failures.
320
+ *
321
+ * Scoped deliberately to **reads**. Retrying a write risks broadcasting the same
322
+ * transaction twice — a resubmit that looks like a timeout to the client may already
323
+ * be in the mempool — so every write path in this SDK calls through unretried.
324
+ */
325
+ interface RetryOptions {
326
+ /** Total attempts including the first. Default 3. */
327
+ maxAttempts?: number;
328
+ /** First backoff delay. Doubles each attempt. Default 250ms. */
329
+ baseDelayMs?: number;
330
+ /** Ceiling on any single backoff. Default 4000ms. */
331
+ maxDelayMs?: number;
332
+ signal?: AbortSignal;
333
+ onRetry?: (info: {
334
+ attempt: number;
335
+ delayMs: number;
336
+ error: unknown;
337
+ }) => void;
338
+ }
339
+ /**
340
+ * Whether an error is worth retrying.
341
+ *
342
+ * Errs toward *not* retrying: an unrecognised error is treated as permanent, so a
343
+ * genuine bug surfaces immediately instead of being masked by three slow attempts.
344
+ */
345
+ declare function isTransient(error: unknown): boolean;
346
+ /**
347
+ * Run `fn`, retrying transient failures with exponential backoff and full jitter.
348
+ *
349
+ * Jitter matters more than usual here: several vaults refreshing on the same tick
350
+ * would otherwise retry in lockstep and hammer a rate-limited endpoint in waves.
351
+ */
352
+ declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
353
+
354
+ /**
355
+ * Environment variables read when `useEnv` is not disabled.
356
+ *
357
+ * Explicit `ClientOptions` always win over the environment; the environment always
358
+ * wins over the network preset. Nothing here is read at module load — resolution
359
+ * happens inside `connect()`, so importing the SDK never touches `process.env`.
360
+ */
361
+ declare const ENV_VARS: {
362
+ readonly network: "QUAIVAULT_NETWORK";
363
+ readonly rpcUrl: "QUAIVAULT_RPC_URL";
364
+ readonly privateKey: "QUAIVAULT_PRIVATE_KEY";
365
+ readonly indexerUrl: "QUAIVAULT_INDEXER_URL";
366
+ readonly indexerAnonKey: "QUAIVAULT_INDEXER_ANON_KEY";
367
+ readonly indexerSchema: "QUAIVAULT_INDEXER_SCHEMA";
368
+ readonly indexerHealthUrl: "QUAIVAULT_INDEXER_HEALTH_URL";
369
+ readonly factory: "QUAIVAULT_FACTORY";
370
+ readonly implementation: "QUAIVAULT_IMPLEMENTATION";
371
+ readonly socialRecovery: "QUAIVAULT_SOCIAL_RECOVERY_MODULE";
372
+ readonly multiSendCallOnly: "QUAIVAULT_MULTISEND_CALL_ONLY";
373
+ readonly consistency: "QUAIVAULT_CONSISTENCY";
374
+ readonly maxIndexerLagBlocks: "QUAIVAULT_MAX_INDEXER_LAG_BLOCKS";
375
+ };
376
+ interface ResolvedConfig {
377
+ network: NetworkConfig;
378
+ contracts: ContractAddresses;
379
+ indexer?: IndexerConfig;
380
+ rpcUrl: string;
381
+ /**
382
+ * ⚠️ Secret. Consumed once to build a signer and never retained afterwards.
383
+ *
384
+ * Never log or serialise a `ResolvedConfig` that still carries this — use
385
+ * {@link redactConfig}, which is what `QuaiVaultClient.config` exposes.
386
+ */
387
+ privateKey?: string;
388
+ consistency: Consistency;
389
+ maxIndexerLagBlocks: number;
390
+ retry: RetryOptions;
391
+ }
392
+ /** A `ResolvedConfig` with the private key removed. Safe to log. */
393
+ type PublicConfig = Omit<ResolvedConfig, 'privateKey'> & {
394
+ privateKey?: never;
395
+ };
396
+ /**
397
+ * Merge, in order of decreasing precedence: explicit options → environment →
398
+ * network preset defaults.
399
+ */
400
+ declare function resolveConfig(options?: ClientOptions): ResolvedConfig;
401
+
402
+ /**
403
+ * Owns the provider/signer pair and hands out contract instances.
404
+ *
405
+ * Quai RPC endpoints are shard-pathed (`https://host/cyprus1`), which `quais`
406
+ * handles via `usePathing: true` — the same setting the indexer uses.
407
+ */
408
+ declare class Connection {
409
+ readonly provider: Provider;
410
+ /** Retry policy for reads. Shared with every contract facade this hands out. */
411
+ readonly retry: RetryOptions;
412
+ private readonly _signer;
413
+ constructor(config: ResolvedConfig, explicit?: {
414
+ provider?: Provider;
415
+ signer?: Signer;
416
+ });
417
+ get signer(): Signer | null;
418
+ hasSigner(): boolean;
419
+ /** The signer, or a typed error naming the operation that needs one. */
420
+ requireSigner(operation: string): Signer;
421
+ address(): Promise<Address>;
422
+ /** Connected address, or null when read-only. */
423
+ addressOrNull(): Promise<Address | null>;
424
+ vault(address: Address, write?: boolean): Contract;
425
+ factory(address: Address, write?: boolean): Contract;
426
+ socialRecovery(address: Address, write?: boolean): Contract;
427
+ }
428
+
429
+ interface MineSaltOptions {
430
+ factory: Address;
431
+ implementation: Address;
432
+ /** Address that will call `createWallet`. The mined address lands on its shard. */
433
+ deployer: Address;
434
+ params: CreateVaultParams;
435
+ /** Override the target shard prefix. Defaults to the deployer's. */
436
+ targetPrefix?: string;
437
+ maxAttempts?: number;
438
+ /** Abort after this long. Default 120_000 ms. */
439
+ timeoutMs?: number;
440
+ /** Called roughly every 1000 attempts. */
441
+ onProgress?: (attempts: number) => void;
442
+ signal?: AbortSignal;
443
+ }
444
+ interface MiningStrategy {
445
+ readonly name: string;
446
+ mine(options: Required<Pick<MineSaltOptions, 'factory' | 'deployer'>> & ResolvedMineJob): Promise<MinedSalt>;
447
+ }
448
+ /** Everything the inner loop needs, precomputed once. */
449
+ interface ResolvedMineJob {
450
+ bytecodeHash: Bytes32;
451
+ targetPrefix: string;
452
+ maxAttempts: number;
453
+ timeoutMs: number;
454
+ onProgress?: (attempts: number) => void;
455
+ signal?: AbortSignal;
456
+ }
457
+ /**
458
+ * Chunked single-threaded miner. Yields to the event loop between chunks so it does
459
+ * not block a browser UI thread or a Node server's other work.
460
+ *
461
+ * Works everywhere, which makes it the safe default.
462
+ */
463
+ declare const syncStrategy: MiningStrategy;
464
+ /**
465
+ * Node-only multi-core miner. Falls back to {@link syncStrategy} when
466
+ * `node:worker_threads` is unavailable (browsers, restricted runtimes).
467
+ */
468
+ declare const workerThreadsStrategy: MiningStrategy;
469
+ /** Prefer worker_threads on Node; the sync miner degrades gracefully everywhere else. */
470
+ declare function defaultStrategy(): MiningStrategy;
471
+ /**
472
+ * Mine a CREATE2 salt that deploys a vault onto the deployer's Quai shard.
473
+ *
474
+ * The predicted address is a pure function of (factory, implementation, deployer,
475
+ * salt, create params) — so the caller can mine ahead of time, persist the salt,
476
+ * and deploy later, as long as every one of those inputs is unchanged.
477
+ */
478
+ declare function mineSalt(options: MineSaltOptions, strategy?: MiningStrategy): Promise<MinedSalt>;
479
+
480
+ interface CreateProgress {
481
+ step: 'validating' | 'mining' | 'deploying' | 'confirming' | 'done';
482
+ message: string;
483
+ attempts?: number;
484
+ predictedAddress?: Address;
485
+ chainTxHash?: Hex;
486
+ }
487
+ interface FactoryContext {
488
+ connection: Connection;
489
+ contracts: ContractAddresses;
490
+ }
491
+ /** Deploy and look up vaults through `QuaiVaultFactory`. */
492
+ declare class Factory {
493
+ private readonly ctx;
494
+ constructor(ctx: FactoryContext);
495
+ get address(): Address;
496
+ private contract;
497
+ /** The implementation every proxy from this factory delegates to. */
498
+ implementation(): Promise<Address>;
499
+ /** Whether the factory has this vault in its registry. */
500
+ isRegistered(vault: Address): Promise<boolean>;
501
+ vaultCount(): Promise<number>;
502
+ vaultAt(index: number): Promise<Address>;
503
+ /**
504
+ * Verify the configured implementation address matches what the factory reports.
505
+ *
506
+ * A mismatch means salt mining will predict addresses the factory will not deploy
507
+ * to, so it is worth catching before a deployment rather than after.
508
+ */
509
+ verify(): Promise<{
510
+ valid: boolean;
511
+ errors: string[];
512
+ }>;
513
+ /** Off-chain CREATE2 prediction. Identical to `factory.predictWalletAddress`. */
514
+ predictAddress(deployer: Address, salt: Bytes32, params: CreateVaultParams): Address;
515
+ /**
516
+ * Mine a CREATE2 salt placing the vault on the deployer's shard.
517
+ *
518
+ * Quai addresses are shard-scoped, so an arbitrary salt usually produces a vault
519
+ * the deployer cannot reach. The mined salt is only valid for these exact create
520
+ * params — changing owners, threshold, delay, modules or delegatecall targets
521
+ * changes the predicted address.
522
+ */
523
+ mineSalt(params: CreateVaultParams, options?: Partial<Omit<MineSaltOptions, 'factory' | 'implementation' | 'params'>>, strategy?: MiningStrategy): Promise<MinedSalt>;
524
+ /**
525
+ * Deploy a vault, mining a salt first when one is not supplied.
526
+ *
527
+ * Always uses the 6-argument `createWallet` overload so the deployed bytecode
528
+ * matches what mining predicted, whatever combination of modules and delegatecall
529
+ * targets is requested.
530
+ */
531
+ create(params: CreateVaultParams, options?: {
532
+ onProgress?: (progress: CreateProgress) => void;
533
+ miningStrategy?: MiningStrategy;
534
+ maxAttempts?: number;
535
+ timeoutMs?: number;
536
+ }): Promise<CreateVaultResult>;
537
+ /** Register an externally deployed proxy. Callable only by one of its owners. */
538
+ register(vault: Address): Promise<{
539
+ chainTxHash: Hex;
540
+ }>;
541
+ }
542
+
543
+ /**
544
+ * The indexer uses one Postgres schema per network, chosen at runtime, so the
545
+ * client cannot be pinned to supabase-js's default `"public"` schema generic.
546
+ */
547
+ type AnySupabaseClient = SupabaseClient<any, any, any, any, any>;
548
+ /**
549
+ * Thin wrapper over the indexer's Supabase project.
550
+ *
551
+ * The anon key is a public read-only credential: the indexer's RLS grants `anon`
552
+ * SELECT on every table and nothing else, with all writes reserved for
553
+ * `service_role`. It carries no authority beyond reading already-public chain data.
554
+ */
555
+ declare class IndexerClient {
556
+ readonly config: IndexerConfig;
557
+ private readonly client;
558
+ private healthCache;
559
+ private inflightHealth;
560
+ /** Health results are reused for this long to keep read paths cheap. */
561
+ readonly healthCacheMs: number;
562
+ constructor(config: IndexerConfig, options?: {
563
+ healthCacheMs?: number;
564
+ });
565
+ /** Raw Supabase client, for queries the SDK does not wrap. */
566
+ get raw(): AnySupabaseClient;
567
+ from(table: string): _supabase_postgrest_js.PostgrestQueryBuilder<any, any, any, string, unknown>;
568
+ /**
569
+ * Run a query, parse each row, and surface failures as `IndexerQueryError`.
570
+ *
571
+ * No retry wrapper here on purpose. `postgrest-js` already retries GET/HEAD/OPTIONS
572
+ * internally (3 attempts, retryable statuses and fetch errors, honouring
573
+ * `Retry-After`), and it reports failures as a resolved `{ error }` value rather
574
+ * than a rejection — so an outer `withRetry` would be dead code that silently
575
+ * multiplied attempts if it were ever made to work. Chain reads, which do reject,
576
+ * are retried in `src/chain/retry.ts`.
577
+ */
578
+ select<S extends z.ZodTypeAny>(table: string, schema: S, build: (q: ReturnType<AnySupabaseClient["from"]>) => PromiseLike<{
579
+ data: unknown[] | null;
580
+ error: {
581
+ message: string;
582
+ code?: string;
583
+ } | null;
584
+ }>): Promise<Array<z.infer<S>>>;
585
+ /** Single-row variant. Returns null for PostgREST's "no rows" code. */
586
+ selectOne<S extends z.ZodTypeAny>(table: string, schema: S, build: (q: ReturnType<AnySupabaseClient["from"]>) => PromiseLike<{
587
+ data: unknown | null;
588
+ error: {
589
+ message: string;
590
+ code?: string;
591
+ } | null;
592
+ }>): Promise<z.infer<S> | null>;
593
+ /** The indexer's sync head, straight from the `indexer_state` table. */
594
+ state(): Promise<{
595
+ lastIndexedBlock: number;
596
+ isSyncing: boolean;
597
+ } | null>;
598
+ /**
599
+ * Liveness and lag. Prefers the HTTP health endpoint (which knows the chain head)
600
+ * and falls back to the `indexer_state` table when it is unreachable.
601
+ */
602
+ health(force?: boolean): Promise<IndexerHealth>;
603
+ /**
604
+ * Block until the indexer has processed `blockNumber`.
605
+ *
606
+ * Closes the write-then-read race: a transaction confirmed at block N is not
607
+ * queryable until the indexer reaches N, so `propose()` immediately followed by
608
+ * `pendingTransactions()` will silently miss it. Await this in between.
609
+ *
610
+ * Resolves immediately when the head is already at or past the target.
611
+ */
612
+ waitForBlock(blockNumber: number, options?: {
613
+ timeoutMs?: number;
614
+ pollIntervalMs?: number;
615
+ signal?: AbortSignal;
616
+ }): Promise<{
617
+ reached: boolean;
618
+ lastIndexedBlock: number;
619
+ }>;
620
+ private probeHealth;
621
+ }
622
+
623
+ declare const WalletSchema: z.ZodObject<{
624
+ address: z.ZodString;
625
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
626
+ threshold: z.ZodNumber;
627
+ owner_count: z.ZodNumber;
628
+ created_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
629
+ created_at_tx: z.ZodString;
630
+ min_execution_delay: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
631
+ created_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
632
+ updated_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
633
+ }, "strip", z.ZodTypeAny, {
634
+ address: string;
635
+ threshold: number;
636
+ owner_count: number;
637
+ created_at_block: string | number;
638
+ created_at_tx: string;
639
+ name?: string | null | undefined;
640
+ min_execution_delay?: number | null | undefined;
641
+ created_at?: string | null | undefined;
642
+ updated_at?: string | null | undefined;
643
+ }, {
644
+ address: string;
645
+ threshold: number;
646
+ owner_count: number;
647
+ created_at_block: string | number;
648
+ created_at_tx: string;
649
+ name?: string | null | undefined;
650
+ min_execution_delay?: number | null | undefined;
651
+ created_at?: string | null | undefined;
652
+ updated_at?: string | null | undefined;
653
+ }>;
654
+ type WalletRow = z.infer<typeof WalletSchema>;
655
+ declare const WalletOwnerSchema: z.ZodObject<{
656
+ wallet_address: z.ZodString;
657
+ owner_address: z.ZodString;
658
+ added_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
659
+ added_at_tx: z.ZodString;
660
+ removed_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
661
+ removed_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
662
+ is_active: z.ZodBoolean;
663
+ }, "strip", z.ZodTypeAny, {
664
+ wallet_address: string;
665
+ owner_address: string;
666
+ added_at_block: string | number;
667
+ added_at_tx: string;
668
+ is_active: boolean;
669
+ removed_at_block?: string | number | null | undefined;
670
+ removed_at_tx?: string | null | undefined;
671
+ }, {
672
+ wallet_address: string;
673
+ owner_address: string;
674
+ added_at_block: string | number;
675
+ added_at_tx: string;
676
+ is_active: boolean;
677
+ removed_at_block?: string | number | null | undefined;
678
+ removed_at_tx?: string | null | undefined;
679
+ }>;
680
+ type WalletOwnerRow = z.infer<typeof WalletOwnerSchema>;
681
+ declare const TransactionStatusEnum: z.ZodEnum<["pending", "executed", "cancelled", "expired", "failed"]>;
682
+ declare const TransactionSchema: z.ZodObject<{
683
+ wallet_address: z.ZodString;
684
+ tx_hash: z.ZodString;
685
+ to_address: z.ZodString;
686
+ value: z.ZodString;
687
+ data: z.ZodOptional<z.ZodNullable<z.ZodString>>;
688
+ transaction_type: z.ZodString;
689
+ decoded_params: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
690
+ status: z.ZodEnum<["pending", "executed", "cancelled", "expired", "failed"]>;
691
+ confirmation_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
692
+ submitted_by: z.ZodString;
693
+ submitted_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
694
+ submitted_at_tx: z.ZodString;
695
+ executed_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
696
+ executed_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
697
+ executed_by: z.ZodOptional<z.ZodNullable<z.ZodString>>;
698
+ cancelled_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
699
+ cancelled_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
700
+ expiration: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
701
+ execution_delay: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
702
+ approved_at: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
703
+ executable_after: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
704
+ is_expired: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
705
+ failed_return_data: z.ZodOptional<z.ZodNullable<z.ZodString>>;
706
+ created_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
707
+ updated_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
708
+ }, "strip", z.ZodTypeAny, {
709
+ value: string;
710
+ status: "executed" | "cancelled" | "pending" | "failed" | "expired";
711
+ wallet_address: string;
712
+ tx_hash: string;
713
+ to_address: string;
714
+ transaction_type: string;
715
+ submitted_by: string;
716
+ submitted_at_block: string | number;
717
+ submitted_at_tx: string;
718
+ data?: string | null | undefined;
719
+ expiration?: string | number | null | undefined;
720
+ created_at?: string | null | undefined;
721
+ updated_at?: string | null | undefined;
722
+ decoded_params?: unknown;
723
+ confirmation_count?: number | null | undefined;
724
+ executed_at_block?: string | number | null | undefined;
725
+ executed_at_tx?: string | null | undefined;
726
+ executed_by?: string | null | undefined;
727
+ cancelled_at_block?: string | number | null | undefined;
728
+ cancelled_at_tx?: string | null | undefined;
729
+ execution_delay?: number | null | undefined;
730
+ approved_at?: string | number | null | undefined;
731
+ executable_after?: string | number | null | undefined;
732
+ is_expired?: boolean | null | undefined;
733
+ failed_return_data?: string | null | undefined;
734
+ }, {
735
+ value: string;
736
+ status: "executed" | "cancelled" | "pending" | "failed" | "expired";
737
+ wallet_address: string;
738
+ tx_hash: string;
739
+ to_address: string;
740
+ transaction_type: string;
741
+ submitted_by: string;
742
+ submitted_at_block: string | number;
743
+ submitted_at_tx: string;
744
+ data?: string | null | undefined;
745
+ expiration?: string | number | null | undefined;
746
+ created_at?: string | null | undefined;
747
+ updated_at?: string | null | undefined;
748
+ decoded_params?: unknown;
749
+ confirmation_count?: number | null | undefined;
750
+ executed_at_block?: string | number | null | undefined;
751
+ executed_at_tx?: string | null | undefined;
752
+ executed_by?: string | null | undefined;
753
+ cancelled_at_block?: string | number | null | undefined;
754
+ cancelled_at_tx?: string | null | undefined;
755
+ execution_delay?: number | null | undefined;
756
+ approved_at?: string | number | null | undefined;
757
+ executable_after?: string | number | null | undefined;
758
+ is_expired?: boolean | null | undefined;
759
+ failed_return_data?: string | null | undefined;
760
+ }>;
761
+ type TransactionRow = z.infer<typeof TransactionSchema>;
762
+ declare const ConfirmationSchema: z.ZodObject<{
763
+ wallet_address: z.ZodString;
764
+ tx_hash: z.ZodString;
765
+ owner_address: z.ZodString;
766
+ confirmed_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
767
+ confirmed_at_tx: z.ZodString;
768
+ revoked_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
769
+ revoked_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
770
+ is_active: z.ZodBoolean;
771
+ }, "strip", z.ZodTypeAny, {
772
+ wallet_address: string;
773
+ owner_address: string;
774
+ is_active: boolean;
775
+ tx_hash: string;
776
+ confirmed_at_block: string | number;
777
+ confirmed_at_tx: string;
778
+ revoked_at_block?: string | number | null | undefined;
779
+ revoked_at_tx?: string | null | undefined;
780
+ }, {
781
+ wallet_address: string;
782
+ owner_address: string;
783
+ is_active: boolean;
784
+ tx_hash: string;
785
+ confirmed_at_block: string | number;
786
+ confirmed_at_tx: string;
787
+ revoked_at_block?: string | number | null | undefined;
788
+ revoked_at_tx?: string | null | undefined;
789
+ }>;
790
+ type ConfirmationRow = z.infer<typeof ConfirmationSchema>;
791
+ declare const WalletModuleSchema: z.ZodObject<{
792
+ wallet_address: z.ZodString;
793
+ module_address: z.ZodString;
794
+ enabled_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
795
+ enabled_at_tx: z.ZodString;
796
+ disabled_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
797
+ disabled_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
798
+ is_active: z.ZodBoolean;
799
+ }, "strip", z.ZodTypeAny, {
800
+ wallet_address: string;
801
+ is_active: boolean;
802
+ module_address: string;
803
+ enabled_at_block: string | number;
804
+ enabled_at_tx: string;
805
+ disabled_at_block?: string | number | null | undefined;
806
+ disabled_at_tx?: string | null | undefined;
807
+ }, {
808
+ wallet_address: string;
809
+ is_active: boolean;
810
+ module_address: string;
811
+ enabled_at_block: string | number;
812
+ enabled_at_tx: string;
813
+ disabled_at_block?: string | number | null | undefined;
814
+ disabled_at_tx?: string | null | undefined;
815
+ }>;
816
+ type WalletModuleRow = z.infer<typeof WalletModuleSchema>;
817
+ declare const DelegatecallTargetSchema: z.ZodObject<{
818
+ wallet_address: z.ZodString;
819
+ target_address: z.ZodString;
820
+ added_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
821
+ added_at_tx: z.ZodString;
822
+ removed_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
823
+ removed_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
824
+ is_active: z.ZodBoolean;
825
+ }, "strip", z.ZodTypeAny, {
826
+ wallet_address: string;
827
+ added_at_block: string | number;
828
+ added_at_tx: string;
829
+ is_active: boolean;
830
+ target_address: string;
831
+ removed_at_block?: string | number | null | undefined;
832
+ removed_at_tx?: string | null | undefined;
833
+ }, {
834
+ wallet_address: string;
835
+ added_at_block: string | number;
836
+ added_at_tx: string;
837
+ is_active: boolean;
838
+ target_address: string;
839
+ removed_at_block?: string | number | null | undefined;
840
+ removed_at_tx?: string | null | undefined;
841
+ }>;
842
+ type DelegatecallTargetRow = z.infer<typeof DelegatecallTargetSchema>;
843
+ declare const DepositSchema: z.ZodObject<{
844
+ wallet_address: z.ZodString;
845
+ sender_address: z.ZodString;
846
+ amount: z.ZodString;
847
+ deposited_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
848
+ deposited_at_tx: z.ZodString;
849
+ created_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
850
+ }, "strip", z.ZodTypeAny, {
851
+ amount: string;
852
+ wallet_address: string;
853
+ sender_address: string;
854
+ deposited_at_block: string | number;
855
+ deposited_at_tx: string;
856
+ created_at?: string | null | undefined;
857
+ }, {
858
+ amount: string;
859
+ wallet_address: string;
860
+ sender_address: string;
861
+ deposited_at_block: string | number;
862
+ deposited_at_tx: string;
863
+ created_at?: string | null | undefined;
864
+ }>;
865
+ type DepositRow = z.infer<typeof DepositSchema>;
866
+ declare const TokenSchema: z.ZodObject<{
867
+ address: z.ZodString;
868
+ standard: z.ZodEnum<["ERC20", "ERC721", "ERC1155"]>;
869
+ symbol: z.ZodString;
870
+ name: z.ZodString;
871
+ decimals: z.ZodNumber;
872
+ discovered_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
873
+ discovered_via: z.ZodOptional<z.ZodNullable<z.ZodString>>;
874
+ }, "strip", z.ZodTypeAny, {
875
+ symbol: string;
876
+ address: string;
877
+ name: string;
878
+ standard: "ERC20" | "ERC721" | "ERC1155";
879
+ decimals: number;
880
+ discovered_at_block?: string | number | null | undefined;
881
+ discovered_via?: string | null | undefined;
882
+ }, {
883
+ symbol: string;
884
+ address: string;
885
+ name: string;
886
+ standard: "ERC20" | "ERC721" | "ERC1155";
887
+ decimals: number;
888
+ discovered_at_block?: string | number | null | undefined;
889
+ discovered_via?: string | null | undefined;
890
+ }>;
891
+ type TokenRow = z.infer<typeof TokenSchema>;
892
+ declare const TokenTransferSchema: z.ZodObject<{
893
+ token_address: z.ZodString;
894
+ wallet_address: z.ZodString;
895
+ from_address: z.ZodString;
896
+ to_address: z.ZodString;
897
+ value: z.ZodString;
898
+ token_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
899
+ batch_index: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
900
+ direction: z.ZodEnum<["inflow", "outflow"]>;
901
+ block_number: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
902
+ transaction_hash: z.ZodString;
903
+ log_index: z.ZodNumber;
904
+ }, "strip", z.ZodTypeAny, {
905
+ value: string;
906
+ wallet_address: string;
907
+ to_address: string;
908
+ token_address: string;
909
+ from_address: string;
910
+ direction: "inflow" | "outflow";
911
+ block_number: string | number;
912
+ transaction_hash: string;
913
+ log_index: number;
914
+ token_id?: string | null | undefined;
915
+ batch_index?: number | null | undefined;
916
+ }, {
917
+ value: string;
918
+ wallet_address: string;
919
+ to_address: string;
920
+ token_address: string;
921
+ from_address: string;
922
+ direction: "inflow" | "outflow";
923
+ block_number: string | number;
924
+ transaction_hash: string;
925
+ log_index: number;
926
+ token_id?: string | null | undefined;
927
+ batch_index?: number | null | undefined;
928
+ }>;
929
+ type TokenTransferRow = z.infer<typeof TokenTransferSchema>;
930
+ declare const SignedMessageSchema: z.ZodObject<{
931
+ wallet_address: z.ZodString;
932
+ msg_hash: z.ZodString;
933
+ data: z.ZodOptional<z.ZodNullable<z.ZodString>>;
934
+ signed_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
935
+ signed_at_tx: z.ZodString;
936
+ unsigned_at_block: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
937
+ unsigned_at_tx: z.ZodOptional<z.ZodNullable<z.ZodString>>;
938
+ is_active: z.ZodBoolean;
939
+ }, "strip", z.ZodTypeAny, {
940
+ wallet_address: string;
941
+ is_active: boolean;
942
+ msg_hash: string;
943
+ signed_at_block: string | number;
944
+ signed_at_tx: string;
945
+ data?: string | null | undefined;
946
+ unsigned_at_block?: string | number | null | undefined;
947
+ unsigned_at_tx?: string | null | undefined;
948
+ }, {
949
+ wallet_address: string;
950
+ is_active: boolean;
951
+ msg_hash: string;
952
+ signed_at_block: string | number;
953
+ signed_at_tx: string;
954
+ data?: string | null | undefined;
955
+ unsigned_at_block?: string | number | null | undefined;
956
+ unsigned_at_tx?: string | null | undefined;
957
+ }>;
958
+ type SignedMessageRow = z.infer<typeof SignedMessageSchema>;
959
+ declare const IndexerStateSchema: z.ZodObject<{
960
+ id: z.ZodString;
961
+ last_indexed_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
962
+ last_block_hash: z.ZodOptional<z.ZodNullable<z.ZodString>>;
963
+ last_indexed_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
964
+ is_syncing: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
965
+ }, "strip", z.ZodTypeAny, {
966
+ id: string;
967
+ last_indexed_block: string | number;
968
+ last_block_hash?: string | null | undefined;
969
+ last_indexed_at?: string | null | undefined;
970
+ is_syncing?: boolean | null | undefined;
971
+ }, {
972
+ id: string;
973
+ last_indexed_block: string | number;
974
+ last_block_hash?: string | null | undefined;
975
+ last_indexed_at?: string | null | undefined;
976
+ is_syncing?: boolean | null | undefined;
977
+ }>;
978
+ type IndexerStateRow = z.infer<typeof IndexerStateSchema>;
979
+ declare const RecoveryConfigSchema: z.ZodObject<{
980
+ wallet_address: z.ZodString;
981
+ threshold: z.ZodNumber;
982
+ recovery_period: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
983
+ setup_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
984
+ setup_at_tx: z.ZodString;
985
+ is_active: z.ZodBoolean;
986
+ }, "strip", z.ZodTypeAny, {
987
+ threshold: number;
988
+ wallet_address: string;
989
+ is_active: boolean;
990
+ recovery_period: string | number;
991
+ setup_at_block: string | number;
992
+ setup_at_tx: string;
993
+ }, {
994
+ threshold: number;
995
+ wallet_address: string;
996
+ is_active: boolean;
997
+ recovery_period: string | number;
998
+ setup_at_block: string | number;
999
+ setup_at_tx: string;
1000
+ }>;
1001
+ type RecoveryConfigRow = z.infer<typeof RecoveryConfigSchema>;
1002
+ declare const RecoveryGuardianSchema: z.ZodObject<{
1003
+ wallet_address: z.ZodString;
1004
+ guardian_address: z.ZodString;
1005
+ added_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
1006
+ added_at_tx: z.ZodString;
1007
+ is_active: z.ZodBoolean;
1008
+ }, "strip", z.ZodTypeAny, {
1009
+ wallet_address: string;
1010
+ added_at_block: string | number;
1011
+ added_at_tx: string;
1012
+ is_active: boolean;
1013
+ guardian_address: string;
1014
+ }, {
1015
+ wallet_address: string;
1016
+ added_at_block: string | number;
1017
+ added_at_tx: string;
1018
+ is_active: boolean;
1019
+ guardian_address: string;
1020
+ }>;
1021
+ type RecoveryGuardianRow = z.infer<typeof RecoveryGuardianSchema>;
1022
+ declare const RecoverySchema: z.ZodObject<{
1023
+ wallet_address: z.ZodString;
1024
+ recovery_hash: z.ZodString;
1025
+ new_owners: z.ZodArray<z.ZodString, "many">;
1026
+ new_threshold: z.ZodNumber;
1027
+ initiator_address: z.ZodString;
1028
+ approval_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
1029
+ required_threshold: z.ZodNumber;
1030
+ execution_time: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
1031
+ status: z.ZodEnum<["pending", "executed", "cancelled", "invalidated", "expired"]>;
1032
+ initiated_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
1033
+ initiated_at_tx: z.ZodString;
1034
+ expiration: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodNumber, z.ZodString]>>>;
1035
+ }, "strip", z.ZodTypeAny, {
1036
+ status: "executed" | "cancelled" | "pending" | "expired" | "invalidated";
1037
+ wallet_address: string;
1038
+ recovery_hash: string;
1039
+ new_owners: string[];
1040
+ new_threshold: number;
1041
+ initiator_address: string;
1042
+ required_threshold: number;
1043
+ execution_time: string | number;
1044
+ initiated_at_block: string | number;
1045
+ initiated_at_tx: string;
1046
+ expiration?: string | number | null | undefined;
1047
+ approval_count?: number | null | undefined;
1048
+ }, {
1049
+ status: "executed" | "cancelled" | "pending" | "expired" | "invalidated";
1050
+ wallet_address: string;
1051
+ recovery_hash: string;
1052
+ new_owners: string[];
1053
+ new_threshold: number;
1054
+ initiator_address: string;
1055
+ required_threshold: number;
1056
+ execution_time: string | number;
1057
+ initiated_at_block: string | number;
1058
+ initiated_at_tx: string;
1059
+ expiration?: string | number | null | undefined;
1060
+ approval_count?: number | null | undefined;
1061
+ }>;
1062
+ type RecoveryRow = z.infer<typeof RecoverySchema>;
1063
+ declare const RecoveryApprovalSchema: z.ZodObject<{
1064
+ wallet_address: z.ZodString;
1065
+ recovery_hash: z.ZodString;
1066
+ guardian_address: z.ZodString;
1067
+ approved_at_block: z.ZodUnion<[z.ZodNumber, z.ZodString]>;
1068
+ approved_at_tx: z.ZodString;
1069
+ is_active: z.ZodBoolean;
1070
+ }, "strip", z.ZodTypeAny, {
1071
+ wallet_address: string;
1072
+ is_active: boolean;
1073
+ guardian_address: string;
1074
+ recovery_hash: string;
1075
+ approved_at_block: string | number;
1076
+ approved_at_tx: string;
1077
+ }, {
1078
+ wallet_address: string;
1079
+ is_active: boolean;
1080
+ guardian_address: string;
1081
+ recovery_hash: string;
1082
+ approved_at_block: string | number;
1083
+ approved_at_tx: string;
1084
+ }>;
1085
+ type RecoveryApprovalRow = z.infer<typeof RecoveryApprovalSchema>;
1086
+ /** Coerce the bigint-ish columns Postgres returns as strings. */
1087
+ declare function toNumber(value: unknown, fallback?: number): number;
1088
+
1089
+ type schemas_ConfirmationRow = ConfirmationRow;
1090
+ declare const schemas_ConfirmationSchema: typeof ConfirmationSchema;
1091
+ type schemas_DelegatecallTargetRow = DelegatecallTargetRow;
1092
+ declare const schemas_DelegatecallTargetSchema: typeof DelegatecallTargetSchema;
1093
+ type schemas_DepositRow = DepositRow;
1094
+ declare const schemas_DepositSchema: typeof DepositSchema;
1095
+ type schemas_IndexerStateRow = IndexerStateRow;
1096
+ declare const schemas_IndexerStateSchema: typeof IndexerStateSchema;
1097
+ type schemas_RecoveryApprovalRow = RecoveryApprovalRow;
1098
+ declare const schemas_RecoveryApprovalSchema: typeof RecoveryApprovalSchema;
1099
+ type schemas_RecoveryConfigRow = RecoveryConfigRow;
1100
+ declare const schemas_RecoveryConfigSchema: typeof RecoveryConfigSchema;
1101
+ type schemas_RecoveryGuardianRow = RecoveryGuardianRow;
1102
+ declare const schemas_RecoveryGuardianSchema: typeof RecoveryGuardianSchema;
1103
+ type schemas_RecoveryRow = RecoveryRow;
1104
+ declare const schemas_RecoverySchema: typeof RecoverySchema;
1105
+ type schemas_SignedMessageRow = SignedMessageRow;
1106
+ declare const schemas_SignedMessageSchema: typeof SignedMessageSchema;
1107
+ type schemas_TokenRow = TokenRow;
1108
+ declare const schemas_TokenSchema: typeof TokenSchema;
1109
+ type schemas_TokenTransferRow = TokenTransferRow;
1110
+ declare const schemas_TokenTransferSchema: typeof TokenTransferSchema;
1111
+ type schemas_TransactionRow = TransactionRow;
1112
+ declare const schemas_TransactionSchema: typeof TransactionSchema;
1113
+ declare const schemas_TransactionStatusEnum: typeof TransactionStatusEnum;
1114
+ type schemas_WalletModuleRow = WalletModuleRow;
1115
+ declare const schemas_WalletModuleSchema: typeof WalletModuleSchema;
1116
+ type schemas_WalletOwnerRow = WalletOwnerRow;
1117
+ declare const schemas_WalletOwnerSchema: typeof WalletOwnerSchema;
1118
+ type schemas_WalletRow = WalletRow;
1119
+ declare const schemas_WalletSchema: typeof WalletSchema;
1120
+ declare const schemas_toNumber: typeof toNumber;
1121
+ declare namespace schemas {
1122
+ export { type schemas_ConfirmationRow as ConfirmationRow, schemas_ConfirmationSchema as ConfirmationSchema, type schemas_DelegatecallTargetRow as DelegatecallTargetRow, schemas_DelegatecallTargetSchema as DelegatecallTargetSchema, type schemas_DepositRow as DepositRow, schemas_DepositSchema as DepositSchema, type schemas_IndexerStateRow as IndexerStateRow, schemas_IndexerStateSchema as IndexerStateSchema, type schemas_RecoveryApprovalRow as RecoveryApprovalRow, schemas_RecoveryApprovalSchema as RecoveryApprovalSchema, type schemas_RecoveryConfigRow as RecoveryConfigRow, schemas_RecoveryConfigSchema as RecoveryConfigSchema, type schemas_RecoveryGuardianRow as RecoveryGuardianRow, schemas_RecoveryGuardianSchema as RecoveryGuardianSchema, type schemas_RecoveryRow as RecoveryRow, schemas_RecoverySchema as RecoverySchema, type schemas_SignedMessageRow as SignedMessageRow, schemas_SignedMessageSchema as SignedMessageSchema, type schemas_TokenRow as TokenRow, schemas_TokenSchema as TokenSchema, type schemas_TokenTransferRow as TokenTransferRow, schemas_TokenTransferSchema as TokenTransferSchema, type schemas_TransactionRow as TransactionRow, schemas_TransactionSchema as TransactionSchema, schemas_TransactionStatusEnum as TransactionStatusEnum, type schemas_WalletModuleRow as WalletModuleRow, schemas_WalletModuleSchema as WalletModuleSchema, type schemas_WalletOwnerRow as WalletOwnerRow, schemas_WalletOwnerSchema as WalletOwnerSchema, type schemas_WalletRow as WalletRow, schemas_WalletSchema as WalletSchema, schemas_toNumber as toNumber };
1123
+ }
1124
+
1125
+ declare class IndexerQueries {
1126
+ private readonly client;
1127
+ constructor(client: IndexerClient);
1128
+ wallet(address: Address): Promise<WalletRow | null>;
1129
+ /** Active owner addresses, lowercased as stored. */
1130
+ owners(address: Address): Promise<string[]>;
1131
+ vaultsForOwner(owner: Address, options?: Pagination): Promise<WalletRow[]>;
1132
+ vaultsForGuardian(guardian: Address, options?: Pagination): Promise<WalletRow[]>;
1133
+ pendingTransactions(vault: Address, options?: Pagination): Promise<TransactionRow[]>;
1134
+ transaction(vault: Address, txHash: string): Promise<TransactionRow | null>;
1135
+ transactionHistory(vault: Address, options?: Pagination & {
1136
+ status?: string[];
1137
+ }): Promise<Page<TransactionRow>>;
1138
+ /** All confirmations for one transaction, including revoked ones. */
1139
+ confirmations(vault: Address, txHash: string): Promise<ConfirmationRow[]>;
1140
+ /**
1141
+ * Active confirmations for many transactions in one round trip.
1142
+ *
1143
+ * "Active" here means not revoked. It does NOT mean the confirming address is
1144
+ * still an owner — the indexer does not deactivate confirmations when an owner is
1145
+ * removed, while the contract invalidates them via approval epochs. Callers must
1146
+ * intersect with the active owner set; `Vault` does this for you.
1147
+ */
1148
+ activeConfirmationsBatch(vault: Address, txHashes: string[]): Promise<Map<string, ConfirmationRow[]>>;
1149
+ modules(vault: Address): Promise<string[]>;
1150
+ delegatecallTargets(vault: Address): Promise<string[]>;
1151
+ deposits(vault: Address, options?: Pagination): Promise<Page<DepositRow>>;
1152
+ tokenTransfers(vault: Address, options?: Pagination): Promise<Page<TokenTransferRow>>;
1153
+ tokens(addresses: string[]): Promise<TokenRow[]>;
1154
+ signedMessages(vault: Address): Promise<SignedMessageRow[]>;
1155
+ recoveryConfig(vault: Address): Promise<{
1156
+ threshold: number;
1157
+ recoveryPeriod: number;
1158
+ guardians: string[];
1159
+ } | null>;
1160
+ pendingRecoveries(vault: Address): Promise<RecoveryRow[]>;
1161
+ recoveryHistory(vault: Address, options?: Pagination): Promise<RecoveryRow[]>;
1162
+ recoveryApprovals(vault: Address, recoveryHash: string): Promise<RecoveryApprovalRow[]>;
1163
+ }
1164
+
1165
+ interface RecoveryModuleContext {
1166
+ connection: Connection;
1167
+ queries: IndexerQueries | null;
1168
+ vaultAddress: Address;
1169
+ moduleAddress: Address | undefined;
1170
+ }
1171
+ /**
1172
+ * Guardian-based recovery for one vault.
1173
+ *
1174
+ * Recovery is an escape hatch that bypasses the owner multisig entirely: enough
1175
+ * guardians can replace the owner set after a delay. Every write here is therefore
1176
+ * gated on the module still being enabled on the vault, which is the vault owners'
1177
+ * only lever once guardians are configured.
1178
+ */
1179
+ declare class RecoveryModule {
1180
+ private readonly ctx;
1181
+ constructor(ctx: RecoveryModuleContext);
1182
+ /** The module address, or a typed error naming the variable to set. */
1183
+ get address(): Address;
1184
+ private contract;
1185
+ private get vault();
1186
+ /** Whether the module is currently enabled on the vault. */
1187
+ isEnabled(): Promise<boolean>;
1188
+ config(): Promise<RecoveryConfig>;
1189
+ isGuardian(address: Address): Promise<boolean>;
1190
+ /** Hashes of recoveries the module still tracks as pending. */
1191
+ pendingHashes(): Promise<Bytes32[]>;
1192
+ hasPending(): Promise<boolean>;
1193
+ /**
1194
+ * One recovery request, read from chain.
1195
+ *
1196
+ * Throws `NotFoundError` for a hash that was never initiated *or* was cancelled —
1197
+ * `cancelRecovery` deletes the struct, so the two are indistinguishable on chain.
1198
+ * Use {@link history} for cancelled recoveries.
1199
+ */
1200
+ get(recoveryHash: Bytes32): Promise<RecoveryRequest>;
1201
+ /** Every recovery the module still tracks as pending, hydrated. */
1202
+ pending(): Promise<RecoveryRequest[]>;
1203
+ /**
1204
+ * Full recovery history including executed, cancelled and expired requests.
1205
+ * Indexer-only — the chain retains no record of cancelled recoveries.
1206
+ */
1207
+ history(options?: {
1208
+ limit?: number;
1209
+ offset?: number;
1210
+ }): Promise<RecoveryRequest[]>;
1211
+ /** Guardians who have an active approval on a recovery. */
1212
+ approvals(recoveryHash: Bytes32): Promise<Address[]>;
1213
+ /** Hash the next `initiate` with these parameters would produce. */
1214
+ predictHash(newOwners: Address[], newThreshold: number): Promise<Bytes32>;
1215
+ /**
1216
+ * Initiate a recovery. Guardians only.
1217
+ *
1218
+ * Returns the recovery hash, read from the `RecoveryInitiated` event — the function's
1219
+ * return value is not recoverable from a receipt.
1220
+ */
1221
+ initiate(params: {
1222
+ newOwners: Address[];
1223
+ newThreshold: number;
1224
+ }): Promise<{
1225
+ recoveryHash: Bytes32;
1226
+ chainTxHash: Hex;
1227
+ }>;
1228
+ /** Approve a recovery. Guardians only. */
1229
+ approve(recoveryHash: Bytes32): Promise<{
1230
+ chainTxHash: Hex;
1231
+ }>;
1232
+ /** Withdraw a guardian approval before the recovery executes. */
1233
+ revokeApproval(recoveryHash: Bytes32): Promise<{
1234
+ chainTxHash: Hex;
1235
+ }>;
1236
+ /**
1237
+ * Execute a recovery, replacing the vault's owner set. Permissionless once the
1238
+ * guardian threshold is met and the recovery period has elapsed.
1239
+ */
1240
+ execute(recoveryHash: Bytes32): Promise<{
1241
+ chainTxHash: Hex;
1242
+ }>;
1243
+ /**
1244
+ * Cancel a recovery. Callable by any current vault owner — this is the owners'
1245
+ * defence against a hostile or mistaken guardian action.
1246
+ */
1247
+ cancel(recoveryHash: Bytes32): Promise<{
1248
+ chainTxHash: Hex;
1249
+ }>;
1250
+ /** Clean up a recovery past its deadline. Permissionless. */
1251
+ expire(recoveryHash: Bytes32): Promise<{
1252
+ chainTxHash: Hex;
1253
+ }>;
1254
+ /** What `caller` may do to a recovery right now, and when blocked actions unlock. */
1255
+ affordances(recoveryHash: Bytes32, caller?: Address): Promise<RecoveryAffordance[]>;
1256
+ private buildFromChain;
1257
+ private validateNewOwners;
1258
+ private assertLive;
1259
+ private assertEnabled;
1260
+ private send;
1261
+ private extractRecoveryHash;
1262
+ private toRevert;
1263
+ }
1264
+
1265
+ interface TokenBalance {
1266
+ token: Address;
1267
+ standard: 'ERC20' | 'ERC721' | 'ERC1155';
1268
+ symbol: string;
1269
+ name: string;
1270
+ decimals: number;
1271
+ /** ERC20: raw units. ERC721: number held. ERC1155: total across ids. */
1272
+ balance: bigint;
1273
+ /** ERC721 / ERC1155 only. */
1274
+ tokenIds?: string[];
1275
+ /** True when the balance came from an on-chain read rather than transfer replay. */
1276
+ verified: boolean;
1277
+ /**
1278
+ * Set when more candidate ids existed than `maxTokenIdChecks` allowed, so
1279
+ * `tokenIds` is a subset. The `balance` is still the authoritative on-chain count.
1280
+ */
1281
+ tokenIdsTruncated?: boolean;
1282
+ }
1283
+ interface VaultBalances {
1284
+ native: bigint;
1285
+ tokens: TokenBalance[];
1286
+ /**
1287
+ * Set when a scan limit was hit, so the result may be incomplete. Silent
1288
+ * truncation would read as "these are all the holdings" when they are not.
1289
+ */
1290
+ truncated?: {
1291
+ /** More transfer history existed than `transferScanLimit` scanned. */
1292
+ transfers?: boolean;
1293
+ /** More distinct tokens were discovered than `maxTokens` allowed. */
1294
+ tokens?: boolean;
1295
+ };
1296
+ }
1297
+ interface BalanceOptions {
1298
+ /**
1299
+ * Confirm each candidate balance with an on-chain read. Slower, but authoritative —
1300
+ * a token that moved through a path the indexer does not observe still reports
1301
+ * correctly. Default true.
1302
+ */
1303
+ verify?: boolean;
1304
+ /** Cap on tokens considered, newest activity first. Default 50. */
1305
+ maxTokens?: number;
1306
+ /**
1307
+ * How many recent transfer rows to scan when discovering tokens. Default 500.
1308
+ *
1309
+ * A vault with more transfers than this may not surface its oldest holdings, so
1310
+ * raise it for long-lived vaults. Reported back in {@link VaultBalances.truncated}.
1311
+ */
1312
+ transferScanLimit?: number;
1313
+ /** Cap on ERC721 ids ownership-checked per token. Default 100. */
1314
+ maxTokenIdChecks?: number;
1315
+ }
1316
+ /**
1317
+ * Aggregate a vault's holdings.
1318
+ *
1319
+ * Discovery is indexer-driven: `token_transfers` tells us which contracts a vault has
1320
+ * ever touched, which the chain cannot answer without scanning every log. Amounts are
1321
+ * then read on chain by default, because replaying transfers misses anything the
1322
+ * indexer did not observe (a token discovered after the transfer, a rebasing balance,
1323
+ * a direct mint). Set `verify: false` to skip the reads and accept replayed totals.
1324
+ */
1325
+ declare function loadBalances(connection: Connection, queries: IndexerQueries | null, vault: Address, options?: BalanceOptions): Promise<VaultBalances>;
1326
+
1327
+ /** Indexer tables a vault subscription can follow. */
1328
+ type WatchTopic = 'transactions' | 'confirmations' | 'owners' | 'modules' | 'deposits' | 'tokenTransfers' | 'recoveries' | 'signedMessages';
1329
+ interface WatchEvent {
1330
+ topic: WatchTopic;
1331
+ table: string;
1332
+ /** Postgres operation that produced the event. */
1333
+ type: 'INSERT' | 'UPDATE' | 'DELETE';
1334
+ /** Row after the change. Absent for DELETE. */
1335
+ row: Record<string, unknown> | null;
1336
+ /** Row before the change, when the table's replica identity provides it. */
1337
+ previous: Record<string, unknown> | null;
1338
+ }
1339
+ interface WatchOptions {
1340
+ topics?: WatchTopic[];
1341
+ /** Called on subscription lifecycle changes, so callers can surface reconnects. */
1342
+ onStatus?: (status: string, error?: Error) => void;
1343
+ }
1344
+ /** Handle returned by {@link watchVault}. Call `unsubscribe` to release the channel. */
1345
+ interface Subscription {
1346
+ unsubscribe(): Promise<void>;
1347
+ readonly topics: WatchTopic[];
1348
+ }
1349
+ /**
1350
+ * Follow indexer changes for one vault over Supabase Realtime.
1351
+ *
1352
+ * All topics share a single channel — one WebSocket per vault rather than one per
1353
+ * table, which matters because Realtime caps concurrent channels per client.
1354
+ *
1355
+ * Realtime delivers *indexer* writes, so events arrive only after the indexer has
1356
+ * processed the block. It is a change feed, not a chain subscription: use it to know
1357
+ * when to re-read, and re-read through `Vault` so approval counts stay correct.
1358
+ */
1359
+ declare function watchVault(client: IndexerClient, vault: Address, handler: (event: WatchEvent) => void, options?: WatchOptions): Subscription;
1360
+
1361
+ interface DecodeContext {
1362
+ /** The vault the transaction belongs to — used to detect self-calls. */
1363
+ vault: Address;
1364
+ to: Address;
1365
+ value: bigint;
1366
+ data: Hex;
1367
+ /** Known module addresses, for classifying `module_config` calls. */
1368
+ socialRecovery?: Address;
1369
+ multiSendCallOnly?: Address;
1370
+ }
1371
+ interface DecodeResult {
1372
+ kind: TransactionKind;
1373
+ decoded?: DecodedCall;
1374
+ summary: string;
1375
+ }
1376
+ /**
1377
+ * Classify and describe a proposed vault transaction from its calldata.
1378
+ *
1379
+ * Resolution order matters: self-calls are checked against the vault ABI first,
1380
+ * because several vault selectors would otherwise be ambiguous with token calls.
1381
+ */
1382
+ declare function decodeCall(ctx: DecodeContext): DecodeResult;
1383
+ interface DecodedBatchCall {
1384
+ operation: number;
1385
+ to: Address;
1386
+ value: bigint;
1387
+ data: Hex;
1388
+ }
1389
+ /**
1390
+ * Unpack a MultiSend payload into its constituent calls.
1391
+ * Layout per call: `operation(1) ‖ to(20) ‖ value(32) ‖ dataLength(32) ‖ data`.
1392
+ */
1393
+ declare function decodeMultiSendPayload(payload: Hex): DecodedBatchCall[];
1394
+
1395
+ declare const interfaces: {
1396
+ vault: Interface;
1397
+ recovery: Interface;
1398
+ multiSend: Interface;
1399
+ erc20: Interface;
1400
+ erc721: Interface;
1401
+ erc1155: Interface;
1402
+ };
1403
+ /** Max value the contracts accept for `minExecutionDelay` / `requestedDelay` (30 days). */
1404
+ declare const MAX_EXECUTION_DELAY: number;
1405
+ declare const MAX_OWNERS = 20;
1406
+ declare const MAX_MODULES = 50;
1407
+ /** Head of the Zodiac module linked list. */
1408
+ declare const SENTINEL_MODULES = "0x0000000000000000000000000000000000000001";
1409
+ declare const selfCall: {
1410
+ addOwner: (owner: Address) => Hex;
1411
+ removeOwner: (owner: Address) => Hex;
1412
+ changeThreshold: (threshold: number) => Hex;
1413
+ enableModule: (module: Address) => Hex;
1414
+ /**
1415
+ * Disable a module.
1416
+ *
1417
+ * `prevModule` is the predecessor in the Zodiac linked list and must be resolved
1418
+ * against the live list — a proposal built against a stale list can never execute.
1419
+ * Prefer `Vault.propose.disableModule`, which resolves it for you.
1420
+ */
1421
+ disableModule: (prevModule: Address, module: Address) => Hex;
1422
+ setMinExecutionDelay: (seconds: number) => Hex;
1423
+ addDelegatecallTarget: (target: Address) => Hex;
1424
+ removeDelegatecallTarget: (target: Address) => Hex;
1425
+ cancelByConsensus: (txHash: Hex) => Hex;
1426
+ /** Sign arbitrary bytes. For EIP-1271 hash pre-approval use {@link approveHashForEip1271}. */
1427
+ signMessage: (data: Hex) => Hex;
1428
+ unsignMessage: (data: Hex) => Hex;
1429
+ /**
1430
+ * Pre-approve a hash so `isValidSignature(hash, …)` returns the EIP-1271 magic value.
1431
+ *
1432
+ * `isValidSignature(h)` checks `signedMessages[getMessageHash(abi.encode(h))]`, while
1433
+ * `signMessage(data)` stores `getMessageHash(data)`. Because `abi.encode` of a static
1434
+ * `bytes32` is the identity on its 32 bytes, this produces calldata identical to
1435
+ * `signMessage(hash)` — the encoding step is a no-op for a well-formed hash.
1436
+ *
1437
+ * It is a separate helper because the *length* is what matters: signing an
1438
+ * arbitrary-length message `M` does NOT make `isValidSignature(keccak256(M))`
1439
+ * validate, since EIP-1271 hashes the 32-byte dataHash, not `M`. This entry point
1440
+ * enforces the 32-byte precondition and states the intent, so that mistake is
1441
+ * caught here rather than discovered when a counterparty's check silently fails.
1442
+ */
1443
+ approveHashForEip1271: (hash: Hex) => Hex;
1444
+ /** Revoke a hash previously approved via {@link approveHashForEip1271}. */
1445
+ revokeHashForEip1271: (hash: Hex) => Hex;
1446
+ };
1447
+ declare const token: {
1448
+ erc20Transfer: (to: Address, amount: bigint) => Hex;
1449
+ erc20Approve: (spender: Address, amount: bigint) => Hex;
1450
+ erc721Transfer: (from: Address, to: Address, tokenId: bigint) => Hex;
1451
+ erc1155Transfer: (from: Address, to: Address, id: bigint, amount: bigint, data?: Hex) => Hex;
1452
+ erc1155BatchTransfer: (from: Address, to: Address, ids: bigint[], amounts: bigint[], data?: Hex) => Hex;
1453
+ };
1454
+ declare const recoveryCall: {
1455
+ /** Encoded for a vault-proposed call to the module (not a self-call). */
1456
+ setupRecovery: (vaultAddress: Address, guardians: Address[], threshold: number, recoveryPeriodSeconds: number) => Hex;
1457
+ };
1458
+ interface BatchCall {
1459
+ to: Address;
1460
+ value?: bigint;
1461
+ data?: Hex;
1462
+ }
1463
+ /**
1464
+ * Pack calls into MultiSendCallOnly's transaction blob:
1465
+ * `operation(1) ‖ to(20) ‖ value(32) ‖ dataLength(32) ‖ data`, concatenated.
1466
+ *
1467
+ * `MultiSendCallOnly` rejects nested DelegateCall, so operation is always 0.
1468
+ */
1469
+ declare function encodeMultiSendPayload(calls: BatchCall[]): Hex;
1470
+ /** Full `multiSend(bytes)` calldata for a batch of calls. */
1471
+ declare function encodeMultiSend(calls: BatchCall[]): Hex;
1472
+ /**
1473
+ * Smallest expiration the contract will accept for a given delay.
1474
+ *
1475
+ * `_proposeTransaction` rejects `expiration <= block.timestamp + effectiveDelay` with
1476
+ * `ExpirationTooSoon`, so a proposal must leave at least one execution opportunity
1477
+ * after the timelock elapses. The margin absorbs the gap between building the
1478
+ * proposal and it being mined.
1479
+ */
1480
+ declare function minimumExpiration(effectiveDelaySeconds: number, marginSeconds?: number, at?: number): number;
1481
+
1482
+ interface VaultContext {
1483
+ connection: Connection;
1484
+ indexer: IndexerClient | null;
1485
+ queries: IndexerQueries | null;
1486
+ contracts: ContractAddresses;
1487
+ consistency: Consistency;
1488
+ maxIndexerLagBlocks: number;
1489
+ }
1490
+ /** A handle to one deployed vault. Cheap to construct; performs no I/O until used. */
1491
+ declare class Vault {
1492
+ readonly address: Address;
1493
+ /** Guardian-based recovery for this vault. */
1494
+ readonly recovery: RecoveryModule;
1495
+ private readonly ctx;
1496
+ constructor(address: Address, ctx: VaultContext);
1497
+ /**
1498
+ * Whether an indexer read is acceptable right now.
1499
+ *
1500
+ * `auto` uses the indexer only while it is live and within the configured lag
1501
+ * budget, so a stalled indexer silently degrades to chain reads instead of
1502
+ * serving stale state.
1503
+ */
1504
+ private useIndexer;
1505
+ private requireQueries;
1506
+ private contract;
1507
+ /** Owners, threshold, timelock floor, nonce and balance. Always read from chain. */
1508
+ info(): Promise<VaultInfo>;
1509
+ /** Active owners. Prefers the indexer, falls back to chain. */
1510
+ owners(): Promise<Address[]>;
1511
+ isOwner(address: Address): Promise<boolean>;
1512
+ threshold(): Promise<number>;
1513
+ balance(): Promise<bigint>;
1514
+ /**
1515
+ * Enabled modules, in linked-list order.
1516
+ *
1517
+ * Order matters: `disableModule` needs each module's predecessor, so this always
1518
+ * reads the chain rather than the indexer (which stores an unordered set).
1519
+ */
1520
+ modules(): Promise<Address[]>;
1521
+ isModuleEnabled(module: Address): Promise<boolean>;
1522
+ /** Addresses permitted as DelegateCall targets. Empty means DelegateCall is disabled. */
1523
+ delegatecallTargets(): Promise<Address[]>;
1524
+ isDelegatecallAllowed(target: Address): Promise<boolean>;
1525
+ /** Whether a hash has been pre-approved for EIP-1271 validation. */
1526
+ isValidSignature(hash: Bytes32): Promise<boolean>;
1527
+ /** The vault-transaction hash for a given call, at a given nonce. */
1528
+ transactionHash(to: Address, value: bigint, data: Hex, nonce?: number): Promise<Bytes32>;
1529
+ transaction(txHash: Bytes32): Promise<VaultTransaction>;
1530
+ pendingTransactions(options?: Pagination): Promise<VaultTransaction[]>;
1531
+ transactionHistory(options?: Pagination & {
1532
+ status?: string[];
1533
+ }): Promise<Page<VaultTransaction>>;
1534
+ private hydrateRows;
1535
+ private fromIndexer;
1536
+ /**
1537
+ * Build a `VaultTransaction` from an indexed row.
1538
+ *
1539
+ * Approval counting intersects the indexer's confirmations with the current owner
1540
+ * set. The indexer marks a confirmation inactive only on an explicit
1541
+ * `ApprovalRevoked`; it does not react to `OwnerRemoved`. On chain, removing an
1542
+ * owner bumps `ownerVersions` and invalidates every approval that address made, so
1543
+ * the stored `confirmation_count` over-reports after any removal. Intersecting here
1544
+ * reproduces the contract's `_countValidApprovals`.
1545
+ */
1546
+ private buildTransaction;
1547
+ /** Authoritative read: the struct plus a `hasApproved` probe per current owner. */
1548
+ private fromChain;
1549
+ /**
1550
+ * What `caller` may legally do to `txHash` right now, and when blocked actions
1551
+ * become available. Defaults to the connected signer's address.
1552
+ */
1553
+ affordances(txHash: Bytes32, caller?: Address): Promise<Affordance[]>;
1554
+ /**
1555
+ * Proposal builders.
1556
+ *
1557
+ * Every method here is async, including the ones whose work is purely local. Mixing
1558
+ * synchronous throws with rejected promises is a footgun: `propose.addOwner(bad)`
1559
+ * would throw before `.catch()` could attach. Validation failures are always
1560
+ * rejections.
1561
+ */
1562
+ readonly propose: {
1563
+ /** Propose an arbitrary call. Every other `propose.*` helper routes through this. */
1564
+ call: (params: {
1565
+ to: Address;
1566
+ value?: bigint;
1567
+ data?: Hex;
1568
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1569
+ /** Send native QUAI. */
1570
+ transfer: (params: {
1571
+ to: Address;
1572
+ amount: bigint;
1573
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1574
+ erc20Transfer: (params: {
1575
+ token: Address;
1576
+ to: Address;
1577
+ amount: bigint;
1578
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1579
+ erc721Transfer: (params: {
1580
+ token: Address;
1581
+ to: Address;
1582
+ tokenId: bigint;
1583
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1584
+ erc1155Transfer: (params: {
1585
+ token: Address;
1586
+ to: Address;
1587
+ id: bigint;
1588
+ amount: bigint;
1589
+ data?: Hex;
1590
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1591
+ /**
1592
+ * Batch several calls through MultiSendCallOnly.
1593
+ *
1594
+ * Requires MultiSendCallOnly to be on this vault's DelegateCall whitelist, which
1595
+ * is empty by default. The precondition is checked before signing so the failure
1596
+ * names the missing `addDelegatecallTarget` proposal instead of reverting.
1597
+ */
1598
+ batch: (calls: BatchCall[], options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1599
+ addOwner: (owner: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1600
+ removeOwner: (owner: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1601
+ changeThreshold: (threshold: number, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1602
+ setMinExecutionDelay: (seconds: number, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1603
+ enableModule: (module: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1604
+ /**
1605
+ * Disable a module, resolving its predecessor in the Zodiac linked list.
1606
+ *
1607
+ * The predecessor is baked into the proposal, so any module added or removed
1608
+ * before this executes invalidates it. `execute` re-checks and raises
1609
+ * `StaleProposalError` rather than letting it revert on chain.
1610
+ */
1611
+ disableModule: (module: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1612
+ addDelegatecallTarget: (target: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1613
+ removeDelegatecallTarget: (target: Address, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1614
+ /** Cancel a transaction that has already reached quorum. Needs full consensus. */
1615
+ cancelByConsensus: (txHash: Bytes32, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1616
+ /** Sign arbitrary bytes. For EIP-1271 hash approval use `approveHashForEip1271`. */
1617
+ signMessage: (message: Hex, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1618
+ unsignMessage: (message: Hex, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1619
+ /**
1620
+ * Make `isValidSignature(hash, …)` return the EIP-1271 magic value.
1621
+ *
1622
+ * Enforces the 32-byte precondition: signing an arbitrary-length message `M`
1623
+ * does not make `isValidSignature(keccak256(M))` validate, because EIP-1271
1624
+ * hashes the dataHash itself, not `M`.
1625
+ */
1626
+ approveHashForEip1271: (hash: Bytes32, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1627
+ revokeHashForEip1271: (hash: Bytes32, options?: ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1628
+ /** Configure social recovery. A call to the module, not a self-call. */
1629
+ setupRecovery: (params: {
1630
+ guardians: Address[];
1631
+ threshold: number;
1632
+ recoveryPeriodSeconds: number;
1633
+ } & ProposeOptions) => Promise<ProposeResult | DryRunResult>;
1634
+ };
1635
+ /** Predecessor of `module` in the Zodiac linked list, for `disableModule`. */
1636
+ previousModule(module: Address): Promise<Address>;
1637
+ private proposeSelfCall;
1638
+ private proposeCall;
1639
+ private minExecutionDelay;
1640
+ approve(txHash: Bytes32): Promise<{
1641
+ chainTxHash: Hex;
1642
+ }>;
1643
+ revokeApproval(txHash: Bytes32): Promise<{
1644
+ chainTxHash: Hex;
1645
+ }>;
1646
+ /**
1647
+ * Execute a transaction that has reached quorum.
1648
+ *
1649
+ * The returned {@link ExecuteResult} says what actually happened. A successful
1650
+ * receipt does not imply execution — see {@link classifyExecution}.
1651
+ */
1652
+ execute(txHash: Bytes32): Promise<ExecuteResult>;
1653
+ /** Approve and, if that meets quorum and the timelock allows, execute in one call. */
1654
+ approveAndExecute(txHash: Bytes32): Promise<ExecuteResult>;
1655
+ /** Cancel a transaction you proposed, before it ever reaches quorum. */
1656
+ cancel(txHash: Bytes32): Promise<{
1657
+ chainTxHash: Hex;
1658
+ }>;
1659
+ /** Close out an expired transaction. Permissionless — no ownership required. */
1660
+ expire(txHash: Bytes32): Promise<{
1661
+ chainTxHash: Hex;
1662
+ }>;
1663
+ private assertCallerIsOwner;
1664
+ /**
1665
+ * Re-validate on chain immediately before signing an execute.
1666
+ *
1667
+ * Reads always go through the chain here, never the indexer: indexed approval
1668
+ * counts can over-report after an owner removal, and a stale count must never gate
1669
+ * a signature.
1670
+ */
1671
+ private assertExecutable;
1672
+ /**
1673
+ * Native and token holdings.
1674
+ *
1675
+ * Token discovery needs the indexer — the chain cannot answer "which contracts has
1676
+ * this address touched" without scanning every log. Amounts are verified on chain
1677
+ * by default; pass `{ verify: false }` to accept replayed transfer totals instead.
1678
+ */
1679
+ balances(options?: BalanceOptions): Promise<VaultBalances>;
1680
+ /** Native QUAI received by the vault. Indexer-only. */
1681
+ deposits(options?: Pagination): Promise<Page<{
1682
+ amount: string;
1683
+ wallet_address: string;
1684
+ sender_address: string;
1685
+ deposited_at_block: string | number;
1686
+ deposited_at_tx: string;
1687
+ created_at?: string | null | undefined;
1688
+ }>>;
1689
+ /** ERC20/721/1155 transfers involving this vault. Indexer-only. */
1690
+ tokenTransfers(options?: Pagination): Promise<Page<{
1691
+ value: string;
1692
+ wallet_address: string;
1693
+ to_address: string;
1694
+ token_address: string;
1695
+ from_address: string;
1696
+ direction: "inflow" | "outflow";
1697
+ block_number: string | number;
1698
+ transaction_hash: string;
1699
+ log_index: number;
1700
+ token_id?: string | null | undefined;
1701
+ batch_index?: number | null | undefined;
1702
+ }>>;
1703
+ /** Message hashes currently signed by the vault for EIP-1271. Indexer-only. */
1704
+ signedMessages(): Promise<{
1705
+ wallet_address: string;
1706
+ is_active: boolean;
1707
+ msg_hash: string;
1708
+ signed_at_block: string | number;
1709
+ signed_at_tx: string;
1710
+ data?: string | null | undefined;
1711
+ unsigned_at_block?: string | number | null | undefined;
1712
+ unsigned_at_tx?: string | null | undefined;
1713
+ }[]>;
1714
+ /**
1715
+ * Follow indexer changes for this vault over Supabase Realtime.
1716
+ *
1717
+ * Events fire after the indexer processes a block, so treat them as a signal to
1718
+ * re-read rather than as state themselves — re-read through this handle so approval
1719
+ * counts stay intersected with the current owner set.
1720
+ *
1721
+ * ```ts
1722
+ * const sub = vault.watch((e) => console.log(e.topic, e.type));
1723
+ * // …later
1724
+ * await sub.unsubscribe();
1725
+ * ```
1726
+ */
1727
+ watch(handler: (event: WatchEvent) => void, options?: WatchOptions): Subscription;
1728
+ /**
1729
+ * Block until the indexer has caught up to `blockNumber` (or to the chain head
1730
+ * when omitted), so a subsequent indexed read reflects a write you just made.
1731
+ *
1732
+ * Returns `reached: false` on timeout rather than throwing — a lagging indexer is
1733
+ * an expected operating condition, and the caller may reasonably fall back to a
1734
+ * chain read. Requires an indexer; without one there is nothing to wait for.
1735
+ */
1736
+ waitForIndexer(blockNumber?: number, options?: {
1737
+ timeoutMs?: number;
1738
+ pollIntervalMs?: number;
1739
+ signal?: AbortSignal;
1740
+ }): Promise<{
1741
+ reached: boolean;
1742
+ lastIndexedBlock: number;
1743
+ }>;
1744
+ /**
1745
+ * Poll until a transaction is executable, then resolve with its current state.
1746
+ *
1747
+ * Lets a caller express "run this when it can run" instead of reimplementing the
1748
+ * timelock arithmetic. Resolves as soon as the status reaches `ready`; rejects if
1749
+ * the transaction reaches a terminal state first, or if the deadline passes.
1750
+ *
1751
+ * Reads go through the chain so the answer is authoritative — a `ready` verdict
1752
+ * here is immediately actionable.
1753
+ */
1754
+ waitForExecutable(txHash: Bytes32, options?: {
1755
+ /** Give up after this long. Default 1 hour. */
1756
+ timeoutMs?: number;
1757
+ /** Poll interval. Default 15s, roughly three Quai blocks. */
1758
+ pollIntervalMs?: number;
1759
+ signal?: AbortSignal;
1760
+ onPoll?: (tx: VaultTransaction) => void;
1761
+ }): Promise<VaultTransaction>;
1762
+ /** Compact human-readable summary of a transaction's state. */
1763
+ describe(txHash: Bytes32, caller?: Address): Promise<string>;
1764
+ }
1765
+
1766
+ /**
1767
+ * Entry point to the SDK.
1768
+ *
1769
+ * Construct with {@link connect}. Nothing here performs I/O until a method is
1770
+ * called, so building a client is cheap and safe at module scope.
1771
+ */
1772
+ declare class QuaiVaultClient {
1773
+ /**
1774
+ * Resolved configuration, with the private key stripped and the indexer key
1775
+ * masked — safe to log. The key itself is consumed once when building the signer.
1776
+ */
1777
+ readonly config: PublicConfig;
1778
+ readonly connection: Connection;
1779
+ readonly factory: Factory;
1780
+ readonly indexer: IndexerClient | null;
1781
+ readonly queries: IndexerQueries | null;
1782
+ constructor(options?: ClientOptions);
1783
+ get network(): NetworkConfig;
1784
+ get provider(): Provider;
1785
+ get signer(): Signer | null;
1786
+ /** The connected address, or null when read-only. */
1787
+ address(): Promise<Address | null>;
1788
+ /** A handle to one vault. No I/O. */
1789
+ vault(address: Address): Vault;
1790
+ private vaultContext;
1791
+ /** Vault discovery. Requires the indexer — there is no on-chain reverse index. */
1792
+ readonly vaults: {
1793
+ /**
1794
+ * Vaults where `owner` is an active owner.
1795
+ *
1796
+ * Indexer-only: `factory.getWalletsByCreator` was removed from the contracts for
1797
+ * gas reasons, and the factory registry is not indexed by owner.
1798
+ */
1799
+ forOwner: (owner: Address, options?: Pagination) => Promise<Address[]>;
1800
+ /** Vaults where `guardian` is an active social-recovery guardian. */
1801
+ forGuardian: (guardian: Address, options?: Pagination) => Promise<Address[]>;
1802
+ /** Whether an address is a vault this factory deployed or registered. */
1803
+ exists: (address: Address) => Promise<boolean>;
1804
+ get: (address: Address) => Vault;
1805
+ };
1806
+ private requireQueries;
1807
+ /** Indexer liveness and lag. Reports unavailable when no indexer is configured. */
1808
+ indexerHealth(): Promise<IndexerHealth>;
1809
+ }
1810
+ /**
1811
+ * Create a client.
1812
+ *
1813
+ * Configuration resolves in order of decreasing precedence: explicit options,
1814
+ * environment variables, then the network preset. With nothing supplied it targets
1815
+ * mainnet over the public RPC in read-only mode.
1816
+ *
1817
+ * ```ts
1818
+ * // read-only, from env (QUAIVAULT_NETWORK, QUAIVAULT_INDEXER_ANON_KEY, …)
1819
+ * const qv = connect();
1820
+ *
1821
+ * // read-write against testnet
1822
+ * const qv = connect({ network: 'testnet', privateKey: process.env.KEY });
1823
+ * ```
1824
+ */
1825
+ declare function connect(options?: ClientOptions): QuaiVaultClient;
1826
+ /** Namespace form, for callers who prefer `QuaiVault.connect(…)`. */
1827
+ declare const QuaiVault: {
1828
+ connect: typeof connect;
1829
+ };
1830
+
1831
+ /**
1832
+ * Raw `Transaction` struct as returned by `QuaiVault.transactions(bytes32)`.
1833
+ * Field names match the Solidity struct.
1834
+ */
1835
+ interface RawTransactionStruct {
1836
+ to: string;
1837
+ timestamp: bigint;
1838
+ expiration: bigint;
1839
+ proposer: string;
1840
+ executed: boolean;
1841
+ cancelled: boolean;
1842
+ approvedAt: bigint;
1843
+ executionDelay: bigint;
1844
+ value: bigint;
1845
+ data: string;
1846
+ }
1847
+ /**
1848
+ * Typed facade over the generated `Contract`.
1849
+ *
1850
+ * `quais` types its dynamic method accessor as possibly-undefined and cannot
1851
+ * disambiguate overloads by arity, so every call here goes through `getFunction`
1852
+ * with an explicit signature. Keeping that in one place means callers get real
1853
+ * types and overload selection is stated once, next to the ABI it depends on.
1854
+ */
1855
+ declare class VaultContract {
1856
+ readonly contract: Contract;
1857
+ private readonly retry;
1858
+ constructor(contract: Contract, retry?: RetryOptions);
1859
+ private fn;
1860
+ /**
1861
+ * Reads retry transient RPC failures (see {@link withRetry}); writes never do,
1862
+ * because a resubmit that looks like a timeout may already be in the mempool.
1863
+ */
1864
+ private read;
1865
+ get interface(): quais.Interface;
1866
+ getOwners(): Promise<string[]>;
1867
+ isOwner(address: Address): Promise<boolean>;
1868
+ threshold(): Promise<bigint>;
1869
+ nonce(): Promise<bigint>;
1870
+ minExecutionDelay(): Promise<bigint>;
1871
+ moduleCount(): Promise<bigint>;
1872
+ getModules(): Promise<string[]>;
1873
+ isModuleEnabled(module: Address): Promise<boolean>;
1874
+ delegatecallAllowed(target: Address): Promise<boolean>;
1875
+ transactions(txHash: Bytes32): Promise<RawTransactionStruct>;
1876
+ /**
1877
+ * Whether an owner's approval currently counts toward the threshold.
1878
+ * Accounts for approval-epoch invalidation from owner removal.
1879
+ */
1880
+ hasApproved(txHash: Bytes32, owner: Address): Promise<boolean>;
1881
+ /** True when the transaction was closed by `expireTransaction`, not cancelled. */
1882
+ expiredTxs(txHash: Bytes32): Promise<boolean>;
1883
+ getTransactionHash(to: Address, value: bigint, data: Hex, nonce: number | bigint): Promise<Bytes32>;
1884
+ isValidSignature(hash: Bytes32, signature: Hex): Promise<string>;
1885
+ /**
1886
+ * Overload with expiration and delay. The 3-argument overload is used when
1887
+ * neither is supplied, so a caller that wants no expiry never has to pass 0.
1888
+ */
1889
+ proposeTransactionFull(to: Address, value: bigint, data: Hex, expiration: number, executionDelay: number): Promise<ContractTransactionResponse>;
1890
+ proposeTransactionSimple(to: Address, value: bigint, data: Hex): Promise<ContractTransactionResponse>;
1891
+ approveTransaction(txHash: Bytes32): Promise<ContractTransactionResponse>;
1892
+ approveAndExecute(txHash: Bytes32): Promise<ContractTransactionResponse>;
1893
+ executeTransaction(txHash: Bytes32): Promise<ContractTransactionResponse>;
1894
+ revokeApproval(txHash: Bytes32): Promise<ContractTransactionResponse>;
1895
+ cancelTransaction(txHash: Bytes32): Promise<ContractTransactionResponse>;
1896
+ expireTransaction(txHash: Bytes32): Promise<ContractTransactionResponse>;
1897
+ encode(signature: string, args: unknown[]): Hex;
1898
+ }
1899
+ /** Minimal shape of a sent transaction — enough to await a receipt. */
1900
+ interface ContractTransactionResponse {
1901
+ hash: string;
1902
+ wait(confirmations?: number): Promise<unknown>;
1903
+ }
1904
+ /** Typed facade over `QuaiVaultFactory`. */
1905
+ declare class FactoryContract {
1906
+ readonly contract: Contract;
1907
+ private readonly retry;
1908
+ constructor(contract: Contract, retry?: RetryOptions);
1909
+ private fn;
1910
+ /**
1911
+ * Reads retry transient RPC failures (see {@link withRetry}); writes never do,
1912
+ * because a resubmit that looks like a timeout may already be in the mempool.
1913
+ */
1914
+ private read;
1915
+ get interface(): quais.Interface;
1916
+ implementation(): Promise<string>;
1917
+ isWallet(address: Address): Promise<boolean>;
1918
+ getWalletCount(): Promise<bigint>;
1919
+ deployedWallets(index: number | bigint): Promise<string>;
1920
+ predictWalletAddress(deployer: Address, salt: Bytes32, owners: Address[], threshold: number, minExecutionDelay: number, initialModules: Address[], initialDelegatecallTargets: Address[]): Promise<string>;
1921
+ /** Full 6-argument overload. The SDK always uses it so mining stays consistent. */
1922
+ createWallet(owners: Address[], threshold: number, salt: Bytes32, minExecutionDelay: number, initialModules: Address[], initialDelegatecallTargets: Address[]): Promise<ContractTransactionResponse>;
1923
+ registerWallet(wallet: Address): Promise<ContractTransactionResponse>;
1924
+ }
1925
+
1926
+ /** Raw `RecoveryConfig` struct from `getRecoveryConfig(address)`. */
1927
+ interface RawRecoveryConfig {
1928
+ guardians: string[];
1929
+ threshold: bigint;
1930
+ recoveryPeriod: bigint;
1931
+ }
1932
+ /** Raw `Recovery` struct from `getRecovery(address,bytes32)`. */
1933
+ interface RawRecovery {
1934
+ newOwners: string[];
1935
+ newThreshold: bigint;
1936
+ approvalCount: bigint;
1937
+ executionTime: bigint;
1938
+ expiration: bigint;
1939
+ requiredThreshold: bigint;
1940
+ executed: boolean;
1941
+ }
1942
+ /**
1943
+ * Typed facade over `SocialRecoveryModule`.
1944
+ *
1945
+ * Every function is wallet-scoped: one module deployment serves every vault, so the
1946
+ * vault address is always the first argument.
1947
+ */
1948
+ declare class RecoveryContract {
1949
+ readonly contract: Contract;
1950
+ private readonly retry;
1951
+ constructor(contract: Contract, retry?: RetryOptions);
1952
+ private fn;
1953
+ /**
1954
+ * Reads retry transient RPC failures (see {@link withRetry}); writes never do,
1955
+ * because a resubmit that looks like a timeout may already be in the mempool.
1956
+ */
1957
+ private read;
1958
+ get interface(): quais.Interface;
1959
+ getRecoveryConfig(vault: Address): Promise<RawRecoveryConfig>;
1960
+ getRecovery(vault: Address, recoveryHash: Bytes32): Promise<RawRecovery>;
1961
+ isGuardian(vault: Address, guardian: Address): Promise<boolean>;
1962
+ getPendingRecoveryHashes(vault: Address): Promise<string[]>;
1963
+ hasPendingRecoveries(vault: Address): Promise<boolean>;
1964
+ recoveryApprovals(vault: Address, recoveryHash: Bytes32, guardian: Address): Promise<boolean>;
1965
+ recoveryNonces(vault: Address): Promise<bigint>;
1966
+ maxGuardians(): Promise<bigint>;
1967
+ /** Hash for an explicit nonce. Use {@link predictNextRecoveryHash} for the next one. */
1968
+ getRecoveryHash(vault: Address, newOwners: Address[], newThreshold: number, nonce: bigint | number): Promise<Bytes32>;
1969
+ /** Hash the next `initiateRecovery` would produce, at the current nonce. */
1970
+ predictNextRecoveryHash(vault: Address, newOwners: Address[], newThreshold: number): Promise<Bytes32>;
1971
+ initiateRecovery(vault: Address, newOwners: Address[], newThreshold: number): Promise<ContractTransactionResponse>;
1972
+ approveRecovery(vault: Address, recoveryHash: Bytes32): Promise<ContractTransactionResponse>;
1973
+ revokeRecoveryApproval(vault: Address, recoveryHash: Bytes32): Promise<ContractTransactionResponse>;
1974
+ executeRecovery(vault: Address, recoveryHash: Bytes32): Promise<ContractTransactionResponse>;
1975
+ cancelRecovery(vault: Address, recoveryHash: Bytes32): Promise<ContractTransactionResponse>;
1976
+ expireRecovery(vault: Address, recoveryHash: Bytes32): Promise<ContractTransactionResponse>;
1977
+ }
1978
+
1979
+ interface ReceiptLike {
1980
+ hash?: string;
1981
+ transactionHash?: string;
1982
+ blockNumber?: number | bigint | null;
1983
+ gasUsed?: bigint | number | null;
1984
+ status?: number | null;
1985
+ logs: ReadonlyArray<{
1986
+ topics: ReadonlyArray<string>;
1987
+ data: string;
1988
+ address?: string;
1989
+ }>;
1990
+ }
1991
+ /**
1992
+ * Classify what actually happened to a vault transaction, from the receipt of the
1993
+ * Quai transaction that carried `execute` / `approveAndExecute`.
1994
+ *
1995
+ * This exists because `receipt.status === 1` does NOT mean the vault transaction ran.
1996
+ * Three cases produce a successful receipt with no execution:
1997
+ *
1998
+ * 1. **Lazy timelock clock** — `executeTransaction` on a timelocked transaction whose
1999
+ * `approvedAt` was never set (e.g. the threshold was lowered after owners approved)
2000
+ * sets `approvedAt`, emits `ThresholdReached`, and returns without executing. It
2001
+ * deliberately does not revert, because a revert would roll back the clock start.
2002
+ * 2. **`approveAndExecute` returned false** — the threshold is unmet, or the timelock
2003
+ * has not elapsed. A boolean return value is invisible in a receipt.
2004
+ * 3. **External call failure (Option B)** — the vault marks the transaction `executed`
2005
+ * permanently and emits `TransactionFailed` instead of reverting. The vault
2006
+ * transaction is dead; the receipt says success.
2007
+ */
2008
+ declare function classifyExecution(receipt: ReceiptLike, vault: string, txHash: Bytes32): ExecuteResult;
2009
+ /**
2010
+ * Pull the vault-transaction hash out of a `proposeTransaction` receipt.
2011
+ * The `TransactionProposed` event is the only reliable source — `proposeTransaction`
2012
+ * returns the hash, but return values are not available from a receipt.
2013
+ */
2014
+ declare function extractProposedTxHash(receipt: ReceiptLike, vault: string): Bytes32 | null;
2015
+
2016
+ interface RawTransactionState {
2017
+ executed: boolean;
2018
+ cancelled: boolean;
2019
+ /** From `expiredTxs[txHash]` — distinguishes expiry from voluntary cancellation. */
2020
+ isExpired?: boolean;
2021
+ expiration: number;
2022
+ executionDelay: number;
2023
+ approvedAt: number;
2024
+ approvalCount: number;
2025
+ threshold: number;
2026
+ /** Set when a TransactionFailed event was indexed. */
2027
+ failed?: boolean;
2028
+ }
2029
+ declare function nowSeconds(): number;
2030
+ /**
2031
+ * `approvedAt + executionDelay`, or 0 when the timelock clock has not started.
2032
+ *
2033
+ * `approvedAt` is written once, on the first threshold crossing, and is never
2034
+ * cleared — revoking approvals below the threshold does not reset the clock.
2035
+ */
2036
+ declare function executableAfterOf(approvedAt: number, executionDelay: number): number;
2037
+ /**
2038
+ * Derive the lifecycle status from raw contract state.
2039
+ *
2040
+ * `ready` and `timelocked` refine the contract's "pending": both mean not yet
2041
+ * executed with quorum reached, differing only on whether the delay has elapsed.
2042
+ * The contract has no such distinction — it is computed here so callers do not
2043
+ * have to reimplement the timelock arithmetic.
2044
+ */
2045
+ declare function deriveStatus(state: RawTransactionState, at?: number): TransactionStatus;
2046
+ /** Whether the status admits no further state transitions. */
2047
+ declare function isTerminal(status: TransactionStatus): boolean;
2048
+ interface RawRecoveryState {
2049
+ executed: boolean;
2050
+ approvalCount: number;
2051
+ /** Threshold captured at initiation, not the current config. */
2052
+ requiredThreshold: number;
2053
+ executionTime: number;
2054
+ expiration: number;
2055
+ }
2056
+ /**
2057
+ * Derive a recovery's lifecycle status from raw module state.
2058
+ *
2059
+ * Expiry is computed from `expiration` rather than taken from a stored flag, because
2060
+ * nothing transitions a recovery to expired on its own — `expireRecovery` is a
2061
+ * permissionless cleanup call somebody has to make. The indexer only writes an
2062
+ * expired status when it sees `RecoveryExpiredEvent`, so an un-cleaned recovery reads
2063
+ * as `pending` there long after it died. Computing it here keeps the SDK honest
2064
+ * regardless of which side the data came from.
2065
+ *
2066
+ * `cancelled` is never returned: `cancelRecovery` deletes the on-chain struct, so it
2067
+ * is only observable through indexed history.
2068
+ */
2069
+ declare function deriveRecoveryStatus(state: RawRecoveryState, at?: number): Exclude<RecoveryStatus, 'cancelled'>;
2070
+
2071
+ interface AffordanceContext {
2072
+ tx: VaultTransaction;
2073
+ caller: Address;
2074
+ isOwner: boolean;
2075
+ /** Whether `caller` currently has a threshold-counting approval. */
2076
+ hasApproved: boolean;
2077
+ at?: number;
2078
+ }
2079
+ /**
2080
+ * Enumerate which actions `caller` may legally take on `tx` right now, and when a
2081
+ * blocked action becomes available.
2082
+ *
2083
+ * Every rule here mirrors an on-chain check, so callers can plan instead of
2084
+ * discovering constraints through reverted transactions:
2085
+ *
2086
+ * - `onlyOwner` on approve / revoke / execute / cancel
2087
+ * - epoch-based approvals (`_approvalValid`) for already-approved / not-approved
2088
+ * - `approvedAt != 0` permanently locking proposer-cancel, even after revocations
2089
+ * - the timelock on external calls (self-calls bypass it)
2090
+ * - expiration, and the permissionless `expireTransaction` cleanup path
2091
+ */
2092
+ declare function computeAffordances(ctx: AffordanceContext): Affordance[];
2093
+ /** The subset of actions currently allowed. */
2094
+ declare function allowedActions(affordances: Affordance[]): Affordance[];
2095
+
2096
+ /**
2097
+ * Encode the `initialize` call the factory delegatecalls from the proxy constructor.
2098
+ *
2099
+ * This MUST match `QuaiVaultFactory._createWallet` exactly — including the module and
2100
+ * delegatecall-target arrays. Encoding `[]` for those while deploying with a non-empty
2101
+ * array yields a different bytecode hash, so the mined address will not be the address
2102
+ * the factory actually deploys to.
2103
+ */
2104
+ declare function encodeInitData(params: {
2105
+ owners: Address[];
2106
+ threshold: number;
2107
+ minExecutionDelay?: number;
2108
+ initialModules?: Address[];
2109
+ initialDelegatecallTargets?: Address[];
2110
+ }): string;
2111
+ /**
2112
+ * keccak256 of the full proxy creation bytecode (creation code + ABI-encoded
2113
+ * constructor args), which is the third input to CREATE2.
2114
+ *
2115
+ * Constant for a given set of create params, so it is computed once and reused
2116
+ * across every mining attempt.
2117
+ */
2118
+ declare function computeBytecodeHash(implementation: Address, initData: string): Bytes32;
2119
+ /**
2120
+ * The factory namespaces user salts by caller:
2121
+ * `fullSalt = keccak256(abi.encodePacked(msg.sender, userSalt))`.
2122
+ * Two deployers using the same user salt therefore get different addresses.
2123
+ */
2124
+ declare function computeFullSalt(deployer: Address, userSalt: Bytes32): Bytes32;
2125
+ /**
2126
+ * Reproduce `QuaiVaultFactory.predictWalletAddress` off-chain — no RPC needed.
2127
+ */
2128
+ declare function predictVaultAddress(args: {
2129
+ factory: Address;
2130
+ implementation: Address;
2131
+ deployer: Address;
2132
+ salt: Bytes32;
2133
+ params: CreateVaultParams;
2134
+ }): Address;
2135
+ /**
2136
+ * The shard prefix of a Quai address: `0x` plus the leading byte.
2137
+ *
2138
+ * Quai addresses are shard-scoped. A vault deployed outside the deployer's shard is
2139
+ * unreachable from it, which is why deployment requires mining a salt rather than
2140
+ * picking one at random.
2141
+ */
2142
+ declare function shardPrefixOf(address: Address): string;
2143
+
2144
+ /**
2145
+ * Address validation for Quai's dual-ledger, sharded address space.
2146
+ *
2147
+ * Two independent properties are encoded in a Quai address:
2148
+ *
2149
+ * - **Zone** — the first byte (`0x00`–`0x02`, `0x10`–`0x12`, `0x20`–`0x22`). An address
2150
+ * outside those ranges belongs to no shard and cannot hold state.
2151
+ * - **Ledger** — the 9th bit, i.e. the high bit of the *second* byte. Set means Qi (the
2152
+ * UTXO ledger); clear means Quai (the EVM account ledger).
2153
+ *
2154
+ * They are genuinely orthogonal: `0x008111…` sits in zone `0x00` yet is a Qi address, so
2155
+ * a zone check alone is not enough.
2156
+ *
2157
+ * **Qi addresses cannot interact with smart contracts.** A Qi owner could never sign a
2158
+ * vault transaction, a Qi guardian could never approve a recovery, and native value sent
2159
+ * to one leaves the Quai ledger. The contracts do not check for this — `_addOwner` only
2160
+ * rejects the zero address, the vault itself and the module sentinel — so admitting one
2161
+ * permanently reduces the number of addresses able to reach the threshold, and enough of
2162
+ * them bricks the vault. The SDK therefore rejects Qi addresses wherever an address is
2163
+ * committed to a role or receives value.
2164
+ */
2165
+ type AddressLedger = 'quai' | 'qi';
2166
+ interface AddressCheck {
2167
+ valid: boolean;
2168
+ checksummed?: Address;
2169
+ /** Zone prefix, e.g. `0x00`. Absent when the address belongs to no shard. */
2170
+ zone?: string;
2171
+ ledger?: AddressLedger;
2172
+ reason?: string;
2173
+ }
2174
+ /** Inspect an address without throwing. */
2175
+ declare function inspectAddress(value: unknown): AddressCheck;
2176
+ /** Whether an address is shard-valid and on the Quai ledger. */
2177
+ declare function isUsableQuaiAddress(value: unknown): boolean;
2178
+ /**
2179
+ * Assert an address can participate in Quai smart-contract calls, returning it
2180
+ * checksummed.
2181
+ *
2182
+ * Use for any address being committed to a role (owner, guardian, module, delegatecall
2183
+ * target) or receiving value. Do **not** use it for addresses being *removed* — a Qi
2184
+ * address that predates this check must still be removable — nor for read-only lookups,
2185
+ * where rejecting the query is less useful than answering it.
2186
+ */
2187
+ declare function assertQuaiAddress(value: unknown, label?: string): Address;
2188
+ /** Assert every address in a list, reporting the offending index. */
2189
+ declare function assertQuaiAddresses(values: readonly unknown[], label: string): Address[];
2190
+
2191
+ /** Machine-readable error codes. Stable across releases. */
2192
+ type QuaiVaultErrorCode = 'CONFIG' | 'NOT_CONNECTED' | 'NO_SIGNER' | 'NO_INDEXER' | 'INDEXER_QUERY' | 'INDEXER_STALE' | 'VALIDATION' | 'NOT_FOUND' | 'PRECONDITION' | 'REVERT' | 'SALT_MINING' | 'STALE_PROPOSAL' | 'UNSUPPORTED';
2193
+ declare class QuaiVaultError extends Error {
2194
+ readonly code: QuaiVaultErrorCode;
2195
+ /** What the caller can do about it, in plain language. */
2196
+ readonly remediation?: string;
2197
+ /** Unix seconds after which retrying could succeed. */
2198
+ readonly retryableAt?: number;
2199
+ readonly cause?: unknown;
2200
+ constructor(code: QuaiVaultErrorCode, message: string, options?: {
2201
+ remediation?: string;
2202
+ retryableAt?: number;
2203
+ cause?: unknown;
2204
+ });
2205
+ /** Structured form, for logs and machine consumers. */
2206
+ toJSON(): Record<string, unknown>;
2207
+ }
2208
+ declare class ConfigError extends QuaiVaultError {
2209
+ constructor(message: string, remediation?: string);
2210
+ }
2211
+ declare class NoSignerError extends QuaiVaultError {
2212
+ constructor(operation: string);
2213
+ }
2214
+ declare class NoIndexerError extends QuaiVaultError {
2215
+ constructor(operation: string);
2216
+ }
2217
+ declare class IndexerQueryError extends QuaiVaultError {
2218
+ constructor(message: string, cause?: unknown);
2219
+ }
2220
+ declare class ValidationError extends QuaiVaultError {
2221
+ constructor(message: string, remediation?: string);
2222
+ }
2223
+ declare class NotFoundError extends QuaiVaultError {
2224
+ constructor(message: string);
2225
+ }
2226
+ /** A precondition the contract enforces would fail — checked before signing. */
2227
+ declare class PreconditionError extends QuaiVaultError {
2228
+ constructor(message: string, options?: {
2229
+ remediation?: string;
2230
+ retryableAt?: number;
2231
+ });
2232
+ }
2233
+ /** A contract call reverted. Carries the decoded custom error when resolvable. */
2234
+ declare class RevertError extends QuaiVaultError {
2235
+ readonly revert?: DecodedRevert;
2236
+ constructor(message: string, revert?: DecodedRevert, options?: {
2237
+ remediation?: string;
2238
+ cause?: unknown;
2239
+ });
2240
+ toJSON(): Record<string, unknown>;
2241
+ }
2242
+ declare class SaltMiningError extends QuaiVaultError {
2243
+ constructor(message: string, remediation?: string);
2244
+ }
2245
+ /**
2246
+ * A `disableModule` proposal whose baked-in `prevModule` no longer matches the
2247
+ * live linked list. The proposal can never execute and must be replaced.
2248
+ */
2249
+ declare class StaleProposalError extends QuaiVaultError {
2250
+ constructor(message: string);
2251
+ }
2252
+
2253
+ /**
2254
+ * Decode revert data against every QuaiVault ABI, plus the two Solidity builtins.
2255
+ * Returns `undefined` for empty data or an unrecognised selector.
2256
+ */
2257
+ declare function decodeRevert(data: unknown): DecodedRevert | undefined;
2258
+ /** Remediation text for a custom error name, if the SDK knows one. */
2259
+ declare function remediationFor(errorName: string): string | undefined;
2260
+ /**
2261
+ * Pull revert data out of the many shapes providers use, then decode it.
2262
+ * Handles quais/ethers `CALL_EXCEPTION` errors, raw RPC error bodies and nested causes.
2263
+ */
2264
+ declare function decodeRevertFromError(error: unknown): DecodedRevert | undefined;
2265
+ /** All known custom error selectors. Exposed for tooling and tests. */
2266
+ declare function knownErrorSelectors(): Array<{
2267
+ selector: Hex;
2268
+ signature: string;
2269
+ }>;
2270
+
2271
+ export { type Address, type AddressCheck, type AddressLedger, type Affordance, type AffordanceBlocker, type AffordanceContext, type ApprovalRecord, type BalanceOptions, type BatchCall, type Bytes32, type ClientOptions, ConfigError, Connection, type Consistency, type ContractAddresses, type CreateProgress, type CreateVaultParams, type CreateVaultResult, type DecodeContext, type DecodeResult, type DecodedBatchCall, type DecodedCall, type DecodedRevert, type DryRunResult, ENV_VARS, type ExecuteOutcome, type ExecuteResult, Factory, type FactoryContext, FactoryContract, type Hex, IndexerClient, type IndexerConfig, type IndexerHealth, IndexerQueries, IndexerQueryError, MAX_EXECUTION_DELAY, MAX_MODULES, MAX_OWNERS, type MineSaltOptions, type MinedSalt, type MiningStrategy, type NetworkConfig, type NetworkName, NoIndexerError, NoSignerError, NotFoundError, Operation, type Page, type Pagination, PreconditionError, type ProposeOptions, type ProposeResult, QuaiVault, QuaiVaultClient, QuaiVaultError, type QuaiVaultErrorCode, type RawRecovery, type RawRecoveryConfig, type RawRecoveryState, type RawTransactionState, type RawTransactionStruct, type ReceiptLike, type RecoveryAction, type RecoveryAffordance, type RecoveryConfig, RecoveryContract, RecoveryModule, type RecoveryModuleContext, type RecoveryRequest, type RecoveryStatus, type ResolvedConfig, type RetryOptions, RevertError, SENTINEL_MODULES, SaltMiningError, StaleProposalError, type Subscription, type TokenBalance, type TransactionKind, type TransactionStatus, ValidationError, Vault, type VaultAction, type VaultBalances, type VaultContext, VaultContract, type VaultInfo, type VaultTransaction, type WatchEvent, type WatchOptions, type WatchTopic, allowedActions, assertQuaiAddress, assertQuaiAddresses, classifyExecution, computeAffordances, computeBytecodeHash, computeFullSalt, connect, decodeCall, decodeMultiSendPayload, decodeRevert, decodeRevertFromError, defaultStrategy, deriveRecoveryStatus, deriveStatus, encodeInitData, encodeMultiSend, encodeMultiSendPayload, executableAfterOf, extractProposedTxHash, schemas as indexerSchemas, inspectAddress, interfaces, isNetworkName, isTerminal, isTransient, isUsableQuaiAddress, knownErrorSelectors, loadBalances, mainnet, mineSalt, minimumExpiration, networks, nowSeconds, predictVaultAddress, recoveryCall, remediationFor, resolveConfig, selfCall, shardPrefixOf, syncStrategy, testnet, token as tokenCalls, watchVault, withRetry, workerThreadsStrategy };