@totemsdk/wots-lease 1.0.1 → 1.0.2

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.
Files changed (55) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +60 -0
  3. package/dist/axia.js +21 -25
  4. package/dist/certificate.js +7 -12
  5. package/dist/cjs/axia.d.ts +33 -0
  6. package/dist/cjs/axia.js +145 -0
  7. package/dist/cjs/certificate.d.ts +37 -0
  8. package/dist/cjs/certificate.js +74 -0
  9. package/dist/cjs/device.d.ts +18 -0
  10. package/dist/cjs/device.js +34 -0
  11. package/dist/cjs/errors.d.ts +71 -0
  12. package/dist/cjs/errors.js +135 -0
  13. package/dist/cjs/hybrid.d.ts +37 -0
  14. package/dist/cjs/hybrid.js +145 -0
  15. package/dist/cjs/index.d.ts +17 -0
  16. package/dist/cjs/index.js +45 -0
  17. package/dist/cjs/journal.d.ts +30 -0
  18. package/dist/cjs/journal.js +369 -0
  19. package/dist/cjs/local.d.ts +83 -0
  20. package/dist/cjs/local.js +440 -0
  21. package/dist/cjs/onchain.d.ts +54 -0
  22. package/dist/cjs/onchain.js +314 -0
  23. package/dist/cjs/package.json +1 -0
  24. package/dist/cjs/quorum.d.ts +75 -0
  25. package/dist/cjs/quorum.js +307 -0
  26. package/dist/cjs/replicated-backup.d.ts +30 -0
  27. package/dist/cjs/replicated-backup.js +52 -0
  28. package/dist/cjs/seed.d.ts +41 -0
  29. package/dist/cjs/seed.js +84 -0
  30. package/dist/cjs/stubs.d.ts +50 -0
  31. package/dist/cjs/stubs.js +125 -0
  32. package/dist/cjs/types.d.ts +272 -0
  33. package/dist/cjs/types.js +5 -0
  34. package/dist/cjs/watermark.d.ts +51 -0
  35. package/dist/cjs/watermark.js +328 -0
  36. package/dist/device.js +2 -6
  37. package/dist/errors.js +14 -31
  38. package/dist/hybrid.js +4 -8
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +12 -43
  41. package/dist/journal.js +35 -38
  42. package/dist/local.js +29 -33
  43. package/dist/onchain.js +23 -27
  44. package/dist/quorum.js +9 -13
  45. package/dist/replicated-backup.d.ts +30 -0
  46. package/dist/replicated-backup.js +48 -0
  47. package/dist/seed.js +7 -11
  48. package/dist/stubs.js +5 -11
  49. package/dist/types.js +1 -2
  50. package/dist/watermark.js +11 -18
  51. package/package.json +22 -13
  52. package/rust/pkg/package.json +2 -2
  53. package/rust/pkg/wots_lease_wasm_bg.wasm +0 -0
  54. package/rust/pkg-node/package.json +2 -2
  55. package/rust/pkg-node/wots_lease_wasm_bg.wasm +0 -0
@@ -0,0 +1,440 @@
1
+ "use strict";
2
+ /**
3
+ * LocalLeaseProvider — Layer 1: local durable ledger.
4
+ *
5
+ * Single-device, no network coordination.
6
+ * All WOTS safety guaranteed by the local WotsWatermarkStore + LeaseJournal.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.LocalLeaseProvider = void 0;
10
+ const core_1 = require("@totemsdk/core");
11
+ const core_2 = require("@totemsdk/core");
12
+ const watermark_js_1 = require("./watermark.js");
13
+ const journal_js_1 = require("./journal.js");
14
+ const errors_js_1 = require("./errors.js");
15
+ function randomId() {
16
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
17
+ return crypto.randomUUID();
18
+ }
19
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
20
+ }
21
+ class LocalLeaseProvider {
22
+ constructor(storage, logger = new core_1.NoopLogger(), deviceId = 'local') {
23
+ this.logger = logger;
24
+ this._initialized = false;
25
+ this.initializePromise = null;
26
+ this.mutationTail = Promise.resolve();
27
+ this.watermark = new watermark_js_1.WotsWatermarkStore(storage, logger);
28
+ this.leaseStore = new core_2.LeaseStore(storage, logger);
29
+ this.journal = new journal_js_1.LeaseJournal(storage, logger);
30
+ this.deviceId = deviceId;
31
+ this.cas = LocalLeaseProvider.detectCas(storage);
32
+ }
33
+ static detectCas(storage) {
34
+ const candidate = storage;
35
+ return typeof candidate.conditionalUpdate === 'function' ? candidate : undefined;
36
+ }
37
+ async initialize() {
38
+ if (this._initialized)
39
+ return;
40
+ if (!this.initializePromise) {
41
+ this.initializePromise = this.initializeOnce().catch((error) => {
42
+ this.initializePromise = null;
43
+ throw error;
44
+ });
45
+ }
46
+ await this.initializePromise;
47
+ }
48
+ async initializeOnce() {
49
+ await this.journal.initialize();
50
+ await this.watermark.initialize();
51
+ await this.leaseStore.initialize();
52
+ await this.reconcileDurableSafetyState();
53
+ await this.recoverExpiredReservations();
54
+ await this.burnUnresolvedReservations();
55
+ this._initialized = true;
56
+ }
57
+ /**
58
+ * Rebuild the no-reuse watermark from immutable journal history and leases.
59
+ * The journal is audit authority; the watermark remains allocation authority.
60
+ */
61
+ async reconcileDurableSafetyState() {
62
+ const entries = this.journal.getAll();
63
+ const latestBySlot = new Map();
64
+ const latestByReservation = new Map();
65
+ for (const entry of entries) {
66
+ latestBySlot.set(`${entry.treeId}:${entry.wotsIndex}`, entry);
67
+ if (entry.reservationId)
68
+ latestByReservation.set(entry.reservationId, entry);
69
+ }
70
+ for (const entry of latestBySlot.values()) {
71
+ await this.watermark.markUnavailable(entry.treeId, entry.indices, entry.status);
72
+ }
73
+ const leases = this.leaseStore.getAll();
74
+ const leaseIds = new Set(leases.map((lease) => lease.leaseId));
75
+ for (const entry of latestByReservation.values()) {
76
+ if (entry.status !== 'reserved' || leaseIds.has(entry.reservationId))
77
+ continue;
78
+ await this.journal.append({
79
+ ...entry,
80
+ status: 'burned',
81
+ timestamp: Date.now(),
82
+ });
83
+ await this.watermark.markUnavailable(entry.treeId, entry.indices, 'burned');
84
+ }
85
+ for (const lease of leases) {
86
+ const treeId = lease.treeId ?? 'default';
87
+ const latest = this.journal.getByReservation(lease.leaseId);
88
+ if (!latest) {
89
+ const status = lease.status === 'finalized'
90
+ ? 'committed'
91
+ : lease.status === 'cancelled'
92
+ ? 'burned'
93
+ : lease.status === 'expired'
94
+ ? 'reserved-expired'
95
+ : 'reserved';
96
+ await this.journal.append({
97
+ treeId,
98
+ branchId: 'default',
99
+ wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
100
+ indices: lease.indices,
101
+ status,
102
+ reservationId: lease.leaseId,
103
+ ...(lease.txId !== undefined ? { txId: lease.txId } : {}),
104
+ timestamp: lease.createdAt,
105
+ deviceId: this.deviceId,
106
+ });
107
+ await this.watermark.markUnavailable(treeId, lease.indices, status);
108
+ continue;
109
+ }
110
+ if (latest.status === 'committed' && lease.status !== 'finalized') {
111
+ await this.leaseStore.updateStatus(lease.leaseId, 'finalized');
112
+ }
113
+ else if (latest.status === 'burned' && lease.status !== 'cancelled') {
114
+ await this.leaseStore.updateStatus(lease.leaseId, 'cancelled');
115
+ }
116
+ else if (latest.status === 'reserved-expired' && lease.status !== 'expired') {
117
+ await this.leaseStore.updateStatus(lease.leaseId, 'expired');
118
+ }
119
+ else if (latest.status === 'reserved' && lease.status === 'finalized') {
120
+ await this.journal.append({
121
+ treeId,
122
+ branchId: 'default',
123
+ wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
124
+ indices: lease.indices,
125
+ status: 'committed',
126
+ reservationId: lease.leaseId,
127
+ ...(lease.txId !== undefined ? { txId: lease.txId } : {}),
128
+ timestamp: Date.now(),
129
+ deviceId: this.deviceId,
130
+ });
131
+ await this.watermark.markUnavailable(treeId, lease.indices, 'committed');
132
+ }
133
+ else if (latest.status === 'reserved' && lease.status === 'cancelled') {
134
+ await this.journal.append({
135
+ treeId,
136
+ branchId: 'default',
137
+ wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
138
+ indices: lease.indices,
139
+ status: 'burned',
140
+ reservationId: lease.leaseId,
141
+ timestamp: Date.now(),
142
+ deviceId: this.deviceId,
143
+ });
144
+ await this.watermark.markUnavailable(treeId, lease.indices, 'burned');
145
+ }
146
+ }
147
+ }
148
+ /** Burn all active (non-expired) leases that have no matching treeId.
149
+ * On restart, leases with missing treeId (from before the treeId field was added)
150
+ * must be burned to prevent index reuse. */
151
+ async burnUnresolvedReservations() {
152
+ const now = Date.now();
153
+ for (const lease of this.leaseStore.getAll()) {
154
+ if (lease.status === 'active' && lease.expiresAt >= now && !lease.treeId) {
155
+ this.logger.warn(`[LocalLeaseProvider] Burning unresolved lease ${lease.leaseId} (no treeId)`);
156
+ if (this.journal.getByReservation(lease.leaseId)?.status !== 'burned') {
157
+ await this.journal.append({
158
+ treeId: 'default',
159
+ branchId: 'default',
160
+ wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
161
+ indices: lease.indices,
162
+ status: 'burned',
163
+ reservationId: lease.leaseId,
164
+ timestamp: now,
165
+ deviceId: this.deviceId,
166
+ });
167
+ }
168
+ await this.watermark.markUnavailable('default', lease.indices, 'burned');
169
+ await this.leaseStore.updateStatus(lease.leaseId, 'cancelled');
170
+ }
171
+ }
172
+ }
173
+ async recoverExpiredReservations() {
174
+ const now = Date.now();
175
+ for (const lease of this.leaseStore.getAll()) {
176
+ if (lease.status === 'expired' || (lease.status === 'active' && lease.expiresAt < now)) {
177
+ const treeId = lease.treeId ?? 'default';
178
+ if (this.journal.getByReservation(lease.leaseId)?.status !== 'reserved-expired') {
179
+ await this.journal.append({
180
+ treeId,
181
+ branchId: 'default',
182
+ wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
183
+ indices: lease.indices,
184
+ status: 'reserved-expired',
185
+ reservationId: lease.leaseId,
186
+ timestamp: now,
187
+ deviceId: this.deviceId,
188
+ });
189
+ }
190
+ await this.watermark.markUnavailable(treeId, lease.indices, 'reserved-expired');
191
+ if (lease.status !== 'expired')
192
+ await this.leaseStore.updateStatus(lease.leaseId, 'expired');
193
+ }
194
+ }
195
+ }
196
+ async reserveKeyUse(params) {
197
+ return this.withMutationLock(async () => {
198
+ if (!this._initialized)
199
+ await this.initialize();
200
+ // Pick up any allocation made by another provider over the same storage
201
+ // before choosing a slot (AUD-004).
202
+ await this.watermark.refresh();
203
+ const indices = this.watermark.getNextIndices(params.treeId);
204
+ return this.reserveSpecificKeyUseUnlocked(params, indices);
205
+ });
206
+ }
207
+ /**
208
+ * Reserve a specific set of indices (used by quorum coordination so every
209
+ * peer attests to the same slot). Throws IndicesUnavailableError when the
210
+ * slot is already taken.
211
+ */
212
+ async reserveSpecificKeyUse(params, indices) {
213
+ return this.withMutationLock(async () => {
214
+ if (!this._initialized)
215
+ await this.initialize();
216
+ await this.watermark.refresh();
217
+ return this.reserveSpecificKeyUseUnlocked(params, indices);
218
+ });
219
+ }
220
+ async reserveSpecificKeyUseUnlocked(params, indices) {
221
+ if (!this._initialized)
222
+ await this.initialize();
223
+ const treeId = params.treeId;
224
+ const ttlMs = params.ttlMs ?? 120000;
225
+ if (this.watermark.isUnavailable(treeId, indices)) {
226
+ throw new errors_js_1.IndicesUnavailableError(treeId, indices);
227
+ }
228
+ // Validate device range BEFORE marking unavailable — prevents permanent index loss
229
+ if (params.deviceId) {
230
+ const range = this.getDeviceRange(params.deviceId);
231
+ if (range && (indices.addressIndex < range.start || indices.addressIndex > range.end)) {
232
+ throw new errors_js_1.DeviceRangeViolationError(indices.addressIndex, range.start, range.end);
233
+ }
234
+ }
235
+ const reservationId = randomId();
236
+ const expiresAt = Date.now() + ttlMs;
237
+ // Cross-instance CAS claim (AUD-004): on a durable medium shared by two
238
+ // provider instances over distinct adapter objects, refresh() above can
239
+ // race and both pick the same slot. Claim it atomically first; a lost race
240
+ // means another instance owns it, so the slot is not re-allocated.
241
+ if (this.cas) {
242
+ const claimKey = `totem_wots_claim:${treeId}:${(0, watermark_js_1.flatIndex)(indices)}`;
243
+ const claim = await this.cas.conditionalUpdate(claimKey, (current) => current
244
+ ? { abort: 'slot already claimed' }
245
+ : {
246
+ next: {
247
+ reservationId,
248
+ ...(params.deviceId !== undefined ? { deviceId: params.deviceId } : {}),
249
+ claimedAt: Date.now(),
250
+ },
251
+ });
252
+ if (!claim.applied) {
253
+ throw new errors_js_1.IndicesUnavailableError(treeId, indices);
254
+ }
255
+ }
256
+ // Write-ahead safety record: once this succeeds, recovery will never
257
+ // re-expose the index even if a later snapshot write fails.
258
+ await this.journal.append({
259
+ treeId,
260
+ branchId: params.branchId ?? 'default',
261
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
262
+ indices,
263
+ status: 'reserved',
264
+ reservationId,
265
+ payloadHash: params.payloadHash,
266
+ timestamp: Date.now(),
267
+ deviceId: params.deviceId ?? this.deviceId,
268
+ });
269
+ await this.watermark.markUnavailable(treeId, indices, 'reserved');
270
+ const lease = {
271
+ leaseId: reservationId,
272
+ leaseToken: reservationId,
273
+ treeId,
274
+ indices,
275
+ expiresAt,
276
+ status: 'active',
277
+ createdAt: Date.now(),
278
+ leaseTTL: ttlMs,
279
+ };
280
+ await this.leaseStore.save(lease);
281
+ return { reservationId, indices, expiresAt };
282
+ }
283
+ withMutationLock(operation) {
284
+ const run = this.mutationTail.then(operation, operation);
285
+ this.mutationTail = run.then(() => undefined, () => undefined);
286
+ return run;
287
+ }
288
+ async commitKeyUse(reservationId, txId) {
289
+ return this.withMutationLock(() => this.commitKeyUseUnlocked(reservationId, txId));
290
+ }
291
+ async commitKeyUseUnlocked(reservationId, txId) {
292
+ if (!this._initialized)
293
+ await this.initialize();
294
+ await this.watermark.refresh();
295
+ const lease = this.leaseStore.get(reservationId);
296
+ if (!lease)
297
+ throw new errors_js_1.LeaseNotFoundError(reservationId);
298
+ const treeId = lease.treeId ?? 'default';
299
+ const indices = lease.indices;
300
+ const latest = this.journal.getByReservation(reservationId);
301
+ if (latest?.status === 'committed') {
302
+ if (latest.txId !== txId) {
303
+ throw new errors_js_1.InvalidLeaseTransitionError(reservationId, 'committed', 'committed');
304
+ }
305
+ await this.watermark.markUnavailable(treeId, indices, 'committed');
306
+ if (lease.status !== 'finalized')
307
+ await this.leaseStore.updateStatus(reservationId, 'finalized');
308
+ return;
309
+ }
310
+ if (latest?.status === 'burned' || latest?.status === 'reserved-expired'
311
+ || lease.status === 'cancelled' || lease.status === 'expired') {
312
+ throw new errors_js_1.InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'committed');
313
+ }
314
+ await this.journal.append({
315
+ treeId,
316
+ branchId: 'default',
317
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
318
+ indices,
319
+ status: 'committed',
320
+ reservationId,
321
+ txId,
322
+ timestamp: Date.now(),
323
+ deviceId: this.deviceId,
324
+ });
325
+ await this.watermark.markUnavailable(treeId, indices, 'committed');
326
+ await this.leaseStore.updateStatus(reservationId, 'finalized');
327
+ }
328
+ async burnReservation(reservationId, reason) {
329
+ return this.withMutationLock(() => this.burnReservationUnlocked(reservationId, reason));
330
+ }
331
+ async burnReservationUnlocked(reservationId, reason) {
332
+ if (!this._initialized)
333
+ await this.initialize();
334
+ await this.watermark.refresh();
335
+ const lease = this.leaseStore.get(reservationId);
336
+ if (!lease)
337
+ throw new errors_js_1.LeaseNotFoundError(reservationId);
338
+ const treeId = lease.treeId ?? 'default';
339
+ const indices = lease.indices;
340
+ const latest = this.journal.getByReservation(reservationId);
341
+ if (latest?.status === 'burned') {
342
+ await this.watermark.markUnavailable(treeId, indices, 'burned');
343
+ if (lease.status !== 'cancelled')
344
+ await this.leaseStore.updateStatus(reservationId, 'cancelled');
345
+ return;
346
+ }
347
+ if (latest?.status === 'committed' || latest?.status === 'reserved-expired'
348
+ || lease.status === 'finalized' || lease.status === 'expired') {
349
+ throw new errors_js_1.InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'burned');
350
+ }
351
+ await this.journal.append({
352
+ treeId,
353
+ branchId: 'default',
354
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
355
+ indices,
356
+ status: 'burned',
357
+ reservationId,
358
+ timestamp: Date.now(),
359
+ deviceId: this.deviceId,
360
+ });
361
+ await this.watermark.markUnavailable(treeId, indices, 'burned');
362
+ await this.leaseStore.updateStatus(reservationId, 'cancelled');
363
+ this.logger.warn(`[LocalLeaseProvider] Burning reservation ${reservationId}: ${reason}`);
364
+ }
365
+ async getLocalWatermark(treeId) {
366
+ if (!this._initialized)
367
+ await this.initialize();
368
+ return this.watermark.getLocalWatermark(treeId);
369
+ }
370
+ /**
371
+ * Export a portable watermark seed for restore (RFC-013 §9).
372
+ */
373
+ async exportWatermark(treeId) {
374
+ if (!this._initialized)
375
+ await this.initialize();
376
+ return this.watermark.exportSeed(treeId);
377
+ }
378
+ /**
379
+ * Seed the local watermark from an exported/operator/on-chain snapshot.
380
+ * Forward-only by default; refuses to start below the true high-water mark.
381
+ */
382
+ async seedWatermark(seed, options) {
383
+ return this.withMutationLock(async () => {
384
+ if (!this._initialized)
385
+ await this.initialize();
386
+ await this.watermark.refresh();
387
+ await this.watermark.seed(seed, options);
388
+ });
389
+ }
390
+ /** List all tree IDs known to the local watermark store. */
391
+ listTrees() {
392
+ return Object.keys(this.watermark.getRawState().trees);
393
+ }
394
+ /** Expose the journal for quorum/on-chain providers to merge remote entries. */
395
+ getJournal() {
396
+ return this.journal;
397
+ }
398
+ /**
399
+ * Advance the local watermark to a remote cursor (monotonic merge).
400
+ * Used by quorum sync and the lookup-node LeaseCoordinator when a peer
401
+ * publishes a watermark ahead of ours. Returns false when the remote
402
+ * cursor is behind (no-op).
403
+ */
404
+ async advanceToRemoteWatermark(treeId, remote) {
405
+ if (!this._initialized)
406
+ await this.initialize();
407
+ const local = this.watermark.getLocalWatermark(treeId);
408
+ const localFlat = (0, watermark_js_1.flatIndex)({ addressIndex: local.addressCursor, l1: local.l1Cursor, l2: local.l2Cursor });
409
+ const remoteFlat = (0, watermark_js_1.flatIndex)({ addressIndex: remote.addressCursor, l1: remote.l1Cursor, l2: remote.l2Cursor });
410
+ if (remoteFlat <= localFlat)
411
+ return false;
412
+ await this.watermark.save(treeId, {
413
+ addressCursor: remote.addressCursor,
414
+ l1Cursor: remote.l1Cursor,
415
+ l2Cursor: remote.l2Cursor,
416
+ lastSyncTimestamp: Date.now(),
417
+ });
418
+ return true;
419
+ }
420
+ async publishWatermark(_treeId) {
421
+ // Layer 1: no-op
422
+ }
423
+ async syncLeaseJournal() {
424
+ // Layer 1: no-op
425
+ return { synced: true, conflicts: [] };
426
+ }
427
+ async verifyLeaseCertificate(cert) {
428
+ if (cert === undefined)
429
+ return true;
430
+ return false;
431
+ }
432
+ getDeviceRange(deviceId) {
433
+ const match = deviceId.match(/^device-?(\d+)$/i);
434
+ if (!match)
435
+ return null;
436
+ const slot = parseInt(match[1], 10);
437
+ return { start: slot * 8, end: slot * 8 + 7 };
438
+ }
439
+ }
440
+ exports.LocalLeaseProvider = LocalLeaseProvider;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * OnchainWatermarkProvider — Layer 5: on-chain watermark anchoring.
3
+ *
4
+ * Safety model
5
+ * ────────────
6
+ * The local provider remains the authoritative watermark. This provider adds
7
+ * a verifiable public record: a dedicated watermark coin whose STATE(0) holds
8
+ * the flat watermark cursor. Every publish spends the coin back to itself
9
+ * with an advanced cursor, so any third party can read the on-chain state and
10
+ * prove that a given signing slot was consumed — without trusting this device.
11
+ *
12
+ * The watermark coin script is `RETURN SIGNEDBY(0x<digest>)` — the same
13
+ * single-sig pattern used by statechain reclaim addresses. The signer is the
14
+ * WOTS key owner; the digest is the 32-byte public key digest.
15
+ *
16
+ * Failure semantics
17
+ * ─────────────────
18
+ * - publishWatermark fails loudly (OnchainWatermarkError) when the chain
19
+ * rejects the TX — the local watermark is never rolled back.
20
+ * - syncLeaseJournal merges the on-chain cursor into the local watermark
21
+ * monotonically (remote ahead → advance; remote behind → conflict record).
22
+ */
23
+ import type { WotsLeaseProvider, ReserveParams, LeaseReservation, LeaseCertificate, LocalWatermark, SyncResult, OnchainWatermarkProviderConfig } from './types.js';
24
+ export declare class OnchainWatermarkProvider implements WotsLeaseProvider {
25
+ private readonly chain;
26
+ private readonly initialWatermarkCoinId;
27
+ private currentCoinId;
28
+ private readonly watermarkAddress;
29
+ private readonly tokenId;
30
+ private readonly amount;
31
+ private readonly local;
32
+ private readonly signer;
33
+ private readonly statePort;
34
+ private readonly minBlocksBetweenPublishes;
35
+ private readonly storage?;
36
+ private _initialized;
37
+ private _lastPublishBlock;
38
+ constructor(config: OnchainWatermarkProviderConfig);
39
+ initialize(): Promise<void>;
40
+ private ensureInit;
41
+ reserveKeyUse(params: ReserveParams): Promise<LeaseReservation>;
42
+ commitKeyUse(reservationId: string, txId: string): Promise<void>;
43
+ burnReservation(reservationId: string, reason: string): Promise<void>;
44
+ getLocalWatermark(treeId: string): Promise<LocalWatermark>;
45
+ /**
46
+ * Publish the local watermark cursor on-chain by spending the watermark
47
+ * coin back to itself with STATE(statePort) = flat cursor.
48
+ *
49
+ * Rate-limited by minBlocksBetweenPublishes using the chain tip.
50
+ */
51
+ publishWatermark(treeId: string): Promise<void>;
52
+ syncLeaseJournal(): Promise<SyncResult>;
53
+ verifyLeaseCertificate(cert?: LeaseCertificate): Promise<boolean>;
54
+ }