@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.
- package/LICENSE +202 -21
- package/NOTICE +60 -0
- package/dist/axia.d.ts +1 -1
- package/dist/axia.js +41 -28
- package/dist/certificate.d.ts +37 -0
- package/dist/certificate.js +69 -0
- package/dist/cjs/axia.d.ts +33 -0
- package/dist/cjs/axia.js +145 -0
- package/dist/cjs/certificate.d.ts +37 -0
- package/dist/cjs/certificate.js +74 -0
- package/dist/cjs/device.d.ts +18 -0
- package/dist/cjs/device.js +34 -0
- package/dist/cjs/errors.d.ts +71 -0
- package/dist/cjs/errors.js +135 -0
- package/dist/cjs/hybrid.d.ts +37 -0
- package/dist/cjs/hybrid.js +145 -0
- package/dist/cjs/index.d.ts +17 -0
- package/dist/cjs/index.js +45 -0
- package/dist/cjs/journal.d.ts +30 -0
- package/dist/cjs/journal.js +369 -0
- package/dist/cjs/local.d.ts +83 -0
- package/dist/cjs/local.js +440 -0
- package/dist/cjs/onchain.d.ts +54 -0
- package/dist/cjs/onchain.js +314 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/quorum.d.ts +75 -0
- package/dist/cjs/quorum.js +307 -0
- package/dist/cjs/replicated-backup.d.ts +30 -0
- package/dist/cjs/replicated-backup.js +52 -0
- package/dist/cjs/seed.d.ts +41 -0
- package/dist/cjs/seed.js +84 -0
- package/dist/cjs/stubs.d.ts +50 -0
- package/dist/cjs/stubs.js +125 -0
- package/dist/cjs/types.d.ts +272 -0
- package/dist/cjs/types.js +5 -0
- package/dist/cjs/watermark.d.ts +51 -0
- package/dist/cjs/watermark.js +328 -0
- package/dist/device.js +2 -6
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +49 -25
- package/dist/hybrid.d.ts +4 -1
- package/dist/hybrid.js +72 -26
- package/dist/index.d.ts +6 -2
- package/dist/index.js +12 -38
- package/dist/journal.d.ts +14 -4
- package/dist/journal.js +327 -49
- package/dist/local.d.ts +31 -1
- package/dist/local.js +267 -68
- package/dist/onchain.d.ts +3 -1
- package/dist/onchain.js +63 -28
- package/dist/quorum.d.ts +15 -0
- package/dist/quorum.js +54 -15
- package/dist/replicated-backup.d.ts +30 -0
- package/dist/replicated-backup.js +48 -0
- package/dist/seed.d.ts +41 -0
- package/dist/seed.js +80 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +20 -10
- package/dist/types.d.ts +74 -1
- package/dist/types.js +1 -2
- package/dist/watermark.d.ts +22 -1
- package/dist/watermark.js +133 -16
- package/package.json +25 -15
- package/rust/pkg/package.json +3 -3
- package/rust/pkg/wots_lease_wasm_bg.wasm +0 -0
- package/rust/pkg-node/package.json +3 -3
- package/rust/pkg-node/wots_lease_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Implementations for Layers 3–5 of the WotsLeaseProvider hierarchy.
|
|
4
|
+
*
|
|
5
|
+
* Layer 3 (PersonalLeaseNodeProvider) — HTTP client for a personal @totemsdk/lookup-node
|
|
6
|
+
* that has lease coordination enabled ({ lease: { enabled: true } }).
|
|
7
|
+
* The node exposes REST endpoints under /v1/lease/*.
|
|
8
|
+
*
|
|
9
|
+
* Layers 4–5 (P2PQuorumLeaseProvider, OnchainWatermarkProvider) — implemented in
|
|
10
|
+
* quorum.ts and onchain.ts; re-exported here for backward compatibility.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.PersonalLeaseNodeProvider = exports.OnchainWatermarkProvider = exports.P2PQuorumLeaseProvider = void 0;
|
|
14
|
+
const certificate_js_1 = require("./certificate.js");
|
|
15
|
+
var quorum_js_1 = require("./quorum.js");
|
|
16
|
+
Object.defineProperty(exports, "P2PQuorumLeaseProvider", { enumerable: true, get: function () { return quorum_js_1.P2PQuorumLeaseProvider; } });
|
|
17
|
+
var onchain_js_1 = require("./onchain.js");
|
|
18
|
+
Object.defineProperty(exports, "OnchainWatermarkProvider", { enumerable: true, get: function () { return onchain_js_1.OnchainWatermarkProvider; } });
|
|
19
|
+
/**
|
|
20
|
+
* Layer 3 — personal lookup-node lease coordinator.
|
|
21
|
+
*
|
|
22
|
+
* Calls the HTTP REST API exposed by a running @totemsdk/lookup-node that has
|
|
23
|
+
* lease coordination enabled. All write operations are forwarded to the node's
|
|
24
|
+
* LeaseCoordinator so the node acts as the source-of-truth watermark journal
|
|
25
|
+
* for high-value transactions.
|
|
26
|
+
*
|
|
27
|
+
* Wrap with HybridLeaseProvider so local reservations always succeed even when
|
|
28
|
+
* the personal node is temporarily unreachable:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const provider = new HybridLeaseProvider({
|
|
32
|
+
* local: new LocalLeaseProvider(storage),
|
|
33
|
+
* node: new PersonalLeaseNodeProvider({ nodeUrl, nodePubkey }),
|
|
34
|
+
* threshold: 10, // escalate to node for txns >= 10 MIN
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
class PersonalLeaseNodeProvider {
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.baseUrl = config.nodeUrl.replace(/\/$/, '');
|
|
41
|
+
this.nodePubkey = config.nodePubkey;
|
|
42
|
+
this.authToken = config.authToken;
|
|
43
|
+
this.certificateSigner = config.certificateSigner;
|
|
44
|
+
this.verify = config.verify;
|
|
45
|
+
}
|
|
46
|
+
headers() {
|
|
47
|
+
const h = { 'Content-Type': 'application/json' };
|
|
48
|
+
if (this.authToken)
|
|
49
|
+
h['Authorization'] = `Bearer ${this.authToken}`;
|
|
50
|
+
return h;
|
|
51
|
+
}
|
|
52
|
+
async post(path, body) {
|
|
53
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: this.headers(),
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const text = await res.text().catch(() => '');
|
|
60
|
+
throw new Error(`PersonalLeaseNodeProvider: ${path} HTTP ${res.status} — ${text.slice(0, 200)}`);
|
|
61
|
+
}
|
|
62
|
+
return res.json();
|
|
63
|
+
}
|
|
64
|
+
async get(path) {
|
|
65
|
+
const res = await fetch(`${this.baseUrl}${path}`, { headers: this.headers() });
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
const text = await res.text().catch(() => '');
|
|
68
|
+
throw new Error(`PersonalLeaseNodeProvider: GET ${path} HTTP ${res.status} — ${text.slice(0, 200)}`);
|
|
69
|
+
}
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
async reserveKeyUse(params) {
|
|
73
|
+
const data = await this.post('/v1/lease/reserve', {
|
|
74
|
+
treeId: params.treeId,
|
|
75
|
+
branchId: params.branchId,
|
|
76
|
+
deviceId: params.deviceId,
|
|
77
|
+
ttlMs: params.ttlMs,
|
|
78
|
+
payloadHash: params.payloadHash,
|
|
79
|
+
purpose: params.purpose,
|
|
80
|
+
});
|
|
81
|
+
return { ...data.reservation, certificate: data.certificate };
|
|
82
|
+
}
|
|
83
|
+
async commitKeyUse(reservationId, txId) {
|
|
84
|
+
const data = await this.post('/v1/lease/commit', { reservationId, txId });
|
|
85
|
+
if (!data.ok)
|
|
86
|
+
throw new Error(`PersonalLeaseNodeProvider: commit failed — ${data.error ?? 'unknown'}`);
|
|
87
|
+
}
|
|
88
|
+
async burnReservation(reservationId, reason) {
|
|
89
|
+
const data = await this.post('/v1/lease/burn', { reservationId, reason });
|
|
90
|
+
if (!data.ok)
|
|
91
|
+
throw new Error(`PersonalLeaseNodeProvider: burn failed — ${data.error ?? 'unknown'}`);
|
|
92
|
+
}
|
|
93
|
+
async getLocalWatermark(treeId) {
|
|
94
|
+
return this.get(`/v1/lease/watermark/${encodeURIComponent(treeId)}`);
|
|
95
|
+
}
|
|
96
|
+
async publishWatermark(treeId) {
|
|
97
|
+
await this.post(`/v1/lease/watermark/${encodeURIComponent(treeId)}/publish`, {});
|
|
98
|
+
}
|
|
99
|
+
async syncLeaseJournal() {
|
|
100
|
+
return this.get('/v1/lease/journal/sync');
|
|
101
|
+
}
|
|
102
|
+
async verifyLeaseCertificate(cert) {
|
|
103
|
+
if (!cert)
|
|
104
|
+
return false;
|
|
105
|
+
if (cert.issuedBy !== this.nodePubkey)
|
|
106
|
+
return false;
|
|
107
|
+
if (cert.expiresAt <= Date.now())
|
|
108
|
+
return false;
|
|
109
|
+
// AUD-016: fail closed. The provider can only verify a certificate when it
|
|
110
|
+
// holds a verifier; an `issuedBy` match is not authenticity evidence.
|
|
111
|
+
const signer = this.certificateSigner?.verify
|
|
112
|
+
? this.certificateSigner
|
|
113
|
+
: this.verify
|
|
114
|
+
? {
|
|
115
|
+
publicKeyDigest: this.nodePubkey,
|
|
116
|
+
sign: async () => {
|
|
117
|
+
throw new Error('PersonalLeaseNodeProvider holds a verify-only signer and cannot sign');
|
|
118
|
+
},
|
|
119
|
+
verify: this.verify,
|
|
120
|
+
}
|
|
121
|
+
: undefined;
|
|
122
|
+
return (0, certificate_js_1.certificateSignatureVerified)(cert, signer);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.PersonalLeaseNodeProvider = PersonalLeaseNodeProvider;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @totemsdk/wots-lease — canonical v3 watermark types and WotsLeaseProvider interface
|
|
3
|
+
*/
|
|
4
|
+
import type { LocalLeaseProvider } from './local.js';
|
|
5
|
+
import type { StorageAdapter } from '@totemsdk/core';
|
|
6
|
+
export type UnavailableReason = 'reserved' | 'committed' | 'burned' | 'reserved-expired';
|
|
7
|
+
export interface SigningIndices {
|
|
8
|
+
addressIndex: number;
|
|
9
|
+
l1: number;
|
|
10
|
+
l2: number;
|
|
11
|
+
}
|
|
12
|
+
export type LeaseStatus = 'pending' | 'active' | 'expired' | 'finalized' | 'cancelled';
|
|
13
|
+
export interface TreeWatermark {
|
|
14
|
+
treeId: string;
|
|
15
|
+
deviceId?: string;
|
|
16
|
+
branchId?: string;
|
|
17
|
+
addressCursor: number;
|
|
18
|
+
l1Cursor: number;
|
|
19
|
+
l2Cursor: number;
|
|
20
|
+
unavailable: Record<number, UnavailableReason>;
|
|
21
|
+
lastSyncTimestamp?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface WotsWatermarkState {
|
|
24
|
+
version: 3;
|
|
25
|
+
trees: Record<string, TreeWatermark>;
|
|
26
|
+
}
|
|
27
|
+
export interface QuorumAttestation {
|
|
28
|
+
peerId: string;
|
|
29
|
+
indices: SigningIndices;
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
signature?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The reservation id the peer issued for these indices in its own journal.
|
|
34
|
+
* Commit/burn broadcasts must reference the peer's id, not the local one
|
|
35
|
+
* (AUD-044) — each peer only knows the reservation it issued.
|
|
36
|
+
*/
|
|
37
|
+
peerReservationId?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Identity used to authenticate lease certificates (Layer 4/5).
|
|
41
|
+
*
|
|
42
|
+
* A certificate's `signature` is a signature over the canonical certificate
|
|
43
|
+
* payload (see certificate.ts). Verifiers reject unsigned certificates.
|
|
44
|
+
*/
|
|
45
|
+
export interface CertificateSigner {
|
|
46
|
+
/** Hex (0x-prefixed or bare) public key digest of the issuing identity. */
|
|
47
|
+
publicKeyDigest: string;
|
|
48
|
+
/** Identity label stored on issued certificates via `issuedBy`. */
|
|
49
|
+
name?: string;
|
|
50
|
+
/** Sign the canonical certificate message. */
|
|
51
|
+
sign(message: Uint8Array): Promise<Uint8Array>;
|
|
52
|
+
/** Verify a signature over the canonical certificate message. */
|
|
53
|
+
verify?(message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
54
|
+
}
|
|
55
|
+
export interface LeaseCertificate {
|
|
56
|
+
reservationId: string;
|
|
57
|
+
treeId: string;
|
|
58
|
+
branchId?: string;
|
|
59
|
+
deviceId?: string;
|
|
60
|
+
indices: SigningIndices;
|
|
61
|
+
purpose?: string;
|
|
62
|
+
payloadHash?: string;
|
|
63
|
+
issuedBy: string;
|
|
64
|
+
issuedAt: number;
|
|
65
|
+
expiresAt: number;
|
|
66
|
+
signature: string;
|
|
67
|
+
/** Layer 4 — quorum attestations collected from P2P peers. */
|
|
68
|
+
attestations?: QuorumAttestation[];
|
|
69
|
+
/** Layer 5 — content hash of the on-chain watermark TX (sha3-256 of TxPoW bytes). */
|
|
70
|
+
txpowid?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface LeaseReservation {
|
|
73
|
+
reservationId: string;
|
|
74
|
+
indices: SigningIndices;
|
|
75
|
+
expiresAt: number;
|
|
76
|
+
certificate?: LeaseCertificate;
|
|
77
|
+
leaseToken?: string;
|
|
78
|
+
}
|
|
79
|
+
export interface ReserveParams {
|
|
80
|
+
treeId: string;
|
|
81
|
+
branchId?: string;
|
|
82
|
+
purpose?: string;
|
|
83
|
+
deviceId?: string;
|
|
84
|
+
ttlMs?: number;
|
|
85
|
+
payloadHash?: string;
|
|
86
|
+
valueHint?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface LocalWatermark {
|
|
89
|
+
treeId: string;
|
|
90
|
+
addressCursor: number;
|
|
91
|
+
l1Cursor: number;
|
|
92
|
+
l2Cursor: number;
|
|
93
|
+
unavailableCount: number;
|
|
94
|
+
capacity: number;
|
|
95
|
+
lastSyncTimestamp?: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A portable, self-describing watermark snapshot (RFC-013 §9). Used to seed a
|
|
99
|
+
* freshly-provisioned local watermark from an exported blob, an operator-supplied
|
|
100
|
+
* high-water mark, or the on-chain cursor, so a restored wallet never starts
|
|
101
|
+
* below the true high-water mark and reissues a spent leaf.
|
|
102
|
+
*/
|
|
103
|
+
export interface WatermarkSeed {
|
|
104
|
+
readonly version: 1;
|
|
105
|
+
readonly treeId: string;
|
|
106
|
+
readonly addressCursor: number;
|
|
107
|
+
readonly l1Cursor: number;
|
|
108
|
+
readonly l2Cursor: number;
|
|
109
|
+
/** Flat indices already consumed (reserved/committed/burned). */
|
|
110
|
+
readonly unavailable?: readonly number[];
|
|
111
|
+
}
|
|
112
|
+
export interface SeedWatermarkOptions {
|
|
113
|
+
/**
|
|
114
|
+
* Permit a seed whose cursor is behind the current local cursor. Defaults to
|
|
115
|
+
* `false` — regression re-exposes used leaves, so seeding fails closed.
|
|
116
|
+
*/
|
|
117
|
+
readonly allowRegression?: boolean;
|
|
118
|
+
}
|
|
119
|
+
export interface ConflictRecord {
|
|
120
|
+
treeId: string;
|
|
121
|
+
localIndex: number;
|
|
122
|
+
remoteIndex: number;
|
|
123
|
+
timestamp: number;
|
|
124
|
+
}
|
|
125
|
+
export interface SyncResult {
|
|
126
|
+
synced: boolean;
|
|
127
|
+
conflicts: ConflictRecord[];
|
|
128
|
+
advancedTo?: SigningIndices;
|
|
129
|
+
}
|
|
130
|
+
export interface WotsLeaseProvider {
|
|
131
|
+
reserveKeyUse(params: ReserveParams): Promise<LeaseReservation>;
|
|
132
|
+
commitKeyUse(reservationId: string, txId: string): Promise<void>;
|
|
133
|
+
burnReservation(reservationId: string, reason: string): Promise<void>;
|
|
134
|
+
getLocalWatermark(treeId: string): Promise<LocalWatermark>;
|
|
135
|
+
publishWatermark(treeId: string): Promise<void>;
|
|
136
|
+
syncLeaseJournal(): Promise<SyncResult>;
|
|
137
|
+
verifyLeaseCertificate(cert?: LeaseCertificate): Promise<boolean>;
|
|
138
|
+
}
|
|
139
|
+
export interface JournalEntry {
|
|
140
|
+
treeId: string;
|
|
141
|
+
branchId: string;
|
|
142
|
+
wotsIndex: number;
|
|
143
|
+
indices: SigningIndices;
|
|
144
|
+
status: 'reserved' | 'committed' | 'burned' | 'reserved-expired';
|
|
145
|
+
reservationId?: string;
|
|
146
|
+
payloadHash?: string;
|
|
147
|
+
txId?: string;
|
|
148
|
+
timestamp: number;
|
|
149
|
+
deviceId: string;
|
|
150
|
+
previousHash?: string;
|
|
151
|
+
hash?: string;
|
|
152
|
+
}
|
|
153
|
+
export interface DeviceKeyRange {
|
|
154
|
+
deviceId: string;
|
|
155
|
+
startAddressIndex: number;
|
|
156
|
+
endAddressIndex: number;
|
|
157
|
+
addressCount: number;
|
|
158
|
+
}
|
|
159
|
+
export interface PersonalLeaseNodeConfig {
|
|
160
|
+
nodeUrl: string;
|
|
161
|
+
nodePubkey: string;
|
|
162
|
+
authToken?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Identity that authenticates certificates issued by this node. When it
|
|
165
|
+
* carries a `verify()` function, `verifyLeaseCertificate` uses it. Without
|
|
166
|
+
* any verifier the provider FAILS CLOSED (AUD-016) — an identity match on
|
|
167
|
+
* `issuedBy` alone is never enough.
|
|
168
|
+
*/
|
|
169
|
+
certificateSigner?: CertificateSigner;
|
|
170
|
+
/**
|
|
171
|
+
* Verify-only cryptographic verifier for certificates issued by the node.
|
|
172
|
+
* The provider holds no signing key, so this is the recommended way to make
|
|
173
|
+
* `verifyLeaseCertificate` functional. When neither this nor a
|
|
174
|
+
* `certificateSigner.verify` is supplied, the provider rejects every
|
|
175
|
+
* certificate (fail closed).
|
|
176
|
+
*/
|
|
177
|
+
verify?: (message: Uint8Array, signature: Uint8Array) => Promise<boolean>;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Layer 4 — P2P quorum lease coordination.
|
|
181
|
+
*
|
|
182
|
+
* `peers` is the set of quorum members this device coordinates with. Each
|
|
183
|
+
* entry is a transport-agnostic RPC handle: the provider sends the same
|
|
184
|
+
* LEASE_RESERVE / LEASE_COMMIT / LEASE_BURN wire messages used by the
|
|
185
|
+
* lookup protocol, so any peer that speaks that protocol can participate
|
|
186
|
+
* (lookup nodes, other devices, or in-memory test peers).
|
|
187
|
+
*/
|
|
188
|
+
export interface QuorumPeer {
|
|
189
|
+
peerId: string;
|
|
190
|
+
request(message: {
|
|
191
|
+
type: 'LEASE_RESERVE' | 'LEASE_COMMIT' | 'LEASE_BURN' | 'LEASE_WATERMARK';
|
|
192
|
+
payload: Record<string, unknown>;
|
|
193
|
+
}, timeoutMs?: number): Promise<{
|
|
194
|
+
type: string;
|
|
195
|
+
payload: Record<string, unknown>;
|
|
196
|
+
}>;
|
|
197
|
+
}
|
|
198
|
+
export interface P2PQuorumLeaseProviderConfig {
|
|
199
|
+
/** Quorum members to coordinate with (excluding self). */
|
|
200
|
+
peers: QuorumPeer[];
|
|
201
|
+
/** Minimum attestations required for a reservation to be considered quorum-approved. Default: 1. */
|
|
202
|
+
minAttestations?: number;
|
|
203
|
+
/** Local provider used for the authoritative local watermark + journal. */
|
|
204
|
+
local: LocalLeaseProvider;
|
|
205
|
+
/** Timeout per peer request. Default: 5_000. */
|
|
206
|
+
requestTimeoutMs?: number;
|
|
207
|
+
/** Require quorum approval on commit as well as reserve. Default: false. */
|
|
208
|
+
requireQuorumOnCommit?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Identity that authenticates issued certificates. When set, reserved
|
|
211
|
+
* certificates carry a real signature; without it, certificates are
|
|
212
|
+
* issued unsigned and will FAIL verification.
|
|
213
|
+
*/
|
|
214
|
+
certificateSigner?: CertificateSigner;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Layer 5 — on-chain watermark anchoring.
|
|
218
|
+
*
|
|
219
|
+
* `chain` is any ChainStateProvider (hosted, Minima RPC, or lookup node).
|
|
220
|
+
* The provider spends a dedicated watermark coin whose STATE(0) holds the
|
|
221
|
+
* flat watermark cursor; every publish advances it on-chain so the watermark
|
|
222
|
+
* is verifiable by third parties without trusting this device.
|
|
223
|
+
*/
|
|
224
|
+
export interface OnchainWatermarkProviderConfig {
|
|
225
|
+
/** Chain access for coin queries, proofs, and broadcasting. */
|
|
226
|
+
chain: {
|
|
227
|
+
getCoin(coinId: string): Promise<{
|
|
228
|
+
coinid: string;
|
|
229
|
+
address: string;
|
|
230
|
+
amount: string;
|
|
231
|
+
tokenid: string;
|
|
232
|
+
state?: unknown[];
|
|
233
|
+
} | null>;
|
|
234
|
+
getProof(coinId: string): Promise<{
|
|
235
|
+
data: unknown;
|
|
236
|
+
}>;
|
|
237
|
+
broadcastTxPoW(txpowHex: string): Promise<{
|
|
238
|
+
success: boolean;
|
|
239
|
+
txpowid?: string;
|
|
240
|
+
message?: string;
|
|
241
|
+
}>;
|
|
242
|
+
getTip?(): Promise<{
|
|
243
|
+
block: number;
|
|
244
|
+
}>;
|
|
245
|
+
};
|
|
246
|
+
/** Coin ID of the dedicated watermark coin. */
|
|
247
|
+
watermarkCoinId: string;
|
|
248
|
+
/** Address the watermark coin currently sits at (spending address). */
|
|
249
|
+
watermarkAddress: string;
|
|
250
|
+
/** Token ID of the watermark coin. Default: '0x00'. */
|
|
251
|
+
tokenId?: string;
|
|
252
|
+
/** Amount of the watermark coin in MIN base units. Default: '1'. */
|
|
253
|
+
amount?: string;
|
|
254
|
+
/** Local provider used for the authoritative local watermark + journal. */
|
|
255
|
+
local: LocalLeaseProvider;
|
|
256
|
+
/** Signer for the watermark coin's script (SIGNEDBY digest). Also authenticates issued certificates. */
|
|
257
|
+
signer: {
|
|
258
|
+
publicKeyDigest: string;
|
|
259
|
+
sign(message: Uint8Array): Promise<Uint8Array>;
|
|
260
|
+
verify?(message: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
261
|
+
};
|
|
262
|
+
/** Port holding the flat watermark cursor in the coin state. Default: 0. */
|
|
263
|
+
statePort?: number;
|
|
264
|
+
/** Minimum blocks between on-chain publishes (rate limit). Default: 1. */
|
|
265
|
+
minBlocksBetweenPublishes?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Optional durable storage for the watermark coin's identity. When set, the
|
|
268
|
+
* provider persists the advanced watermark coin ID after each publish so the
|
|
269
|
+
* rollover survives restarts. Default: in-memory only.
|
|
270
|
+
*/
|
|
271
|
+
storage?: StorageAdapter;
|
|
272
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WotsWatermarkStore — canonical v3 implementation
|
|
3
|
+
*
|
|
4
|
+
* - Reads v1 (flat usedIndices) and v2 (per-address usedIndices) automatically on load.
|
|
5
|
+
* - Writes only v3.
|
|
6
|
+
* - Enforces monotonicity: unavailable map can only grow, cursors can only advance.
|
|
7
|
+
* - Storage key unchanged ('totem_wots_watermark') to avoid storage churn.
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageAdapter, LoggerAdapter } from '@totemsdk/core';
|
|
10
|
+
import type { WotsWatermarkState, TreeWatermark, SigningIndices, UnavailableReason, LocalWatermark, WatermarkSeed, SeedWatermarkOptions } from './types.js';
|
|
11
|
+
export declare function flatIndex(idx: SigningIndices): number;
|
|
12
|
+
export declare function fromFlatIndex(flat: number): SigningIndices;
|
|
13
|
+
export declare function assertValidSigningIndices(indices: SigningIndices): void;
|
|
14
|
+
export declare class WotsWatermarkStore {
|
|
15
|
+
private readonly storage;
|
|
16
|
+
private state;
|
|
17
|
+
private _initialized;
|
|
18
|
+
private readonly logger;
|
|
19
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter);
|
|
20
|
+
initialize(): Promise<void>;
|
|
21
|
+
private ensureInit;
|
|
22
|
+
private persist;
|
|
23
|
+
/**
|
|
24
|
+
* Reload the persisted watermark and merge it monotonically into the local
|
|
25
|
+
* state. A provider that shares its storage with another provider (AUD-004)
|
|
26
|
+
* must call this inside its mutation critical section before allocating or
|
|
27
|
+
* marking, so it observes writes it did not make and never re-issues a slot
|
|
28
|
+
* another instance already took.
|
|
29
|
+
*/
|
|
30
|
+
refresh(): Promise<void>;
|
|
31
|
+
private getOrCreateTree;
|
|
32
|
+
getNextIndices(treeId: string): SigningIndices;
|
|
33
|
+
markUnavailable(treeId: string, indices: SigningIndices, reason: UnavailableReason): Promise<void>;
|
|
34
|
+
isUnavailable(treeId: string, indices: SigningIndices): boolean;
|
|
35
|
+
save(treeId: string, patch: Partial<TreeWatermark>): Promise<void>;
|
|
36
|
+
getLocalWatermark(treeId: string): LocalWatermark;
|
|
37
|
+
/**
|
|
38
|
+
* Export a portable seed for a tree (RFC-013 §9). Includes the cursor and the
|
|
39
|
+
* flat indices already consumed, so it can rebuild the no-reuse state exactly.
|
|
40
|
+
*/
|
|
41
|
+
exportSeed(treeId: string): WatermarkSeed;
|
|
42
|
+
/**
|
|
43
|
+
* Seed the watermark from an exported/operator/on-chain snapshot. Forward-only
|
|
44
|
+
* by default: a seed behind the current cursor raises
|
|
45
|
+
* {@link WatermarkSeedRegressionError} so a restore can never re-expose used
|
|
46
|
+
* leaves. The unavailable set is unioned, never shrunk.
|
|
47
|
+
*/
|
|
48
|
+
seed(seed: WatermarkSeed, options?: SeedWatermarkOptions): Promise<void>;
|
|
49
|
+
getRawState(): WotsWatermarkState;
|
|
50
|
+
clear(): Promise<void>;
|
|
51
|
+
}
|