@totemsdk/wots-lease 1.0.0 → 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 (67) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +60 -0
  3. package/dist/axia.d.ts +1 -1
  4. package/dist/axia.js +41 -28
  5. package/dist/certificate.d.ts +37 -0
  6. package/dist/certificate.js +69 -0
  7. package/dist/cjs/axia.d.ts +33 -0
  8. package/dist/cjs/axia.js +145 -0
  9. package/dist/cjs/certificate.d.ts +37 -0
  10. package/dist/cjs/certificate.js +74 -0
  11. package/dist/cjs/device.d.ts +18 -0
  12. package/dist/cjs/device.js +34 -0
  13. package/dist/cjs/errors.d.ts +71 -0
  14. package/dist/cjs/errors.js +135 -0
  15. package/dist/cjs/hybrid.d.ts +37 -0
  16. package/dist/cjs/hybrid.js +145 -0
  17. package/dist/cjs/index.d.ts +17 -0
  18. package/dist/cjs/index.js +45 -0
  19. package/dist/cjs/journal.d.ts +30 -0
  20. package/dist/cjs/journal.js +369 -0
  21. package/dist/cjs/local.d.ts +83 -0
  22. package/dist/cjs/local.js +440 -0
  23. package/dist/cjs/onchain.d.ts +54 -0
  24. package/dist/cjs/onchain.js +314 -0
  25. package/dist/cjs/package.json +1 -0
  26. package/dist/cjs/quorum.d.ts +75 -0
  27. package/dist/cjs/quorum.js +307 -0
  28. package/dist/cjs/replicated-backup.d.ts +30 -0
  29. package/dist/cjs/replicated-backup.js +52 -0
  30. package/dist/cjs/seed.d.ts +41 -0
  31. package/dist/cjs/seed.js +84 -0
  32. package/dist/cjs/stubs.d.ts +50 -0
  33. package/dist/cjs/stubs.js +125 -0
  34. package/dist/cjs/types.d.ts +272 -0
  35. package/dist/cjs/types.js +5 -0
  36. package/dist/cjs/watermark.d.ts +51 -0
  37. package/dist/cjs/watermark.js +328 -0
  38. package/dist/device.js +2 -6
  39. package/dist/errors.d.ts +26 -0
  40. package/dist/errors.js +49 -25
  41. package/dist/hybrid.d.ts +4 -1
  42. package/dist/hybrid.js +72 -26
  43. package/dist/index.d.ts +6 -2
  44. package/dist/index.js +12 -38
  45. package/dist/journal.d.ts +14 -4
  46. package/dist/journal.js +327 -49
  47. package/dist/local.d.ts +31 -1
  48. package/dist/local.js +267 -68
  49. package/dist/onchain.d.ts +3 -1
  50. package/dist/onchain.js +63 -28
  51. package/dist/quorum.d.ts +15 -0
  52. package/dist/quorum.js +54 -15
  53. package/dist/replicated-backup.d.ts +30 -0
  54. package/dist/replicated-backup.js +48 -0
  55. package/dist/seed.d.ts +41 -0
  56. package/dist/seed.js +80 -0
  57. package/dist/stubs.d.ts +2 -0
  58. package/dist/stubs.js +20 -10
  59. package/dist/types.d.ts +74 -1
  60. package/dist/types.js +1 -2
  61. package/dist/watermark.d.ts +22 -1
  62. package/dist/watermark.js +133 -16
  63. package/package.json +25 -15
  64. package/rust/pkg/package.json +3 -3
  65. package/rust/pkg/wots_lease_wasm_bg.wasm +0 -0
  66. package/rust/pkg-node/package.json +3 -3
  67. package/rust/pkg-node/wots_lease_wasm_bg.wasm +0 -0
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * AxiaLeaseProvider — wraps the Axia API's /v1/wots-hardened endpoints.
4
+ *
5
+ * Callers do NOT need to call /finalize separately — commitKeyUse() does it.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.AxiaLeaseProvider = void 0;
9
+ const core_1 = require("@totemsdk/core");
10
+ const core_2 = require("@totemsdk/core");
11
+ const watermark_js_1 = require("./watermark.js");
12
+ const journal_js_1 = require("./journal.js");
13
+ const errors_js_1 = require("./errors.js");
14
+ const errors_1 = require("@totemsdk/storage/errors");
15
+ class AxiaLeaseProvider {
16
+ constructor(config) {
17
+ this._initialized = false;
18
+ this.apiUrl = config.apiUrl;
19
+ this.apiKey = config.apiKey;
20
+ this.rootPublicKey = config.rootPublicKey;
21
+ this.logger = config.logger ?? new core_1.NoopLogger();
22
+ this.watermark = new watermark_js_1.WotsWatermarkStore(config.storage, this.logger);
23
+ this.leaseStore = new core_1.LeaseStore(config.storage, this.logger);
24
+ this.journal = new journal_js_1.LeaseJournal(config.storage, this.logger);
25
+ }
26
+ async initialize() {
27
+ if (this._initialized)
28
+ return;
29
+ await this.watermark.initialize();
30
+ await this.leaseStore.initialize();
31
+ await this.journal.initialize();
32
+ this._initialized = true;
33
+ }
34
+ async reserveKeyUse(params) {
35
+ if (!this._initialized)
36
+ await this.initialize();
37
+ const txId = params.payloadHash ?? `tx-${Date.now()}`;
38
+ const resp = await (0, core_2.prepareLease)(this.apiUrl, this.apiKey, {
39
+ txId,
40
+ rootPublicKey: this.rootPublicKey,
41
+ to: params.treeId,
42
+ amount: params.valueHint ?? '0',
43
+ ttlMs: params.ttlMs ?? 120000,
44
+ });
45
+ const indices = resp.lease;
46
+ // RFC-020 C2: never accept an index this tree has already consumed, even if
47
+ // a replaying/hostile lease server hands the same slot out again.
48
+ await this.watermark.refresh();
49
+ if (this.watermark.isUnavailable(params.treeId, indices)) {
50
+ throw new errors_js_1.IndicesUnavailableError(params.treeId, indices);
51
+ }
52
+ const expiresAt = Date.now() + (params.ttlMs ?? 120000);
53
+ const reservationId = resp.txId;
54
+ const lease = {
55
+ leaseId: reservationId,
56
+ leaseToken: resp.leaseToken,
57
+ treeId: params.treeId,
58
+ indices,
59
+ expiresAt,
60
+ status: 'active',
61
+ createdAt: Date.now(),
62
+ leaseTTL: params.ttlMs ?? 120000,
63
+ };
64
+ await this.leaseStore.save(lease);
65
+ await this.journal.append({
66
+ treeId: params.treeId,
67
+ branchId: params.branchId ?? 'default',
68
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
69
+ indices,
70
+ status: 'reserved',
71
+ payloadHash: params.payloadHash,
72
+ timestamp: Date.now(),
73
+ deviceId: params.deviceId ?? 'axia',
74
+ });
75
+ return { reservationId, indices, expiresAt, leaseToken: resp.leaseToken };
76
+ }
77
+ async commitKeyUse(reservationId, txId) {
78
+ if (!this._initialized)
79
+ await this.initialize();
80
+ const lease = this.leaseStore.get(reservationId);
81
+ if (!lease)
82
+ throw new errors_js_1.LeaseNotFoundError(reservationId);
83
+ await (0, core_2.finalizeLease)(this.apiUrl, this.apiKey, lease.leaseToken, txId);
84
+ const indices = lease.indices;
85
+ // RFC-020 C2: key the watermark/journal by the real treeId, never the
86
+ // reservation id.
87
+ const treeId = lease.treeId;
88
+ if (!treeId) {
89
+ throw new errors_1.StorageError(`AxiaLeaseProvider: lease ${reservationId} has no treeId`, 'corrupt');
90
+ }
91
+ await this.watermark.markUnavailable(treeId, indices, 'committed');
92
+ await this.leaseStore.updateStatus(reservationId, 'finalized');
93
+ await this.journal.append({
94
+ treeId,
95
+ branchId: 'default',
96
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
97
+ indices,
98
+ status: 'committed',
99
+ txId,
100
+ timestamp: Date.now(),
101
+ deviceId: 'axia',
102
+ });
103
+ }
104
+ async burnReservation(reservationId, reason) {
105
+ if (!this._initialized)
106
+ await this.initialize();
107
+ const lease = this.leaseStore.get(reservationId);
108
+ if (!lease)
109
+ throw new errors_js_1.LeaseNotFoundError(reservationId);
110
+ const indices = lease.indices;
111
+ const treeId = lease.treeId;
112
+ if (!treeId) {
113
+ throw new errors_1.StorageError(`AxiaLeaseProvider: lease ${reservationId} has no treeId`, 'corrupt');
114
+ }
115
+ await this.watermark.markUnavailable(treeId, indices, 'burned');
116
+ await this.leaseStore.updateStatus(reservationId, 'cancelled');
117
+ this.logger.warn(`[AxiaLeaseProvider] Burning reservation ${reservationId}: ${reason}`);
118
+ await this.journal.append({
119
+ treeId,
120
+ branchId: 'default',
121
+ wotsIndex: (0, watermark_js_1.flatIndex)(indices),
122
+ indices,
123
+ status: 'burned',
124
+ timestamp: Date.now(),
125
+ deviceId: 'axia',
126
+ });
127
+ }
128
+ async getLocalWatermark(treeId) {
129
+ if (!this._initialized)
130
+ await this.initialize();
131
+ return this.watermark.getLocalWatermark(treeId);
132
+ }
133
+ async publishWatermark(_treeId) {
134
+ // Server maintains its own watermark via the lease API
135
+ }
136
+ async syncLeaseJournal() {
137
+ return { synced: true, conflicts: [] };
138
+ }
139
+ async verifyLeaseCertificate(_cert) {
140
+ // RFC-020 C2: the Axia API does not return a server-signed certificate we
141
+ // can verify, so fail closed rather than asserting a trust we cannot prove.
142
+ return false;
143
+ }
144
+ }
145
+ exports.AxiaLeaseProvider = AxiaLeaseProvider;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared helpers for authenticating lease certificates.
3
+ *
4
+ * A certificate is authenticated by a real signature over a canonical,
5
+ * deterministic byte encoding of its content. Providers that hold an issuing
6
+ * identity (`certificateSigner`, or the watermark coin owner on Layer 5) sign
7
+ * the payload before returning the certificate. Verification FAILS CLOSED:
8
+ * a certificate is rejected unless the caller supplies a `verify()` function
9
+ * and the signature cryptographically verifies against it. An empty signature,
10
+ * an absent verifier, or an identity without a verifier are all rejections —
11
+ * `issuedBy` alone is never treated as authenticity evidence.
12
+ */
13
+ import type { CertificateSigner, LeaseCertificate } from './types.js';
14
+ /**
15
+ * Canonical byte encoding of a certificate's bindable content.
16
+ *
17
+ * Any change to a bound field changes the message, so a certificate cannot
18
+ * be re-targeted at a different reservation, device, purpose, or expiry.
19
+ */
20
+ export declare function canonicalCertificateMessage(cert: Pick<LeaseCertificate, 'treeId' | 'branchId' | 'deviceId' | 'indices' | 'purpose' | 'payloadHash' | 'reservationId' | 'issuedBy' | 'issuedAt' | 'expiresAt'>): Uint8Array;
21
+ /** Sign a certificate via the issuer identity, returning hex of the signature. */
22
+ export declare function signCertificate(signer: {
23
+ sign(message: Uint8Array): Promise<Uint8Array>;
24
+ }, cert: Parameters<typeof canonicalCertificateMessage>[0]): Promise<string>;
25
+ /**
26
+ * Verify a certificate's signature.
27
+ *
28
+ * Fails closed (AUD-016):
29
+ * - Empty/malformed signature -> false (an unsigned certificate is not
30
+ * authenticity evidence regardless of what issuedBy claims).
31
+ * - No `signer`, or a `signer` without a `verify()` function -> false. Knowing
32
+ * only an identity (e.g. a public key digest) cannot authenticate a
33
+ * signature; a verifier is required.
34
+ * - Otherwise the signature must cryptographically verify against the
35
+ * supplied verifier.
36
+ */
37
+ export declare function certificateSignatureVerified(cert: LeaseCertificate, signer?: CertificateSigner): Promise<boolean>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Shared helpers for authenticating lease certificates.
4
+ *
5
+ * A certificate is authenticated by a real signature over a canonical,
6
+ * deterministic byte encoding of its content. Providers that hold an issuing
7
+ * identity (`certificateSigner`, or the watermark coin owner on Layer 5) sign
8
+ * the payload before returning the certificate. Verification FAILS CLOSED:
9
+ * a certificate is rejected unless the caller supplies a `verify()` function
10
+ * and the signature cryptographically verifies against it. An empty signature,
11
+ * an absent verifier, or an identity without a verifier are all rejections —
12
+ * `issuedBy` alone is never treated as authenticity evidence.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.canonicalCertificateMessage = canonicalCertificateMessage;
16
+ exports.signCertificate = signCertificate;
17
+ exports.certificateSignatureVerified = certificateSignatureVerified;
18
+ const core_1 = require("@totemsdk/core");
19
+ /**
20
+ * Canonical byte encoding of a certificate's bindable content.
21
+ *
22
+ * Any change to a bound field changes the message, so a certificate cannot
23
+ * be re-targeted at a different reservation, device, purpose, or expiry.
24
+ */
25
+ function canonicalCertificateMessage(cert) {
26
+ const encoded = new TextEncoder().encode([
27
+ 'TOTEM-LEASECERT',
28
+ 'v1',
29
+ cert.issuedBy ?? '',
30
+ cert.treeId ?? '',
31
+ cert.branchId ?? '',
32
+ cert.deviceId ?? '',
33
+ cert.reservationId ?? '',
34
+ String(cert.indices?.addressIndex ?? 0),
35
+ String(cert.indices?.l1 ?? 0),
36
+ String(cert.indices?.l2 ?? 0),
37
+ cert.purpose ?? '',
38
+ cert.payloadHash ?? '',
39
+ String(cert.issuedAt ?? 0),
40
+ String(cert.expiresAt ?? 0),
41
+ ].join('\x1f'));
42
+ return (0, core_1.sha3_256)(encoded);
43
+ }
44
+ /** Sign a certificate via the issuer identity, returning hex of the signature. */
45
+ async function signCertificate(signer, cert) {
46
+ return (0, core_1.bytesToHex)(await signer.sign(canonicalCertificateMessage(cert)));
47
+ }
48
+ /**
49
+ * Verify a certificate's signature.
50
+ *
51
+ * Fails closed (AUD-016):
52
+ * - Empty/malformed signature -> false (an unsigned certificate is not
53
+ * authenticity evidence regardless of what issuedBy claims).
54
+ * - No `signer`, or a `signer` without a `verify()` function -> false. Knowing
55
+ * only an identity (e.g. a public key digest) cannot authenticate a
56
+ * signature; a verifier is required.
57
+ * - Otherwise the signature must cryptographically verify against the
58
+ * supplied verifier.
59
+ */
60
+ async function certificateSignatureVerified(cert, signer) {
61
+ if (typeof cert.signature !== 'string' || cert.signature.length === 0) {
62
+ return false;
63
+ }
64
+ if (!signer?.verify)
65
+ return false;
66
+ let sig;
67
+ try {
68
+ sig = (0, core_1.hexToBytes)(cert.signature);
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ return signer.verify(canonicalCertificateMessage(cert), sig);
74
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Device keyspace partitioning — Layer 2.
3
+ *
4
+ * Deterministic address-index range assignment per device slot.
5
+ * Does NOT accept a raw seed; the caller derives the TreeKey in @totemsdk/core.
6
+ *
7
+ * Layout (8 device slots × 8 addresses each = 64 total):
8
+ * device 0 → addressIndex 0–7
9
+ * device 1 → addressIndex 8–15
10
+ * device 2 → addressIndex 16–23
11
+ * ...
12
+ */
13
+ import type { DeviceKeyRange } from './types.js';
14
+ export declare function allocateDeviceRange(params: {
15
+ deviceSlot: number;
16
+ deviceId: string;
17
+ }): DeviceKeyRange;
18
+ export declare function deviceSlotForAddressIndex(addressIndex: number): number;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Device keyspace partitioning — Layer 2.
4
+ *
5
+ * Deterministic address-index range assignment per device slot.
6
+ * Does NOT accept a raw seed; the caller derives the TreeKey in @totemsdk/core.
7
+ *
8
+ * Layout (8 device slots × 8 addresses each = 64 total):
9
+ * device 0 → addressIndex 0–7
10
+ * device 1 → addressIndex 8–15
11
+ * device 2 → addressIndex 16–23
12
+ * ...
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.allocateDeviceRange = allocateDeviceRange;
16
+ exports.deviceSlotForAddressIndex = deviceSlotForAddressIndex;
17
+ const ADDRESSES_PER_DEVICE = 8;
18
+ const MAX_DEVICE_SLOTS = 8;
19
+ function allocateDeviceRange(params) {
20
+ if (params.deviceSlot < 0 || params.deviceSlot >= MAX_DEVICE_SLOTS) {
21
+ throw new RangeError(`Device slot must be 0–${MAX_DEVICE_SLOTS - 1}, got ${params.deviceSlot}`);
22
+ }
23
+ const startAddressIndex = params.deviceSlot * ADDRESSES_PER_DEVICE;
24
+ const endAddressIndex = startAddressIndex + ADDRESSES_PER_DEVICE - 1;
25
+ return {
26
+ deviceId: params.deviceId,
27
+ startAddressIndex,
28
+ endAddressIndex,
29
+ addressCount: ADDRESSES_PER_DEVICE,
30
+ };
31
+ }
32
+ function deviceSlotForAddressIndex(addressIndex) {
33
+ return Math.floor(addressIndex / ADDRESSES_PER_DEVICE);
34
+ }
@@ -0,0 +1,71 @@
1
+ import type { SigningIndices } from './types.js';
2
+ export declare class WatermarkMonotonicityError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /**
6
+ * Raised when seeding would move the local watermark cursor backwards. This is
7
+ * the fail-closed guard against restoring a wallet below its true high-water
8
+ * mark and reusing a WOTS leaf (RFC-013 §9).
9
+ */
10
+ export declare class WatermarkSeedRegressionError extends Error {
11
+ readonly treeId: string;
12
+ readonly currentCursor: number;
13
+ readonly seedCursor: number;
14
+ constructor(treeId: string, currentCursor: number, seedCursor: number);
15
+ }
16
+ /**
17
+ * Raised when a restore/import has no usable watermark source and the caller has
18
+ * not declared the keyspace fresh. Failing closed is required: starting at 0
19
+ * would reissue spent leaves (RFC-013 §9).
20
+ */
21
+ export declare class WatermarkSeedRequiredError extends Error {
22
+ readonly treeId: string;
23
+ constructor(treeId: string);
24
+ }
25
+ export declare class WatermarkExhaustedError extends Error {
26
+ readonly treeId: string;
27
+ constructor(treeId: string);
28
+ }
29
+ export declare class LeaseNotFoundError extends Error {
30
+ readonly reservationId: string;
31
+ constructor(reservationId: string);
32
+ }
33
+ export declare class InvalidLeaseTransitionError extends Error {
34
+ readonly reservationId: string;
35
+ readonly currentStatus: string;
36
+ readonly requestedStatus: string;
37
+ constructor(reservationId: string, currentStatus: string, requestedStatus: string);
38
+ }
39
+ export declare class IndicesUnavailableError extends Error {
40
+ readonly treeId: string;
41
+ readonly indices: SigningIndices;
42
+ constructor(treeId: string, indices: SigningIndices);
43
+ }
44
+ export declare class PersonalLeaseNodeNotConfiguredError extends Error {
45
+ constructor();
46
+ }
47
+ export declare class P2PQuorumNotImplementedError extends Error {
48
+ constructor();
49
+ }
50
+ export declare class OnchainWatermarkNotImplementedError extends Error {
51
+ constructor();
52
+ }
53
+ export declare class QuorumUnavailableError extends Error {
54
+ readonly required: number;
55
+ readonly available: number;
56
+ constructor(required: number, available: number);
57
+ }
58
+ export declare class QuorumConflictError extends Error {
59
+ readonly treeId: string;
60
+ readonly indices: SigningIndices;
61
+ constructor(treeId: string, indices: SigningIndices);
62
+ }
63
+ export declare class OnchainWatermarkError extends Error {
64
+ constructor(message: string);
65
+ }
66
+ export declare class DeviceRangeViolationError extends Error {
67
+ readonly addressIndex: number;
68
+ readonly allowedStart: number;
69
+ readonly allowedEnd: number;
70
+ constructor(addressIndex: number, allowedStart: number, allowedEnd: number);
71
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.InvalidLeaseTransitionError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkSeedRequiredError = exports.WatermarkSeedRegressionError = exports.WatermarkMonotonicityError = void 0;
4
+ class WatermarkMonotonicityError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'WatermarkMonotonicityError';
8
+ }
9
+ }
10
+ exports.WatermarkMonotonicityError = WatermarkMonotonicityError;
11
+ /**
12
+ * Raised when seeding would move the local watermark cursor backwards. This is
13
+ * the fail-closed guard against restoring a wallet below its true high-water
14
+ * mark and reusing a WOTS leaf (RFC-013 §9).
15
+ */
16
+ class WatermarkSeedRegressionError extends Error {
17
+ constructor(treeId, currentCursor, seedCursor) {
18
+ super(`Refusing to seed watermark for ${treeId} below the current cursor ` +
19
+ `(${seedCursor} < ${currentCursor}); this would re-expose used WOTS leaves.`);
20
+ this.treeId = treeId;
21
+ this.currentCursor = currentCursor;
22
+ this.seedCursor = seedCursor;
23
+ this.name = 'WatermarkSeedRegressionError';
24
+ }
25
+ }
26
+ exports.WatermarkSeedRegressionError = WatermarkSeedRegressionError;
27
+ /**
28
+ * Raised when a restore/import has no usable watermark source and the caller has
29
+ * not declared the keyspace fresh. Failing closed is required: starting at 0
30
+ * would reissue spent leaves (RFC-013 §9).
31
+ */
32
+ class WatermarkSeedRequiredError extends Error {
33
+ constructor(treeId) {
34
+ super(`No watermark source available for ${treeId}. Provide an on-chain cursor, an ` +
35
+ 'exported watermark, or an operator high-water mark — or declare the keyspace fresh. ' +
36
+ 'Refusing to start at 0.');
37
+ this.treeId = treeId;
38
+ this.name = 'WatermarkSeedRequiredError';
39
+ }
40
+ }
41
+ exports.WatermarkSeedRequiredError = WatermarkSeedRequiredError;
42
+ class WatermarkExhaustedError extends Error {
43
+ constructor(treeId) {
44
+ super(`WOTS keyspace exhausted for tree: ${treeId}`);
45
+ this.treeId = treeId;
46
+ this.name = 'WatermarkExhaustedError';
47
+ }
48
+ }
49
+ exports.WatermarkExhaustedError = WatermarkExhaustedError;
50
+ class LeaseNotFoundError extends Error {
51
+ constructor(reservationId) {
52
+ super(`Lease reservation not found: ${reservationId}`);
53
+ this.reservationId = reservationId;
54
+ this.name = 'LeaseNotFoundError';
55
+ }
56
+ }
57
+ exports.LeaseNotFoundError = LeaseNotFoundError;
58
+ class InvalidLeaseTransitionError extends Error {
59
+ constructor(reservationId, currentStatus, requestedStatus) {
60
+ super(`Cannot transition lease ${reservationId} from ${currentStatus} to ${requestedStatus}`);
61
+ this.reservationId = reservationId;
62
+ this.currentStatus = currentStatus;
63
+ this.requestedStatus = requestedStatus;
64
+ this.name = 'InvalidLeaseTransitionError';
65
+ }
66
+ }
67
+ exports.InvalidLeaseTransitionError = InvalidLeaseTransitionError;
68
+ class IndicesUnavailableError extends Error {
69
+ constructor(treeId, indices) {
70
+ super(`Indices (${indices.addressIndex}, ${indices.l1}, ${indices.l2}) are already unavailable for tree: ${treeId}`);
71
+ this.treeId = treeId;
72
+ this.indices = indices;
73
+ this.name = 'IndicesUnavailableError';
74
+ }
75
+ }
76
+ exports.IndicesUnavailableError = IndicesUnavailableError;
77
+ class PersonalLeaseNodeNotConfiguredError extends Error {
78
+ constructor() {
79
+ super('PersonalLeaseNodeProvider requires a running lookup node. ' +
80
+ 'Deploy @totemsdk/lookup-node and configure { nodeUrl, nodePubkey }.');
81
+ this.name = 'PersonalLeaseNodeNotConfiguredError';
82
+ }
83
+ }
84
+ exports.PersonalLeaseNodeNotConfiguredError = PersonalLeaseNodeNotConfiguredError;
85
+ class P2PQuorumNotImplementedError extends Error {
86
+ constructor() {
87
+ super('P2PQuorumLeaseProvider is not yet implemented.');
88
+ this.name = 'P2PQuorumNotImplementedError';
89
+ }
90
+ }
91
+ exports.P2PQuorumNotImplementedError = P2PQuorumNotImplementedError;
92
+ class OnchainWatermarkNotImplementedError extends Error {
93
+ constructor() {
94
+ super('OnchainWatermarkProvider is not yet implemented.');
95
+ this.name = 'OnchainWatermarkNotImplementedError';
96
+ }
97
+ }
98
+ exports.OnchainWatermarkNotImplementedError = OnchainWatermarkNotImplementedError;
99
+ class QuorumUnavailableError extends Error {
100
+ constructor(required, available) {
101
+ super(`Quorum not reached: ${available}/${required} peers attested. ` +
102
+ 'The reservation was burned locally — retry when more peers are reachable.');
103
+ this.required = required;
104
+ this.available = available;
105
+ this.name = 'QuorumUnavailableError';
106
+ }
107
+ }
108
+ exports.QuorumUnavailableError = QuorumUnavailableError;
109
+ class QuorumConflictError extends Error {
110
+ constructor(treeId, indices) {
111
+ super(`Quorum conflict for tree ${treeId} at (${indices.addressIndex}, ${indices.l1}, ${indices.l2}): ` +
112
+ 'a peer already holds a reservation for these indices.');
113
+ this.treeId = treeId;
114
+ this.indices = indices;
115
+ this.name = 'QuorumConflictError';
116
+ }
117
+ }
118
+ exports.QuorumConflictError = QuorumConflictError;
119
+ class OnchainWatermarkError extends Error {
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = 'OnchainWatermarkError';
123
+ }
124
+ }
125
+ exports.OnchainWatermarkError = OnchainWatermarkError;
126
+ class DeviceRangeViolationError extends Error {
127
+ constructor(addressIndex, allowedStart, allowedEnd) {
128
+ super(`Address index ${addressIndex} is outside device range [${allowedStart}, ${allowedEnd}]`);
129
+ this.addressIndex = addressIndex;
130
+ this.allowedStart = allowedStart;
131
+ this.allowedEnd = allowedEnd;
132
+ this.name = 'DeviceRangeViolationError';
133
+ }
134
+ }
135
+ exports.DeviceRangeViolationError = DeviceRangeViolationError;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * HybridLeaseProvider — recommended production default.
3
+ *
4
+ * Always uses local first, then gates on node cert and/or quorum attestation
5
+ * for transactions above a configurable value threshold.
6
+ */
7
+ import type { WotsLeaseProvider, ReserveParams, LeaseReservation, LeaseCertificate, LocalWatermark, SyncResult, CertificateSigner } from './types.js';
8
+ import type { LocalLeaseProvider } from './local.js';
9
+ import type { PersonalLeaseNodeProvider } from './stubs.js';
10
+ import type { P2PQuorumLeaseProvider } from './stubs.js';
11
+ import type { OnchainWatermarkProvider } from './stubs.js';
12
+ export interface HybridLeaseProviderConfig {
13
+ local: LocalLeaseProvider;
14
+ node?: PersonalLeaseNodeProvider;
15
+ quorum?: P2PQuorumLeaseProvider;
16
+ onchain?: OnchainWatermarkProvider;
17
+ threshold?: number;
18
+ /** Identity that authenticates locally-assembled quorum certificates. */
19
+ certificateSigner?: CertificateSigner;
20
+ }
21
+ export declare class HybridLeaseProvider implements WotsLeaseProvider {
22
+ private readonly local;
23
+ private readonly node?;
24
+ private readonly quorum?;
25
+ private readonly onchain?;
26
+ private readonly threshold;
27
+ private readonly certificateSigner?;
28
+ constructor(config: HybridLeaseProviderConfig);
29
+ private isHighValue;
30
+ reserveKeyUse(params: ReserveParams): Promise<LeaseReservation>;
31
+ commitKeyUse(reservationId: string, txId: string): Promise<void>;
32
+ burnReservation(reservationId: string, reason: string): Promise<void>;
33
+ getLocalWatermark(treeId: string): Promise<LocalWatermark>;
34
+ publishWatermark(treeId: string): Promise<void>;
35
+ syncLeaseJournal(): Promise<SyncResult>;
36
+ verifyLeaseCertificate(cert?: LeaseCertificate): Promise<boolean>;
37
+ }