@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/README.md
CHANGED
|
@@ -164,9 +164,11 @@ qv.indexerHealth()
|
|
|
164
164
|
```ts
|
|
165
165
|
vault.info() / owners() / threshold() / balance() / modules() / delegatecallTargets()
|
|
166
166
|
vault.isOwner(a) / isModuleEnabled(m) / isDelegatecallAllowed(t) / isValidSignature(hash)
|
|
167
|
-
vault.transaction(txHash) /
|
|
168
|
-
vault.transactionHash(to, value, data, nonce?)
|
|
169
|
-
vault.
|
|
167
|
+
vault.transaction(txHash) / transactions(txHashes) / pendingTransactions(page?)
|
|
168
|
+
vault.transactionHistory(page?) / transactionHash(to, value, data, nonce?)
|
|
169
|
+
vault.hasApproved(txHash, owner)
|
|
170
|
+
vault.affordances(txHash, caller?, at?) / describe(txHash, caller?)
|
|
171
|
+
vault.view() / pinned(view) // explicit owners+threshold snapshot
|
|
170
172
|
vault.balances(opts?) / deposits(page?) / tokenTransfers(page?) / signedMessages()
|
|
171
173
|
vault.waitForExecutable(txHash, opts?)
|
|
172
174
|
vault.watch(handler, opts?) // Supabase Realtime
|
|
@@ -335,6 +337,38 @@ nonce and funds errors are permanent by definition, and anything unrecognised is
|
|
|
335
337
|
permanent too — so a genuine bug surfaces immediately instead of hiding behind three slow
|
|
336
338
|
attempts. Rate limits, 5xx and raw transport failures are retried.
|
|
337
339
|
|
|
340
|
+
## Clock skew
|
|
341
|
+
|
|
342
|
+
The contracts decide with `block.timestamp`. Everything the SDK derives locally — a
|
|
343
|
+
transaction's `ready` vs `timelocked`, what a caller may do next, whether a recovery period
|
|
344
|
+
has elapsed — predicts that, and a machine whose clock is wrong predicts wrongly.
|
|
345
|
+
Containers, CI runners and VMs resumed from a snapshot drift in ways a desktop usually
|
|
346
|
+
does not.
|
|
347
|
+
|
|
348
|
+
If you have measured the offset, feed it back in:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
const block = await qv.provider.getBlock('latest');
|
|
352
|
+
const skew = Date.now() / 1000 - Number(block.timestamp); // positive: local clock ahead
|
|
353
|
+
|
|
354
|
+
const qv = connect({ network: 'mainnet', now: () => Date.now() / 1000 - skew });
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
A function rather than a scalar offset on purpose: an offset needs a sign convention, and
|
|
358
|
+
getting it backwards doubles the error instead of cancelling it, silently. Here the
|
|
359
|
+
arithmetic sits in your code, where it reads as what it means.
|
|
360
|
+
|
|
361
|
+
Detection is deliberately yours. Deriving the offset costs an RPC call the SDK should not
|
|
362
|
+
make on your behalf, and caching one has no clear invalidation.
|
|
363
|
+
|
|
364
|
+
**This never touches elapsed-time measurement** — retry backoff, timeouts, poll intervals
|
|
365
|
+
and cache TTLs stay on the raw local clock. A clock being 12 seconds fast does not make 30
|
|
366
|
+
seconds of backoff into 18.
|
|
367
|
+
|
|
368
|
+
Nothing here can change an on-chain outcome; the chain is the authority and a wrong local
|
|
369
|
+
time fails safe into a revert or a refusal. What it changes is what the SDK *tells* you,
|
|
370
|
+
which is most of what the SDK is for.
|
|
371
|
+
|
|
338
372
|
## Development
|
|
339
373
|
|
|
340
374
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -3647,6 +3647,36 @@ function normalizeTxHash(value, label = "transaction hash") {
|
|
|
3647
3647
|
// src/config/resolve.ts
|
|
3648
3648
|
var import_quais2 = require("quais");
|
|
3649
3649
|
|
|
3650
|
+
// src/lifecycle/status.ts
|
|
3651
|
+
function nowSeconds() {
|
|
3652
|
+
return Math.floor(Date.now() / 1e3);
|
|
3653
|
+
}
|
|
3654
|
+
function executableAfterOf(approvedAt, executionDelay) {
|
|
3655
|
+
return approvedAt > 0 ? approvedAt + executionDelay : 0;
|
|
3656
|
+
}
|
|
3657
|
+
function deriveStatus(state, at = nowSeconds()) {
|
|
3658
|
+
if (state.failed) return "failed";
|
|
3659
|
+
if (state.executed) return "executed";
|
|
3660
|
+
if (state.cancelled) return state.isExpired ? "expired" : "cancelled";
|
|
3661
|
+
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
3662
|
+
if (state.approvalCount < state.threshold) return "pending";
|
|
3663
|
+
const executableAfter = executableAfterOf(state.approvedAt, state.executionDelay);
|
|
3664
|
+
if (state.executionDelay > 0 && (state.approvedAt === 0 || at < executableAfter)) {
|
|
3665
|
+
return "timelocked";
|
|
3666
|
+
}
|
|
3667
|
+
return "ready";
|
|
3668
|
+
}
|
|
3669
|
+
function isTerminal(status) {
|
|
3670
|
+
return status === "executed" || status === "failed" || status === "cancelled" || status === "expired";
|
|
3671
|
+
}
|
|
3672
|
+
function deriveRecoveryStatus(state, at = nowSeconds()) {
|
|
3673
|
+
if (state.executed) return "executed";
|
|
3674
|
+
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
3675
|
+
if (state.approvalCount < state.requiredThreshold) return "pending";
|
|
3676
|
+
if (at < state.executionTime) return "timelocked";
|
|
3677
|
+
return "ready";
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3650
3680
|
// src/config/networks.ts
|
|
3651
3681
|
var INDEXER_URL = "https://xbftgyuxaxagptudledv.supabase.co";
|
|
3652
3682
|
var INDEXER_PUBLISHABLE_KEY = "sb_publishable_EO-sTB-jqUzlsiugOEks-Q_qRtHDaHU";
|
|
@@ -3846,6 +3876,9 @@ function resolveConfig(options = {}) {
|
|
|
3846
3876
|
privateKey: options.signer ? void 0 : pick(options.privateKey, env[ENV_VARS.privateKey]),
|
|
3847
3877
|
consistency,
|
|
3848
3878
|
maxIndexerLagBlocks: options.maxIndexerLagBlocks ?? parseLag(env[ENV_VARS.maxIndexerLagBlocks]) ?? 50,
|
|
3879
|
+
// Deliberately not environment-configurable: a clock is a function, and a numeric
|
|
3880
|
+
// offset in an env var would reintroduce the sign convention `Clock` exists to avoid.
|
|
3881
|
+
now: options.now ?? nowSeconds,
|
|
3849
3882
|
retry: {
|
|
3850
3883
|
...options.retry?.maxAttempts != null ? { maxAttempts: options.retry.maxAttempts } : {},
|
|
3851
3884
|
...options.retry?.baseDelayMs != null ? { baseDelayMs: options.retry.baseDelayMs } : {},
|
|
@@ -4520,7 +4553,7 @@ function encodeMultiSendPayload(calls) {
|
|
|
4520
4553
|
function encodeMultiSend(calls) {
|
|
4521
4554
|
return multiSend.encodeFunctionData("multiSend", [encodeMultiSendPayload(calls)]);
|
|
4522
4555
|
}
|
|
4523
|
-
function minimumExpiration(effectiveDelaySeconds, marginSeconds = 300, at =
|
|
4556
|
+
function minimumExpiration(effectiveDelaySeconds, marginSeconds = 300, at = nowSeconds()) {
|
|
4524
4557
|
return at + effectiveDelaySeconds + marginSeconds;
|
|
4525
4558
|
}
|
|
4526
4559
|
|
|
@@ -5202,14 +5235,19 @@ function toNumber(value, fallback = 0) {
|
|
|
5202
5235
|
}
|
|
5203
5236
|
|
|
5204
5237
|
// src/indexer/client.ts
|
|
5205
|
-
var SDK_VERSION = true ? "0.2.
|
|
5238
|
+
var SDK_VERSION = true ? "0.2.1" : "dev";
|
|
5206
5239
|
var IndexerClient = class {
|
|
5207
5240
|
config;
|
|
5208
5241
|
client;
|
|
5209
5242
|
realtime = null;
|
|
5210
5243
|
healthCache = null;
|
|
5211
5244
|
inflightHealth = null;
|
|
5212
|
-
/**
|
|
5245
|
+
/**
|
|
5246
|
+
* Health results are reused for this long to keep read paths cheap.
|
|
5247
|
+
*
|
|
5248
|
+
* This and `waitForBlock`'s deadline are elapsed-time arithmetic, so they use the
|
|
5249
|
+
* raw local clock rather than `ClientOptions.now` — see the note in `chain/retry.ts`.
|
|
5250
|
+
*/
|
|
5213
5251
|
healthCacheMs;
|
|
5214
5252
|
constructor(config, options = {}) {
|
|
5215
5253
|
this.config = config;
|
|
@@ -5817,36 +5855,6 @@ var RecoveryContract = class {
|
|
|
5817
5855
|
}
|
|
5818
5856
|
};
|
|
5819
5857
|
|
|
5820
|
-
// src/lifecycle/status.ts
|
|
5821
|
-
function nowSeconds() {
|
|
5822
|
-
return Math.floor(Date.now() / 1e3);
|
|
5823
|
-
}
|
|
5824
|
-
function executableAfterOf(approvedAt, executionDelay) {
|
|
5825
|
-
return approvedAt > 0 ? approvedAt + executionDelay : 0;
|
|
5826
|
-
}
|
|
5827
|
-
function deriveStatus(state, at = nowSeconds()) {
|
|
5828
|
-
if (state.failed) return "failed";
|
|
5829
|
-
if (state.executed) return "executed";
|
|
5830
|
-
if (state.cancelled) return state.isExpired ? "expired" : "cancelled";
|
|
5831
|
-
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
5832
|
-
if (state.approvalCount < state.threshold) return "pending";
|
|
5833
|
-
const executableAfter = executableAfterOf(state.approvedAt, state.executionDelay);
|
|
5834
|
-
if (state.executionDelay > 0 && (state.approvedAt === 0 || at < executableAfter)) {
|
|
5835
|
-
return "timelocked";
|
|
5836
|
-
}
|
|
5837
|
-
return "ready";
|
|
5838
|
-
}
|
|
5839
|
-
function isTerminal(status) {
|
|
5840
|
-
return status === "executed" || status === "failed" || status === "cancelled" || status === "expired";
|
|
5841
|
-
}
|
|
5842
|
-
function deriveRecoveryStatus(state, at = nowSeconds()) {
|
|
5843
|
-
if (state.executed) return "executed";
|
|
5844
|
-
if (state.expiration > 0 && at > state.expiration) return "expired";
|
|
5845
|
-
if (state.approvalCount < state.requiredThreshold) return "pending";
|
|
5846
|
-
if (at < state.executionTime) return "timelocked";
|
|
5847
|
-
return "ready";
|
|
5848
|
-
}
|
|
5849
|
-
|
|
5850
5858
|
// src/recovery.ts
|
|
5851
5859
|
var RecoveryModule = class {
|
|
5852
5860
|
constructor(ctx) {
|
|
@@ -5876,6 +5884,10 @@ var RecoveryModule = class {
|
|
|
5876
5884
|
* them gates a write, so none of them should be the one read that silently skips
|
|
5877
5885
|
* transient-failure handling.
|
|
5878
5886
|
*/
|
|
5887
|
+
/** Now, in Unix seconds, for anything compared against a chain timestamp. */
|
|
5888
|
+
now() {
|
|
5889
|
+
return (this.ctx.now ?? nowSeconds)();
|
|
5890
|
+
}
|
|
5879
5891
|
vaultContract() {
|
|
5880
5892
|
return new VaultContract(this.ctx.connection.vault(this.vault), this.ctx.connection.retry);
|
|
5881
5893
|
}
|
|
@@ -5963,7 +5975,7 @@ var RecoveryModule = class {
|
|
|
5963
5975
|
requiredThreshold: row.required_threshold,
|
|
5964
5976
|
executionTime: Number(row.execution_time),
|
|
5965
5977
|
expiration: Number(row.expiration ?? 0)
|
|
5966
|
-
}),
|
|
5978
|
+
}, this.now()),
|
|
5967
5979
|
executed: row.status === "executed",
|
|
5968
5980
|
initiator: (0, import_quais9.getAddress)(row.initiator_address),
|
|
5969
5981
|
source: "indexer"
|
|
@@ -6072,7 +6084,7 @@ var RecoveryModule = class {
|
|
|
6072
6084
|
`Not enough guardian approvals: ${request.approvalCount} of ${request.requiredThreshold}.`
|
|
6073
6085
|
);
|
|
6074
6086
|
}
|
|
6075
|
-
const now =
|
|
6087
|
+
const now = this.now();
|
|
6076
6088
|
if (now < request.executionTime) {
|
|
6077
6089
|
throw new PreconditionError(
|
|
6078
6090
|
`The recovery period has not elapsed. Executable after ${new Date(request.executionTime * 1e3).toISOString()}.`,
|
|
@@ -6117,7 +6129,7 @@ var RecoveryModule = class {
|
|
|
6117
6129
|
// Affordances
|
|
6118
6130
|
// =========================================================================
|
|
6119
6131
|
/** What `caller` may do to a recovery right now, and when blocked actions unlock. */
|
|
6120
|
-
async affordances(recoveryHash, caller) {
|
|
6132
|
+
async affordances(recoveryHash, caller, at) {
|
|
6121
6133
|
const hash = normalizeTxHash(recoveryHash, "recovery hash");
|
|
6122
6134
|
const who = caller ?? await this.ctx.connection.addressOrNull();
|
|
6123
6135
|
if (!who) {
|
|
@@ -6133,7 +6145,7 @@ var RecoveryModule = class {
|
|
|
6133
6145
|
this.isEnabled(),
|
|
6134
6146
|
this.vaultContract().isOwner(address2)
|
|
6135
6147
|
]);
|
|
6136
|
-
const now =
|
|
6148
|
+
const now = at ?? this.now();
|
|
6137
6149
|
const terminal = request.status === "executed" || request.status === "cancelled";
|
|
6138
6150
|
const expired = now > request.expiration && request.expiration > 0;
|
|
6139
6151
|
const quorum = request.approvalCount >= request.requiredThreshold;
|
|
@@ -6230,13 +6242,10 @@ var RecoveryModule = class {
|
|
|
6230
6242
|
const approvalCount = Number(raw.approvalCount ?? 0);
|
|
6231
6243
|
const requiredThreshold = Number(raw.requiredThreshold ?? 0);
|
|
6232
6244
|
const executed = Boolean(raw.executed);
|
|
6233
|
-
const status = deriveRecoveryStatus(
|
|
6234
|
-
executed,
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
executionTime,
|
|
6238
|
-
expiration
|
|
6239
|
-
});
|
|
6245
|
+
const status = deriveRecoveryStatus(
|
|
6246
|
+
{ executed, approvalCount, requiredThreshold, executionTime, expiration },
|
|
6247
|
+
this.now()
|
|
6248
|
+
);
|
|
6240
6249
|
return {
|
|
6241
6250
|
hash,
|
|
6242
6251
|
vault: this.vault,
|
|
@@ -6277,7 +6286,7 @@ var RecoveryModule = class {
|
|
|
6277
6286
|
if (request.executed) {
|
|
6278
6287
|
throw new PreconditionError("This recovery has already been executed.");
|
|
6279
6288
|
}
|
|
6280
|
-
if (request.expiration > 0 &&
|
|
6289
|
+
if (request.expiration > 0 && this.now() > request.expiration) {
|
|
6281
6290
|
throw new PreconditionError("This recovery has expired.", {
|
|
6282
6291
|
remediation: "Call expire() to clean it up, then initiate a new recovery."
|
|
6283
6292
|
});
|
|
@@ -7039,8 +7048,9 @@ function classifyExecution(receipt, vault2, txHash) {
|
|
|
7039
7048
|
}
|
|
7040
7049
|
const thresholdReached = eventFor(events, "ThresholdReached", txHash);
|
|
7041
7050
|
if (thresholdReached) {
|
|
7051
|
+
const approvedAt = toNumber2(thresholdReached.args.approvedAt);
|
|
7042
7052
|
const executableAfter = toNumber2(thresholdReached.args.executableAfter);
|
|
7043
|
-
if (executableAfter >
|
|
7053
|
+
if (executableAfter > approvedAt) {
|
|
7044
7054
|
return {
|
|
7045
7055
|
...base,
|
|
7046
7056
|
outcome: "timelock_started",
|
|
@@ -7082,7 +7092,8 @@ var Vault = class _Vault {
|
|
|
7082
7092
|
connection: ctx.connection,
|
|
7083
7093
|
queries: ctx.queries,
|
|
7084
7094
|
vaultAddress: this.address,
|
|
7085
|
-
moduleAddress: ctx.contracts.socialRecovery
|
|
7095
|
+
moduleAddress: ctx.contracts.socialRecovery,
|
|
7096
|
+
...ctx.now ? { now: ctx.now } : {}
|
|
7086
7097
|
});
|
|
7087
7098
|
}
|
|
7088
7099
|
// =========================================================================
|
|
@@ -7128,6 +7139,15 @@ var Vault = class _Vault {
|
|
|
7128
7139
|
const health = await this.ctx.indexer?.health();
|
|
7129
7140
|
return health?.available ? health.lastIndexedBlock : void 0;
|
|
7130
7141
|
}
|
|
7142
|
+
/**
|
|
7143
|
+
* Now, in Unix seconds, for anything compared against a chain timestamp.
|
|
7144
|
+
*
|
|
7145
|
+
* Never used to measure elapsed time — see the duration sites in
|
|
7146
|
+
* {@link waitForExecutable}, which stay on the raw local clock deliberately.
|
|
7147
|
+
*/
|
|
7148
|
+
now() {
|
|
7149
|
+
return (this.ctx.now ?? nowSeconds)();
|
|
7150
|
+
}
|
|
7131
7151
|
contract(write = false) {
|
|
7132
7152
|
return new VaultContract(this.ctx.connection.vault(this.address, write), this.ctx.connection.retry);
|
|
7133
7153
|
}
|
|
@@ -7178,7 +7198,7 @@ var Vault = class _Vault {
|
|
|
7178
7198
|
owners,
|
|
7179
7199
|
threshold,
|
|
7180
7200
|
...indexedAtBlock !== void 0 ? { indexedAtBlock } : {},
|
|
7181
|
-
capturedAt:
|
|
7201
|
+
capturedAt: this.now(),
|
|
7182
7202
|
source: fromIndexer ? "indexer" : "chain"
|
|
7183
7203
|
};
|
|
7184
7204
|
}
|
|
@@ -7221,6 +7241,21 @@ var Vault = class _Vault {
|
|
|
7221
7241
|
async isOwner(address2) {
|
|
7222
7242
|
return this.contract().isOwner((0, import_quais14.getAddress)(address2));
|
|
7223
7243
|
}
|
|
7244
|
+
/**
|
|
7245
|
+
* Whether `owner`'s approval on `txHash` currently counts toward the threshold.
|
|
7246
|
+
*
|
|
7247
|
+
* Reads the chain, and accounts for approval-epoch invalidation: removing an owner
|
|
7248
|
+
* bumps the vault's epoch and voids every approval that address made, so a
|
|
7249
|
+
* confirmation the indexer still lists may already be dead. This is the authoritative
|
|
7250
|
+
* answer.
|
|
7251
|
+
*
|
|
7252
|
+
* Public because it is one of the two inputs {@link computeAffordances} needs. Without
|
|
7253
|
+
* it a consumer wanting affordances at an adjusted time had no way to assemble an
|
|
7254
|
+
* `AffordanceContext` short of reimplementing this method against the raw contract.
|
|
7255
|
+
*/
|
|
7256
|
+
async hasApproved(txHash, owner) {
|
|
7257
|
+
return this.contract().hasApproved(normalizeTxHash(txHash), (0, import_quais14.getAddress)(owner));
|
|
7258
|
+
}
|
|
7224
7259
|
/** Approvals required to execute. Prefers a pinned view, then the indexer, then chain. */
|
|
7225
7260
|
async threshold() {
|
|
7226
7261
|
if (this.ctx.view) return this.ctx.view.threshold;
|
|
@@ -7444,7 +7479,7 @@ var Vault = class _Vault {
|
|
|
7444
7479
|
approvalCount,
|
|
7445
7480
|
threshold,
|
|
7446
7481
|
failed: row.status === "failed"
|
|
7447
|
-
});
|
|
7482
|
+
}, this.now());
|
|
7448
7483
|
const failedReturnData = row.failed_return_data ?? void 0;
|
|
7449
7484
|
return {
|
|
7450
7485
|
hash: row.tx_hash,
|
|
@@ -7527,7 +7562,7 @@ var Vault = class _Vault {
|
|
|
7527
7562
|
approvedAt,
|
|
7528
7563
|
approvalCount: approvals.length,
|
|
7529
7564
|
threshold: Number(threshold)
|
|
7530
|
-
}),
|
|
7565
|
+
}, this.now()),
|
|
7531
7566
|
approvals,
|
|
7532
7567
|
approvalCount: approvals.length,
|
|
7533
7568
|
threshold: Number(threshold),
|
|
@@ -7545,9 +7580,9 @@ var Vault = class _Vault {
|
|
|
7545
7580
|
* What `caller` may legally do to `txHash` right now, and when blocked actions
|
|
7546
7581
|
* become available. Defaults to the connected signer's address.
|
|
7547
7582
|
*/
|
|
7548
|
-
async affordances(txHash, caller) {
|
|
7583
|
+
async affordances(txHash, caller, at) {
|
|
7549
7584
|
const hash = normalizeTxHash(txHash);
|
|
7550
|
-
return this.resolveAffordances(hash, this.transaction(hash), caller);
|
|
7585
|
+
return this.resolveAffordances(hash, this.transaction(hash), caller, at);
|
|
7551
7586
|
}
|
|
7552
7587
|
/**
|
|
7553
7588
|
* Shared body of {@link affordances}, taking the transaction either as a value or as
|
|
@@ -7558,7 +7593,7 @@ var Vault = class _Vault {
|
|
|
7558
7593
|
* passes the unawaited promise so the transaction read still overlaps the two
|
|
7559
7594
|
* ownership probes rather than serialising behind them.
|
|
7560
7595
|
*/
|
|
7561
|
-
async resolveAffordances(hash, transaction, caller) {
|
|
7596
|
+
async resolveAffordances(hash, transaction, caller, at) {
|
|
7562
7597
|
const who = caller ?? await this.ctx.connection.addressOrNull();
|
|
7563
7598
|
if (!who) {
|
|
7564
7599
|
throw new ValidationError(
|
|
@@ -7571,7 +7606,13 @@ var Vault = class _Vault {
|
|
|
7571
7606
|
vault2.isOwner((0, import_quais14.getAddress)(who)),
|
|
7572
7607
|
vault2.hasApproved(hash, (0, import_quais14.getAddress)(who))
|
|
7573
7608
|
]);
|
|
7574
|
-
return computeAffordances({
|
|
7609
|
+
return computeAffordances({
|
|
7610
|
+
tx,
|
|
7611
|
+
caller: (0, import_quais14.getAddress)(who),
|
|
7612
|
+
isOwner,
|
|
7613
|
+
hasApproved,
|
|
7614
|
+
at: at ?? this.now()
|
|
7615
|
+
});
|
|
7575
7616
|
}
|
|
7576
7617
|
// =========================================================================
|
|
7577
7618
|
// Proposals
|
|
@@ -7782,11 +7823,11 @@ var Vault = class _Vault {
|
|
|
7782
7823
|
}
|
|
7783
7824
|
if (expiration > 0) {
|
|
7784
7825
|
const floor = isSelfCall ? 0 : Math.max(executionDelay, await this.minExecutionDelay());
|
|
7785
|
-
const earliest = minimumExpiration(floor, 0);
|
|
7826
|
+
const earliest = minimumExpiration(floor, 0, this.now());
|
|
7786
7827
|
if (expiration <= earliest) {
|
|
7787
7828
|
throw new ValidationError(
|
|
7788
7829
|
`expiration ${expiration} is too soon: with an effective delay of ${floor}s the vault requires an expiration after ${earliest}.`,
|
|
7789
|
-
`Use at least ${minimumExpiration(floor)} to leave a margin for block time.`
|
|
7830
|
+
`Use at least ${minimumExpiration(floor, 300, this.now())} to leave a margin for block time.`
|
|
7790
7831
|
);
|
|
7791
7832
|
}
|
|
7792
7833
|
}
|
|
@@ -8125,7 +8166,7 @@ var Vault = class _Vault {
|
|
|
8125
8166
|
{ remediation: "Collect the remaining approvals, then wait for the timelock." }
|
|
8126
8167
|
);
|
|
8127
8168
|
}
|
|
8128
|
-
const untilExecutable = tx.executableAfter > 0 ? tx.executableAfter
|
|
8169
|
+
const untilExecutable = tx.executableAfter > 0 ? (tx.executableAfter - this.now()) * 1e3 : pollIntervalMs;
|
|
8129
8170
|
const wait = Math.max(1e3, Math.min(untilExecutable, pollIntervalMs));
|
|
8130
8171
|
if (Date.now() + wait > deadline) {
|
|
8131
8172
|
throw new PreconditionError(
|
|
@@ -8155,7 +8196,7 @@ var Vault = class _Vault {
|
|
|
8155
8196
|
lines.push(` quorum reached: ${new Date(tx.approvedAt * 1e3).toISOString()}`);
|
|
8156
8197
|
}
|
|
8157
8198
|
if (tx.executableAfter > 0) {
|
|
8158
|
-
const remaining = tx.executableAfter -
|
|
8199
|
+
const remaining = tx.executableAfter - this.now();
|
|
8159
8200
|
lines.push(
|
|
8160
8201
|
` executable after: ${new Date(tx.executableAfter * 1e3).toISOString()}` + (remaining > 0 ? ` (in ${remaining}s)` : " (now)")
|
|
8161
8202
|
);
|
|
@@ -8200,6 +8241,11 @@ function toRevertError(err, context) {
|
|
|
8200
8241
|
|
|
8201
8242
|
// src/client.ts
|
|
8202
8243
|
var QuaiVaultClient = class {
|
|
8244
|
+
/**
|
|
8245
|
+
* Source of "now" for time compared against chain timestamps. Resolved once from
|
|
8246
|
+
* {@link ClientOptions.now}, defaulting to the local clock. See {@link Clock}.
|
|
8247
|
+
*/
|
|
8248
|
+
now;
|
|
8203
8249
|
/**
|
|
8204
8250
|
* Resolved configuration, with the private key stripped and the indexer key
|
|
8205
8251
|
* masked — safe to log. The key itself is consumed once when building the signer.
|
|
@@ -8212,6 +8258,7 @@ var QuaiVaultClient = class {
|
|
|
8212
8258
|
constructor(options = {}) {
|
|
8213
8259
|
const resolved = resolveConfig(options);
|
|
8214
8260
|
this.config = redactConfig(resolved);
|
|
8261
|
+
this.now = resolved.now;
|
|
8215
8262
|
this.connection = new Connection(resolved, {
|
|
8216
8263
|
...options.provider ? { provider: options.provider } : {},
|
|
8217
8264
|
...options.signer ? { signer: options.signer } : {}
|
|
@@ -8247,7 +8294,8 @@ var QuaiVaultClient = class {
|
|
|
8247
8294
|
queries: this.queries,
|
|
8248
8295
|
contracts: this.config.contracts,
|
|
8249
8296
|
consistency: this.config.consistency,
|
|
8250
|
-
maxIndexerLagBlocks: this.config.maxIndexerLagBlocks
|
|
8297
|
+
maxIndexerLagBlocks: this.config.maxIndexerLagBlocks,
|
|
8298
|
+
now: this.now
|
|
8251
8299
|
};
|
|
8252
8300
|
}
|
|
8253
8301
|
/** Vault discovery. Requires the indexer — there is no on-chain reverse index. */
|