@quaivault/sdk 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/dist/index.cjs +107 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -4
- package/dist/index.d.ts +81 -4
- package/dist/index.js +107 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -219,6 +219,36 @@ function normalizeTxHash(value, label = "transaction hash") {
|
|
|
219
219
|
// src/config/resolve.ts
|
|
220
220
|
import { isAddress } from "quais";
|
|
221
221
|
|
|
222
|
+
// src/lifecycle/status.ts
|
|
223
|
+
function nowSeconds() {
|
|
224
|
+
return Math.floor(Date.now() / 1e3);
|
|
225
|
+
}
|
|
226
|
+
function executableAfterOf(approvedAt, executionDelay) {
|
|
227
|
+
return approvedAt > 0 ? approvedAt + executionDelay : 0;
|
|
228
|
+
}
|
|
229
|
+
function deriveStatus(state, at = nowSeconds()) {
|
|
230
|
+
if (state.failed) return "failed";
|
|
231
|
+
if (state.executed) return "executed";
|
|
232
|
+
if (state.cancelled) return state.isExpired ? "expired" : "cancelled";
|
|
233
|
+
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
234
|
+
if (state.approvalCount < state.threshold) return "pending";
|
|
235
|
+
const executableAfter = executableAfterOf(state.approvedAt, state.executionDelay);
|
|
236
|
+
if (state.executionDelay > 0 && (state.approvedAt === 0 || at < executableAfter)) {
|
|
237
|
+
return "timelocked";
|
|
238
|
+
}
|
|
239
|
+
return "ready";
|
|
240
|
+
}
|
|
241
|
+
function isTerminal(status) {
|
|
242
|
+
return status === "executed" || status === "failed" || status === "cancelled" || status === "expired";
|
|
243
|
+
}
|
|
244
|
+
function deriveRecoveryStatus(state, at = nowSeconds()) {
|
|
245
|
+
if (state.executed) return "executed";
|
|
246
|
+
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
247
|
+
if (state.approvalCount < state.requiredThreshold) return "pending";
|
|
248
|
+
if (at < state.executionTime) return "timelocked";
|
|
249
|
+
return "ready";
|
|
250
|
+
}
|
|
251
|
+
|
|
222
252
|
// src/config/networks.ts
|
|
223
253
|
var INDEXER_URL = "https://xbftgyuxaxagptudledv.supabase.co";
|
|
224
254
|
var INDEXER_PUBLISHABLE_KEY = "sb_publishable_EO-sTB-jqUzlsiugOEks-Q_qRtHDaHU";
|
|
@@ -418,6 +448,9 @@ function resolveConfig(options = {}) {
|
|
|
418
448
|
privateKey: options.signer ? void 0 : pick(options.privateKey, env[ENV_VARS.privateKey]),
|
|
419
449
|
consistency,
|
|
420
450
|
maxIndexerLagBlocks: options.maxIndexerLagBlocks ?? parseLag(env[ENV_VARS.maxIndexerLagBlocks]) ?? 50,
|
|
451
|
+
// Deliberately not environment-configurable: a clock is a function, and a numeric
|
|
452
|
+
// offset in an env var would reintroduce the sign convention `Clock` exists to avoid.
|
|
453
|
+
now: options.now ?? nowSeconds,
|
|
421
454
|
retry: {
|
|
422
455
|
...options.retry?.maxAttempts != null ? { maxAttempts: options.retry.maxAttempts } : {},
|
|
423
456
|
...options.retry?.baseDelayMs != null ? { baseDelayMs: options.retry.baseDelayMs } : {},
|
|
@@ -1092,7 +1125,7 @@ function encodeMultiSendPayload(calls) {
|
|
|
1092
1125
|
function encodeMultiSend(calls) {
|
|
1093
1126
|
return multiSend.encodeFunctionData("multiSend", [encodeMultiSendPayload(calls)]);
|
|
1094
1127
|
}
|
|
1095
|
-
function minimumExpiration(effectiveDelaySeconds, marginSeconds = 300, at =
|
|
1128
|
+
function minimumExpiration(effectiveDelaySeconds, marginSeconds = 300, at = nowSeconds()) {
|
|
1096
1129
|
return at + effectiveDelaySeconds + marginSeconds;
|
|
1097
1130
|
}
|
|
1098
1131
|
|
|
@@ -1774,14 +1807,19 @@ function toNumber(value, fallback = 0) {
|
|
|
1774
1807
|
}
|
|
1775
1808
|
|
|
1776
1809
|
// src/indexer/client.ts
|
|
1777
|
-
var SDK_VERSION = true ? "0.2.
|
|
1810
|
+
var SDK_VERSION = true ? "0.2.1" : "dev";
|
|
1778
1811
|
var IndexerClient = class {
|
|
1779
1812
|
config;
|
|
1780
1813
|
client;
|
|
1781
1814
|
realtime = null;
|
|
1782
1815
|
healthCache = null;
|
|
1783
1816
|
inflightHealth = null;
|
|
1784
|
-
/**
|
|
1817
|
+
/**
|
|
1818
|
+
* Health results are reused for this long to keep read paths cheap.
|
|
1819
|
+
*
|
|
1820
|
+
* This and `waitForBlock`'s deadline are elapsed-time arithmetic, so they use the
|
|
1821
|
+
* raw local clock rather than `ClientOptions.now` — see the note in `chain/retry.ts`.
|
|
1822
|
+
*/
|
|
1785
1823
|
healthCacheMs;
|
|
1786
1824
|
constructor(config, options = {}) {
|
|
1787
1825
|
this.config = config;
|
|
@@ -2389,36 +2427,6 @@ var RecoveryContract = class {
|
|
|
2389
2427
|
}
|
|
2390
2428
|
};
|
|
2391
2429
|
|
|
2392
|
-
// src/lifecycle/status.ts
|
|
2393
|
-
function nowSeconds() {
|
|
2394
|
-
return Math.floor(Date.now() / 1e3);
|
|
2395
|
-
}
|
|
2396
|
-
function executableAfterOf(approvedAt, executionDelay) {
|
|
2397
|
-
return approvedAt > 0 ? approvedAt + executionDelay : 0;
|
|
2398
|
-
}
|
|
2399
|
-
function deriveStatus(state, at = nowSeconds()) {
|
|
2400
|
-
if (state.failed) return "failed";
|
|
2401
|
-
if (state.executed) return "executed";
|
|
2402
|
-
if (state.cancelled) return state.isExpired ? "expired" : "cancelled";
|
|
2403
|
-
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
2404
|
-
if (state.approvalCount < state.threshold) return "pending";
|
|
2405
|
-
const executableAfter = executableAfterOf(state.approvedAt, state.executionDelay);
|
|
2406
|
-
if (state.executionDelay > 0 && (state.approvedAt === 0 || at < executableAfter)) {
|
|
2407
|
-
return "timelocked";
|
|
2408
|
-
}
|
|
2409
|
-
return "ready";
|
|
2410
|
-
}
|
|
2411
|
-
function isTerminal(status) {
|
|
2412
|
-
return status === "executed" || status === "failed" || status === "cancelled" || status === "expired";
|
|
2413
|
-
}
|
|
2414
|
-
function deriveRecoveryStatus(state, at = nowSeconds()) {
|
|
2415
|
-
if (state.executed) return "executed";
|
|
2416
|
-
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
2417
|
-
if (state.approvalCount < state.requiredThreshold) return "pending";
|
|
2418
|
-
if (at < state.executionTime) return "timelocked";
|
|
2419
|
-
return "ready";
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
2430
|
// src/recovery.ts
|
|
2423
2431
|
var RecoveryModule = class {
|
|
2424
2432
|
constructor(ctx) {
|
|
@@ -2448,6 +2456,10 @@ var RecoveryModule = class {
|
|
|
2448
2456
|
* them gates a write, so none of them should be the one read that silently skips
|
|
2449
2457
|
* transient-failure handling.
|
|
2450
2458
|
*/
|
|
2459
|
+
/** Now, in Unix seconds, for anything compared against a chain timestamp. */
|
|
2460
|
+
now() {
|
|
2461
|
+
return (this.ctx.now ?? nowSeconds)();
|
|
2462
|
+
}
|
|
2451
2463
|
vaultContract() {
|
|
2452
2464
|
return new VaultContract(this.ctx.connection.vault(this.vault), this.ctx.connection.retry);
|
|
2453
2465
|
}
|
|
@@ -2535,7 +2547,7 @@ var RecoveryModule = class {
|
|
|
2535
2547
|
requiredThreshold: row.required_threshold,
|
|
2536
2548
|
executionTime: Number(row.execution_time),
|
|
2537
2549
|
expiration: Number(row.expiration ?? 0)
|
|
2538
|
-
}),
|
|
2550
|
+
}, this.now()),
|
|
2539
2551
|
executed: row.status === "executed",
|
|
2540
2552
|
initiator: getAddress3(row.initiator_address),
|
|
2541
2553
|
source: "indexer"
|
|
@@ -2644,7 +2656,7 @@ var RecoveryModule = class {
|
|
|
2644
2656
|
`Not enough guardian approvals: ${request.approvalCount} of ${request.requiredThreshold}.`
|
|
2645
2657
|
);
|
|
2646
2658
|
}
|
|
2647
|
-
const now =
|
|
2659
|
+
const now = this.now();
|
|
2648
2660
|
if (now < request.executionTime) {
|
|
2649
2661
|
throw new PreconditionError(
|
|
2650
2662
|
`The recovery period has not elapsed. Executable after ${new Date(request.executionTime * 1e3).toISOString()}.`,
|
|
@@ -2689,7 +2701,7 @@ var RecoveryModule = class {
|
|
|
2689
2701
|
// Affordances
|
|
2690
2702
|
// =========================================================================
|
|
2691
2703
|
/** What `caller` may do to a recovery right now, and when blocked actions unlock. */
|
|
2692
|
-
async affordances(recoveryHash, caller) {
|
|
2704
|
+
async affordances(recoveryHash, caller, at) {
|
|
2693
2705
|
const hash = normalizeTxHash(recoveryHash, "recovery hash");
|
|
2694
2706
|
const who = caller ?? await this.ctx.connection.addressOrNull();
|
|
2695
2707
|
if (!who) {
|
|
@@ -2705,7 +2717,7 @@ var RecoveryModule = class {
|
|
|
2705
2717
|
this.isEnabled(),
|
|
2706
2718
|
this.vaultContract().isOwner(address2)
|
|
2707
2719
|
]);
|
|
2708
|
-
const now =
|
|
2720
|
+
const now = at ?? this.now();
|
|
2709
2721
|
const terminal = request.status === "executed" || request.status === "cancelled";
|
|
2710
2722
|
const expired = now > request.expiration && request.expiration > 0;
|
|
2711
2723
|
const quorum = request.approvalCount >= request.requiredThreshold;
|
|
@@ -2802,13 +2814,10 @@ var RecoveryModule = class {
|
|
|
2802
2814
|
const approvalCount = Number(raw.approvalCount ?? 0);
|
|
2803
2815
|
const requiredThreshold = Number(raw.requiredThreshold ?? 0);
|
|
2804
2816
|
const executed = Boolean(raw.executed);
|
|
2805
|
-
const status = deriveRecoveryStatus(
|
|
2806
|
-
executed,
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
executionTime,
|
|
2810
|
-
expiration
|
|
2811
|
-
});
|
|
2817
|
+
const status = deriveRecoveryStatus(
|
|
2818
|
+
{ executed, approvalCount, requiredThreshold, executionTime, expiration },
|
|
2819
|
+
this.now()
|
|
2820
|
+
);
|
|
2812
2821
|
return {
|
|
2813
2822
|
hash,
|
|
2814
2823
|
vault: this.vault,
|
|
@@ -2849,7 +2858,7 @@ var RecoveryModule = class {
|
|
|
2849
2858
|
if (request.executed) {
|
|
2850
2859
|
throw new PreconditionError("This recovery has already been executed.");
|
|
2851
2860
|
}
|
|
2852
|
-
if (request.expiration > 0 &&
|
|
2861
|
+
if (request.expiration > 0 && this.now() > request.expiration) {
|
|
2853
2862
|
throw new PreconditionError("This recovery has expired.", {
|
|
2854
2863
|
remediation: "Call expire() to clean it up, then initiate a new recovery."
|
|
2855
2864
|
});
|
|
@@ -3611,8 +3620,9 @@ function classifyExecution(receipt, vault2, txHash) {
|
|
|
3611
3620
|
}
|
|
3612
3621
|
const thresholdReached = eventFor(events, "ThresholdReached", txHash);
|
|
3613
3622
|
if (thresholdReached) {
|
|
3623
|
+
const approvedAt = toNumber2(thresholdReached.args.approvedAt);
|
|
3614
3624
|
const executableAfter = toNumber2(thresholdReached.args.executableAfter);
|
|
3615
|
-
if (executableAfter >
|
|
3625
|
+
if (executableAfter > approvedAt) {
|
|
3616
3626
|
return {
|
|
3617
3627
|
...base,
|
|
3618
3628
|
outcome: "timelock_started",
|
|
@@ -3654,7 +3664,8 @@ var Vault = class _Vault {
|
|
|
3654
3664
|
connection: ctx.connection,
|
|
3655
3665
|
queries: ctx.queries,
|
|
3656
3666
|
vaultAddress: this.address,
|
|
3657
|
-
moduleAddress: ctx.contracts.socialRecovery
|
|
3667
|
+
moduleAddress: ctx.contracts.socialRecovery,
|
|
3668
|
+
...ctx.now ? { now: ctx.now } : {}
|
|
3658
3669
|
});
|
|
3659
3670
|
}
|
|
3660
3671
|
// =========================================================================
|
|
@@ -3700,6 +3711,15 @@ var Vault = class _Vault {
|
|
|
3700
3711
|
const health = await this.ctx.indexer?.health();
|
|
3701
3712
|
return health?.available ? health.lastIndexedBlock : void 0;
|
|
3702
3713
|
}
|
|
3714
|
+
/**
|
|
3715
|
+
* Now, in Unix seconds, for anything compared against a chain timestamp.
|
|
3716
|
+
*
|
|
3717
|
+
* Never used to measure elapsed time — see the duration sites in
|
|
3718
|
+
* {@link waitForExecutable}, which stay on the raw local clock deliberately.
|
|
3719
|
+
*/
|
|
3720
|
+
now() {
|
|
3721
|
+
return (this.ctx.now ?? nowSeconds)();
|
|
3722
|
+
}
|
|
3703
3723
|
contract(write = false) {
|
|
3704
3724
|
return new VaultContract(this.ctx.connection.vault(this.address, write), this.ctx.connection.retry);
|
|
3705
3725
|
}
|
|
@@ -3750,7 +3770,7 @@ var Vault = class _Vault {
|
|
|
3750
3770
|
owners,
|
|
3751
3771
|
threshold,
|
|
3752
3772
|
...indexedAtBlock !== void 0 ? { indexedAtBlock } : {},
|
|
3753
|
-
capturedAt:
|
|
3773
|
+
capturedAt: this.now(),
|
|
3754
3774
|
source: fromIndexer ? "indexer" : "chain"
|
|
3755
3775
|
};
|
|
3756
3776
|
}
|
|
@@ -3793,6 +3813,21 @@ var Vault = class _Vault {
|
|
|
3793
3813
|
async isOwner(address2) {
|
|
3794
3814
|
return this.contract().isOwner(getAddress5(address2));
|
|
3795
3815
|
}
|
|
3816
|
+
/**
|
|
3817
|
+
* Whether `owner`'s approval on `txHash` currently counts toward the threshold.
|
|
3818
|
+
*
|
|
3819
|
+
* Reads the chain, and accounts for approval-epoch invalidation: removing an owner
|
|
3820
|
+
* bumps the vault's epoch and voids every approval that address made, so a
|
|
3821
|
+
* confirmation the indexer still lists may already be dead. This is the authoritative
|
|
3822
|
+
* answer.
|
|
3823
|
+
*
|
|
3824
|
+
* Public because it is one of the two inputs {@link computeAffordances} needs. Without
|
|
3825
|
+
* it a consumer wanting affordances at an adjusted time had no way to assemble an
|
|
3826
|
+
* `AffordanceContext` short of reimplementing this method against the raw contract.
|
|
3827
|
+
*/
|
|
3828
|
+
async hasApproved(txHash, owner) {
|
|
3829
|
+
return this.contract().hasApproved(normalizeTxHash(txHash), getAddress5(owner));
|
|
3830
|
+
}
|
|
3796
3831
|
/** Approvals required to execute. Prefers a pinned view, then the indexer, then chain. */
|
|
3797
3832
|
async threshold() {
|
|
3798
3833
|
if (this.ctx.view) return this.ctx.view.threshold;
|
|
@@ -4016,7 +4051,7 @@ var Vault = class _Vault {
|
|
|
4016
4051
|
approvalCount,
|
|
4017
4052
|
threshold,
|
|
4018
4053
|
failed: row.status === "failed"
|
|
4019
|
-
});
|
|
4054
|
+
}, this.now());
|
|
4020
4055
|
const failedReturnData = row.failed_return_data ?? void 0;
|
|
4021
4056
|
return {
|
|
4022
4057
|
hash: row.tx_hash,
|
|
@@ -4099,7 +4134,7 @@ var Vault = class _Vault {
|
|
|
4099
4134
|
approvedAt,
|
|
4100
4135
|
approvalCount: approvals.length,
|
|
4101
4136
|
threshold: Number(threshold)
|
|
4102
|
-
}),
|
|
4137
|
+
}, this.now()),
|
|
4103
4138
|
approvals,
|
|
4104
4139
|
approvalCount: approvals.length,
|
|
4105
4140
|
threshold: Number(threshold),
|
|
@@ -4117,9 +4152,9 @@ var Vault = class _Vault {
|
|
|
4117
4152
|
* What `caller` may legally do to `txHash` right now, and when blocked actions
|
|
4118
4153
|
* become available. Defaults to the connected signer's address.
|
|
4119
4154
|
*/
|
|
4120
|
-
async affordances(txHash, caller) {
|
|
4155
|
+
async affordances(txHash, caller, at) {
|
|
4121
4156
|
const hash = normalizeTxHash(txHash);
|
|
4122
|
-
return this.resolveAffordances(hash, this.transaction(hash), caller);
|
|
4157
|
+
return this.resolveAffordances(hash, this.transaction(hash), caller, at);
|
|
4123
4158
|
}
|
|
4124
4159
|
/**
|
|
4125
4160
|
* Shared body of {@link affordances}, taking the transaction either as a value or as
|
|
@@ -4130,7 +4165,7 @@ var Vault = class _Vault {
|
|
|
4130
4165
|
* passes the unawaited promise so the transaction read still overlaps the two
|
|
4131
4166
|
* ownership probes rather than serialising behind them.
|
|
4132
4167
|
*/
|
|
4133
|
-
async resolveAffordances(hash, transaction, caller) {
|
|
4168
|
+
async resolveAffordances(hash, transaction, caller, at) {
|
|
4134
4169
|
const who = caller ?? await this.ctx.connection.addressOrNull();
|
|
4135
4170
|
if (!who) {
|
|
4136
4171
|
throw new ValidationError(
|
|
@@ -4143,7 +4178,13 @@ var Vault = class _Vault {
|
|
|
4143
4178
|
vault2.isOwner(getAddress5(who)),
|
|
4144
4179
|
vault2.hasApproved(hash, getAddress5(who))
|
|
4145
4180
|
]);
|
|
4146
|
-
return computeAffordances({
|
|
4181
|
+
return computeAffordances({
|
|
4182
|
+
tx,
|
|
4183
|
+
caller: getAddress5(who),
|
|
4184
|
+
isOwner,
|
|
4185
|
+
hasApproved,
|
|
4186
|
+
at: at ?? this.now()
|
|
4187
|
+
});
|
|
4147
4188
|
}
|
|
4148
4189
|
// =========================================================================
|
|
4149
4190
|
// Proposals
|
|
@@ -4354,11 +4395,11 @@ var Vault = class _Vault {
|
|
|
4354
4395
|
}
|
|
4355
4396
|
if (expiration > 0) {
|
|
4356
4397
|
const floor = isSelfCall ? 0 : Math.max(executionDelay, await this.minExecutionDelay());
|
|
4357
|
-
const earliest = minimumExpiration(floor, 0);
|
|
4398
|
+
const earliest = minimumExpiration(floor, 0, this.now());
|
|
4358
4399
|
if (expiration <= earliest) {
|
|
4359
4400
|
throw new ValidationError(
|
|
4360
4401
|
`expiration ${expiration} is too soon: with an effective delay of ${floor}s the vault requires an expiration after ${earliest}.`,
|
|
4361
|
-
`Use at least ${minimumExpiration(floor)} to leave a margin for block time.`
|
|
4402
|
+
`Use at least ${minimumExpiration(floor, 300, this.now())} to leave a margin for block time.`
|
|
4362
4403
|
);
|
|
4363
4404
|
}
|
|
4364
4405
|
}
|
|
@@ -4697,7 +4738,7 @@ var Vault = class _Vault {
|
|
|
4697
4738
|
{ remediation: "Collect the remaining approvals, then wait for the timelock." }
|
|
4698
4739
|
);
|
|
4699
4740
|
}
|
|
4700
|
-
const untilExecutable = tx.executableAfter > 0 ? tx.executableAfter
|
|
4741
|
+
const untilExecutable = tx.executableAfter > 0 ? (tx.executableAfter - this.now()) * 1e3 : pollIntervalMs;
|
|
4701
4742
|
const wait = Math.max(1e3, Math.min(untilExecutable, pollIntervalMs));
|
|
4702
4743
|
if (Date.now() + wait > deadline) {
|
|
4703
4744
|
throw new PreconditionError(
|
|
@@ -4727,7 +4768,7 @@ var Vault = class _Vault {
|
|
|
4727
4768
|
lines.push(` quorum reached: ${new Date(tx.approvedAt * 1e3).toISOString()}`);
|
|
4728
4769
|
}
|
|
4729
4770
|
if (tx.executableAfter > 0) {
|
|
4730
|
-
const remaining = tx.executableAfter -
|
|
4771
|
+
const remaining = tx.executableAfter - this.now();
|
|
4731
4772
|
lines.push(
|
|
4732
4773
|
` executable after: ${new Date(tx.executableAfter * 1e3).toISOString()}` + (remaining > 0 ? ` (in ${remaining}s)` : " (now)")
|
|
4733
4774
|
);
|
|
@@ -4772,6 +4813,11 @@ function toRevertError(err, context) {
|
|
|
4772
4813
|
|
|
4773
4814
|
// src/client.ts
|
|
4774
4815
|
var QuaiVaultClient = class {
|
|
4816
|
+
/**
|
|
4817
|
+
* Source of "now" for time compared against chain timestamps. Resolved once from
|
|
4818
|
+
* {@link ClientOptions.now}, defaulting to the local clock. See {@link Clock}.
|
|
4819
|
+
*/
|
|
4820
|
+
now;
|
|
4775
4821
|
/**
|
|
4776
4822
|
* Resolved configuration, with the private key stripped and the indexer key
|
|
4777
4823
|
* masked — safe to log. The key itself is consumed once when building the signer.
|
|
@@ -4784,6 +4830,7 @@ var QuaiVaultClient = class {
|
|
|
4784
4830
|
constructor(options = {}) {
|
|
4785
4831
|
const resolved = resolveConfig(options);
|
|
4786
4832
|
this.config = redactConfig(resolved);
|
|
4833
|
+
this.now = resolved.now;
|
|
4787
4834
|
this.connection = new Connection(resolved, {
|
|
4788
4835
|
...options.provider ? { provider: options.provider } : {},
|
|
4789
4836
|
...options.signer ? { signer: options.signer } : {}
|
|
@@ -4819,7 +4866,8 @@ var QuaiVaultClient = class {
|
|
|
4819
4866
|
queries: this.queries,
|
|
4820
4867
|
contracts: this.config.contracts,
|
|
4821
4868
|
consistency: this.config.consistency,
|
|
4822
|
-
maxIndexerLagBlocks: this.config.maxIndexerLagBlocks
|
|
4869
|
+
maxIndexerLagBlocks: this.config.maxIndexerLagBlocks,
|
|
4870
|
+
now: this.now
|
|
4823
4871
|
};
|
|
4824
4872
|
}
|
|
4825
4873
|
/** Vault discovery. Requires the indexer — there is no on-chain reverse index. */
|