@totemsdk/core 1.0.0
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/dist/Streamable.d.ts +268 -0
- package/dist/Streamable.js +416 -0
- package/dist/adapters/index.d.ts +190 -0
- package/dist/adapters/index.js +107 -0
- package/dist/adapters/types.d.ts +149 -0
- package/dist/adapters/types.js +67 -0
- package/dist/binio.d.ts +9 -0
- package/dist/binio.js +30 -0
- package/dist/bip39.d.ts +91 -0
- package/dist/bip39.js +417 -0
- package/dist/derive.d.ts +2 -0
- package/dist/derive.js +9 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +53 -0
- package/dist/javaStreamables.d.ts +233 -0
- package/dist/javaStreamables.js +332 -0
- package/dist/lease/LeaseMonitor.d.ts +44 -0
- package/dist/lease/LeaseMonitor.js +107 -0
- package/dist/lease/LeaseStore.d.ts +57 -0
- package/dist/lease/LeaseStore.js +144 -0
- package/dist/lease/WatermarkStore.d.ts +61 -0
- package/dist/lease/WatermarkStore.js +202 -0
- package/dist/lease/index.d.ts +14 -0
- package/dist/lease/index.js +13 -0
- package/dist/lease-client.d.ts +31 -0
- package/dist/lease-client.js +39 -0
- package/dist/minima32.d.ts +18 -0
- package/dist/minima32.js +112 -0
- package/dist/minimaWireSerializer.d.ts +60 -0
- package/dist/minimaWireSerializer.js +71 -0
- package/dist/mmr.d.ts +156 -0
- package/dist/mmr.js +342 -0
- package/dist/mx.d.ts +1 -0
- package/dist/mx.js +1 -0
- package/dist/params.d.ts +29 -0
- package/dist/params.js +16 -0
- package/dist/script.d.ts +7 -0
- package/dist/script.js +22 -0
- package/dist/scripts/contract-helpers.d.ts +252 -0
- package/dist/scripts/contract-helpers.js +394 -0
- package/dist/scripts/dapp-types.d.ts +119 -0
- package/dist/scripts/dapp-types.js +72 -0
- package/dist/scripts/index.d.ts +4 -0
- package/dist/scripts/index.js +4 -0
- package/dist/scripts/types.d.ts +81 -0
- package/dist/scripts/types.js +145 -0
- package/dist/scripts/witness-serializer.d.ts +29 -0
- package/dist/scripts/witness-serializer.js +288 -0
- package/dist/transaction.d.ts +106 -0
- package/dist/transaction.js +284 -0
- package/dist/treekey.d.ts +383 -0
- package/dist/treekey.js +762 -0
- package/dist/tx/TransactionLifecycle.d.ts +55 -0
- package/dist/tx/TransactionLifecycle.js +189 -0
- package/dist/tx/TransactionReceiptStore.d.ts +36 -0
- package/dist/tx/TransactionReceiptStore.js +85 -0
- package/dist/tx/TransactionService.d.ts +50 -0
- package/dist/tx/TransactionService.js +178 -0
- package/dist/tx/index.d.ts +13 -0
- package/dist/tx/index.js +12 -0
- package/dist/tx/types.d.ts +114 -0
- package/dist/tx/types.js +5 -0
- package/dist/util.d.ts +1 -0
- package/dist/util.js +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +35 -0
- package/dist/verify.d.ts +18 -0
- package/dist/verify.js +162 -0
- package/dist/version.d.ts +12 -0
- package/dist/version.js +12 -0
- package/dist/wots.d.ts +232 -0
- package/dist/wots.js +538 -0
- package/package.json +65 -0
- package/src/Streamable.d.ts +265 -0
- package/src/Streamable.js +431 -0
- package/src/Streamable.ts +567 -0
- package/src/__tests__/Streamable.parity.test.ts +422 -0
- package/src/__tests__/perAddressDerivation.parity.test.ts +152 -0
- package/src/__tests__/wots-parity.test.ts +289 -0
- package/src/adapters/index.d.ts +190 -0
- package/src/adapters/index.js +117 -0
- package/src/adapters/index.ts +301 -0
- package/src/adapters/types.ts +235 -0
- package/src/addr.oracle.test.ts +25 -0
- package/src/address.test.ts +55 -0
- package/src/binio.ts +21 -0
- package/src/bip39.d.ts +91 -0
- package/src/bip39.js +427 -0
- package/src/bip39.ts +432 -0
- package/src/derive.d.ts +2 -0
- package/src/derive.js +13 -0
- package/src/derive.ts +11 -0
- package/src/index.d.ts +26 -0
- package/src/index.js +128 -0
- package/src/index.test.ts +5 -0
- package/src/index.ts +206 -0
- package/src/javaStreamables.d.ts +233 -0
- package/src/javaStreamables.js +328 -0
- package/src/javaStreamables.ts +403 -0
- package/src/lease/LeaseMonitor.d.ts +44 -0
- package/src/lease/LeaseMonitor.js +111 -0
- package/src/lease/LeaseMonitor.ts +157 -0
- package/src/lease/LeaseStore.d.ts +57 -0
- package/src/lease/LeaseStore.js +148 -0
- package/src/lease/LeaseStore.ts +203 -0
- package/src/lease/WatermarkStore.d.ts +61 -0
- package/src/lease/WatermarkStore.js +206 -0
- package/src/lease/WatermarkStore.ts +270 -0
- package/src/lease/index.d.ts +14 -0
- package/src/lease/index.js +22 -0
- package/src/lease/index.ts +41 -0
- package/src/lease-client.d.ts +31 -0
- package/src/lease-client.js +44 -0
- package/src/lease-client.ts +69 -0
- package/src/minima32.d.ts +10 -0
- package/src/minima32.js +113 -0
- package/src/minima32.test.ts +11 -0
- package/src/minima32.ts +127 -0
- package/src/minimaWireSerializer.d.ts +189 -0
- package/src/minimaWireSerializer.js +311 -0
- package/src/minimaWireSerializer.test.ts +466 -0
- package/src/minimaWireSerializer.ts +117 -0
- package/src/mmr.d.ts +153 -0
- package/src/mmr.js +351 -0
- package/src/mmr.oracle.test.ts +28 -0
- package/src/mmr.ts +429 -0
- package/src/mx.test.ts +25 -0
- package/src/mx.ts +1 -0
- package/src/params.d.ts +29 -0
- package/src/params.d.ts.map +1 -0
- package/src/params.js +20 -0
- package/src/params.js.map +1 -0
- package/src/params.ts +44 -0
- package/src/script.d.ts +7 -0
- package/src/script.js +27 -0
- package/src/script.ts +24 -0
- package/src/scripts/contract-helpers.ts +609 -0
- package/src/scripts/dapp-types.ts +240 -0
- package/src/scripts/index.ts +22 -0
- package/src/scripts/types.ts +247 -0
- package/src/scripts/witness-serializer.ts +384 -0
- package/src/transaction.ts +430 -0
- package/src/treekey.d.ts +383 -0
- package/src/treekey.js +778 -0
- package/src/treekey.parity.test.ts +459 -0
- package/src/treekey.ts +979 -0
- package/src/tx/TransactionLifecycle.d.ts +55 -0
- package/src/tx/TransactionLifecycle.js +188 -0
- package/src/tx/TransactionLifecycle.ts +280 -0
- package/src/tx/TransactionReceiptStore.d.ts +36 -0
- package/src/tx/TransactionReceiptStore.js +89 -0
- package/src/tx/TransactionReceiptStore.ts +112 -0
- package/src/tx/TransactionService.d.ts +31 -0
- package/src/tx/TransactionService.js +140 -0
- package/src/tx/TransactionService.ts +250 -0
- package/src/tx/index.d.ts +13 -0
- package/src/tx/index.js +20 -0
- package/src/tx/index.ts +44 -0
- package/src/tx/types.d.ts +80 -0
- package/src/tx/types.js +6 -0
- package/src/tx/types.ts +127 -0
- package/src/util.d.ts +1 -0
- package/src/util.js +5 -0
- package/src/util.ts +1 -0
- package/src/utils.d.ts +8 -0
- package/src/utils.js +42 -0
- package/src/utils.ts +38 -0
- package/src/verify.ts +209 -0
- package/src/version.ts +13 -0
- package/src/wots.d.ts +224 -0
- package/src/wots.d.ts.map +1 -0
- package/src/wots.js +559 -0
- package/src/wots.js.map +1 -0
- package/src/wots.ts +636 -0
- package/test-vectors.json +45 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseMonitor
|
|
3
|
+
* Monitors lease expiry and triggers renewal/cleanup actions
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Automatic expiry detection with configurable thresholds
|
|
7
|
+
* - Event-based notifications for expiring leases
|
|
8
|
+
* - Adaptive monitoring interval based on shortest TTL
|
|
9
|
+
*/
|
|
10
|
+
import { DefaultTimerAdapter, NoopLogger } from '../adapters/index.js';
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
defaultIntervalMs: 5000,
|
|
13
|
+
expiryThresholdMs: 5000,
|
|
14
|
+
minIntervalMs: 1000,
|
|
15
|
+
maxIntervalMs: 30000,
|
|
16
|
+
};
|
|
17
|
+
export class LeaseMonitor {
|
|
18
|
+
constructor(leaseStore, timer = new DefaultTimerAdapter(), logger = new NoopLogger(), config = {}) {
|
|
19
|
+
this.timerHandle = null;
|
|
20
|
+
this.isRunning = false;
|
|
21
|
+
this.expiryCallbacks = new Set();
|
|
22
|
+
this.leaseStore = leaseStore;
|
|
23
|
+
this.timer = timer;
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
if (this.isRunning) {
|
|
29
|
+
this.logger.debug('LeaseMonitor already running');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.isRunning = true;
|
|
33
|
+
this.scheduleNextCheck();
|
|
34
|
+
this.logger.debug('LeaseMonitor started');
|
|
35
|
+
}
|
|
36
|
+
stop() {
|
|
37
|
+
if (!this.isRunning)
|
|
38
|
+
return;
|
|
39
|
+
this.isRunning = false;
|
|
40
|
+
if (this.timerHandle !== null) {
|
|
41
|
+
this.timer.clearTimeout(this.timerHandle);
|
|
42
|
+
this.timerHandle = null;
|
|
43
|
+
}
|
|
44
|
+
this.logger.debug('LeaseMonitor stopped');
|
|
45
|
+
}
|
|
46
|
+
isActive() {
|
|
47
|
+
return this.isRunning;
|
|
48
|
+
}
|
|
49
|
+
onExpirySoon(callback) {
|
|
50
|
+
this.expiryCallbacks.add(callback);
|
|
51
|
+
return () => this.expiryCallbacks.delete(callback);
|
|
52
|
+
}
|
|
53
|
+
removeAllListeners() {
|
|
54
|
+
this.expiryCallbacks.clear();
|
|
55
|
+
}
|
|
56
|
+
async checkNow() {
|
|
57
|
+
const now = this.timer.now();
|
|
58
|
+
const expiringSoon = this.leaseStore.getExpiringSoon(this.config.expiryThresholdMs);
|
|
59
|
+
const events = expiringSoon.map(lease => ({
|
|
60
|
+
leaseId: lease.leaseId,
|
|
61
|
+
expiresAt: lease.expiresAt,
|
|
62
|
+
remainingMs: Math.max(0, lease.expiresAt - now),
|
|
63
|
+
lease,
|
|
64
|
+
}));
|
|
65
|
+
for (const event of events) {
|
|
66
|
+
this.notifyExpirySoon(event);
|
|
67
|
+
}
|
|
68
|
+
await this.leaseStore.cleanupExpired();
|
|
69
|
+
return events;
|
|
70
|
+
}
|
|
71
|
+
scheduleNextCheck() {
|
|
72
|
+
if (!this.isRunning)
|
|
73
|
+
return;
|
|
74
|
+
const interval = this.calculateInterval();
|
|
75
|
+
this.timerHandle = this.timer.setTimeout(() => {
|
|
76
|
+
this.runCheck();
|
|
77
|
+
}, interval);
|
|
78
|
+
this.logger.debug(`Next lease check scheduled in ${interval}ms`);
|
|
79
|
+
}
|
|
80
|
+
async runCheck() {
|
|
81
|
+
if (!this.isRunning)
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
await this.checkNow();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
this.logger.error('Error during lease check:', error);
|
|
88
|
+
}
|
|
89
|
+
this.scheduleNextCheck();
|
|
90
|
+
}
|
|
91
|
+
calculateInterval() {
|
|
92
|
+
const storeInterval = this.leaseStore.calculateMonitoringInterval();
|
|
93
|
+
const interval = Math.max(this.config.minIntervalMs, Math.min(storeInterval, this.config.maxIntervalMs));
|
|
94
|
+
return interval;
|
|
95
|
+
}
|
|
96
|
+
notifyExpirySoon(event) {
|
|
97
|
+
this.logger.debug(`Lease expiring soon: ${event.leaseId}, remaining: ${event.remainingMs}ms`);
|
|
98
|
+
this.expiryCallbacks.forEach(callback => {
|
|
99
|
+
try {
|
|
100
|
+
callback(event);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
this.logger.error('Error in expiry callback:', error);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseStore
|
|
3
|
+
* WOTS lease lifecycle management with platform-agnostic storage
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters/index.js';
|
|
13
|
+
export interface WotsIndices {
|
|
14
|
+
addressIndex: number;
|
|
15
|
+
l1: number;
|
|
16
|
+
l2: number;
|
|
17
|
+
}
|
|
18
|
+
export type LeaseStatus = 'pending' | 'active' | 'expired' | 'finalized' | 'cancelled';
|
|
19
|
+
export interface StoredLease {
|
|
20
|
+
leaseId: string;
|
|
21
|
+
leaseToken: string;
|
|
22
|
+
indices: WotsIndices;
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
status: LeaseStatus;
|
|
25
|
+
createdAt: number;
|
|
26
|
+
txId?: string;
|
|
27
|
+
leaseTTL: number;
|
|
28
|
+
}
|
|
29
|
+
export interface LeaseStoreConfig {
|
|
30
|
+
storageKey?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class LeaseStore {
|
|
33
|
+
private leases;
|
|
34
|
+
private _initialized;
|
|
35
|
+
private readonly storageKey;
|
|
36
|
+
private readonly storage;
|
|
37
|
+
private readonly logger;
|
|
38
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter, config?: LeaseStoreConfig);
|
|
39
|
+
initialize(): Promise<void>;
|
|
40
|
+
isInitialized(): boolean;
|
|
41
|
+
private load;
|
|
42
|
+
private persist;
|
|
43
|
+
save(lease: StoredLease): Promise<void>;
|
|
44
|
+
get(leaseId: string): StoredLease | undefined;
|
|
45
|
+
getByToken(leaseToken: string): StoredLease | undefined;
|
|
46
|
+
getAll(): StoredLease[];
|
|
47
|
+
getActive(): StoredLease[];
|
|
48
|
+
getExpiringSoon(thresholdMs?: number): StoredLease[];
|
|
49
|
+
delete(leaseId: string): Promise<boolean>;
|
|
50
|
+
deleteByToken(leaseToken: string): Promise<boolean>;
|
|
51
|
+
updateStatus(leaseId: string, status: LeaseStatus): Promise<void>;
|
|
52
|
+
cleanupExpired(): Promise<number>;
|
|
53
|
+
clear(): Promise<void>;
|
|
54
|
+
getMinimumTTL(): number | null;
|
|
55
|
+
calculateMonitoringInterval(): number;
|
|
56
|
+
private flattenIndex;
|
|
57
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module LeaseStore
|
|
3
|
+
* WOTS lease lifecycle management with platform-agnostic storage
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import { NoopLogger } from '../adapters/index.js';
|
|
13
|
+
const DEFAULT_STORAGE_KEY = 'totem_wots_leases';
|
|
14
|
+
export class LeaseStore {
|
|
15
|
+
constructor(storage, logger = new NoopLogger(), config = {}) {
|
|
16
|
+
this.leases = new Map();
|
|
17
|
+
this._initialized = false;
|
|
18
|
+
this.storage = storage;
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
21
|
+
}
|
|
22
|
+
async initialize() {
|
|
23
|
+
if (this._initialized)
|
|
24
|
+
return;
|
|
25
|
+
await this.load();
|
|
26
|
+
await this.cleanupExpired();
|
|
27
|
+
this._initialized = true;
|
|
28
|
+
}
|
|
29
|
+
isInitialized() {
|
|
30
|
+
return this._initialized;
|
|
31
|
+
}
|
|
32
|
+
async load() {
|
|
33
|
+
try {
|
|
34
|
+
const leasesArray = await this.storage.get(this.storageKey);
|
|
35
|
+
if (leasesArray) {
|
|
36
|
+
this.leases = new Map(leasesArray.map(lease => [lease.leaseId, lease]));
|
|
37
|
+
this.logger.debug(`Loaded ${this.leases.size} leases from storage`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.logger.error('Failed to load leases:', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async persist() {
|
|
45
|
+
try {
|
|
46
|
+
const leasesArray = Array.from(this.leases.values());
|
|
47
|
+
await this.storage.set(this.storageKey, leasesArray);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.logger.error('Failed to persist leases:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async save(lease) {
|
|
55
|
+
const flatIndex = this.flattenIndex(lease.indices);
|
|
56
|
+
this.logger.debug(`Storing lease: ${lease.leaseId}`);
|
|
57
|
+
this.logger.debug(` indices: (addressIndex=${lease.indices.addressIndex}, l1=${lease.indices.l1}, l2=${lease.indices.l2}) [${flatIndex}/262,144]`);
|
|
58
|
+
this.logger.debug(` status: ${lease.status}, TTL: ${lease.leaseTTL}ms`);
|
|
59
|
+
this.leases.set(lease.leaseId, lease);
|
|
60
|
+
await this.persist();
|
|
61
|
+
}
|
|
62
|
+
get(leaseId) {
|
|
63
|
+
return this.leases.get(leaseId);
|
|
64
|
+
}
|
|
65
|
+
getByToken(leaseToken) {
|
|
66
|
+
return Array.from(this.leases.values()).find(lease => lease.leaseToken === leaseToken);
|
|
67
|
+
}
|
|
68
|
+
getAll() {
|
|
69
|
+
return Array.from(this.leases.values());
|
|
70
|
+
}
|
|
71
|
+
getActive() {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
return this.getAll().filter(lease => lease.status === 'active' && lease.expiresAt > now);
|
|
74
|
+
}
|
|
75
|
+
getExpiringSoon(thresholdMs = 5000) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
return this.getAll().filter(lease => lease.status === 'active' &&
|
|
78
|
+
lease.expiresAt > now &&
|
|
79
|
+
lease.expiresAt <= now + thresholdMs);
|
|
80
|
+
}
|
|
81
|
+
async delete(leaseId) {
|
|
82
|
+
const lease = this.leases.get(leaseId);
|
|
83
|
+
if (lease) {
|
|
84
|
+
this.logger.debug(`Deleting lease: ${leaseId}, final status: ${lease.status}`);
|
|
85
|
+
}
|
|
86
|
+
const existed = this.leases.delete(leaseId);
|
|
87
|
+
if (existed) {
|
|
88
|
+
await this.persist();
|
|
89
|
+
}
|
|
90
|
+
return existed;
|
|
91
|
+
}
|
|
92
|
+
async deleteByToken(leaseToken) {
|
|
93
|
+
const lease = this.getByToken(leaseToken);
|
|
94
|
+
if (lease) {
|
|
95
|
+
return this.delete(lease.leaseId);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
async updateStatus(leaseId, status) {
|
|
100
|
+
const lease = this.leases.get(leaseId);
|
|
101
|
+
if (lease) {
|
|
102
|
+
const oldStatus = lease.status;
|
|
103
|
+
this.logger.debug(`Lease status change: ${leaseId} ${oldStatus} → ${status}`);
|
|
104
|
+
lease.status = status;
|
|
105
|
+
await this.persist();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async cleanupExpired() {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
const expiredLeaseIds = [];
|
|
111
|
+
for (const [leaseId, lease] of this.leases.entries()) {
|
|
112
|
+
if (lease.expiresAt <= now && lease.status !== 'finalized') {
|
|
113
|
+
lease.status = 'expired';
|
|
114
|
+
expiredLeaseIds.push(leaseId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (expiredLeaseIds.length > 0) {
|
|
118
|
+
this.logger.debug(`Marked ${expiredLeaseIds.length} leases as expired`);
|
|
119
|
+
await this.persist();
|
|
120
|
+
}
|
|
121
|
+
return expiredLeaseIds.length;
|
|
122
|
+
}
|
|
123
|
+
async clear() {
|
|
124
|
+
this.leases.clear();
|
|
125
|
+
await this.storage.remove(this.storageKey);
|
|
126
|
+
this.logger.debug('Cleared all leases');
|
|
127
|
+
}
|
|
128
|
+
getMinimumTTL() {
|
|
129
|
+
const activeLeases = this.getActive();
|
|
130
|
+
if (activeLeases.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
return Math.min(...activeLeases.map(lease => lease.leaseTTL));
|
|
133
|
+
}
|
|
134
|
+
calculateMonitoringInterval() {
|
|
135
|
+
const minTTL = this.getMinimumTTL();
|
|
136
|
+
if (minTTL === null)
|
|
137
|
+
return 5000;
|
|
138
|
+
const calculatedInterval = Math.min(minTTL / 4, 5000);
|
|
139
|
+
return Math.max(calculatedInterval, 1000);
|
|
140
|
+
}
|
|
141
|
+
flattenIndex(indices) {
|
|
142
|
+
return (indices.addressIndex * 64 * 64) + (indices.l1 * 64) + indices.l2;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module WatermarkStore
|
|
3
|
+
* WOTS index tracking with server synchronization
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache (or creates default)
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters/index.js';
|
|
13
|
+
export interface WotsIndices {
|
|
14
|
+
addressIndex: number;
|
|
15
|
+
l1: number;
|
|
16
|
+
l2: number;
|
|
17
|
+
}
|
|
18
|
+
export interface WatermarkState {
|
|
19
|
+
next_addressIndex: number;
|
|
20
|
+
next_l1: number;
|
|
21
|
+
next_l2: number;
|
|
22
|
+
usedIndices: Array<[number, number, number]>;
|
|
23
|
+
lastSyncTimestamp?: number;
|
|
24
|
+
serverWatermark?: WotsIndices;
|
|
25
|
+
}
|
|
26
|
+
export interface SyncResult {
|
|
27
|
+
updated: boolean;
|
|
28
|
+
drift: number;
|
|
29
|
+
hasConflict: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface WatermarkStoreConfig {
|
|
32
|
+
storageKey?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class WatermarkStore {
|
|
35
|
+
private state;
|
|
36
|
+
private _initialized;
|
|
37
|
+
private readonly storageKey;
|
|
38
|
+
private readonly storage;
|
|
39
|
+
private readonly logger;
|
|
40
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter, config?: WatermarkStoreConfig);
|
|
41
|
+
initialize(): Promise<WatermarkState>;
|
|
42
|
+
isInitialized(): boolean;
|
|
43
|
+
load(): Promise<WatermarkState | null>;
|
|
44
|
+
save(watermark: WatermarkState): Promise<void>;
|
|
45
|
+
clear(): Promise<void>;
|
|
46
|
+
getCurrent(): WatermarkState | null;
|
|
47
|
+
getNextIndices(): WotsIndices | null;
|
|
48
|
+
isExhausted(): boolean;
|
|
49
|
+
hasAvailableIndices(): boolean;
|
|
50
|
+
markUsed(indices: WotsIndices): Promise<void>;
|
|
51
|
+
advanceWatermark(indices: WotsIndices): Promise<void>;
|
|
52
|
+
updateFromServer(serverWatermark: WotsIndices): Promise<SyncResult>;
|
|
53
|
+
getUsageStats(): {
|
|
54
|
+
used: number;
|
|
55
|
+
total: number;
|
|
56
|
+
percentage: number;
|
|
57
|
+
};
|
|
58
|
+
private calculateNextIndices;
|
|
59
|
+
private shouldUseServerWatermark;
|
|
60
|
+
private flattenIndex;
|
|
61
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module WatermarkStore
|
|
3
|
+
* WOTS index tracking with server synchronization
|
|
4
|
+
*
|
|
5
|
+
* Uses in-memory cache backed by persistent storage.
|
|
6
|
+
* Pattern:
|
|
7
|
+
* - initialize() loads from storage into memory cache (or creates default)
|
|
8
|
+
* - Sync getters read from memory cache (fast, no I/O)
|
|
9
|
+
* - Mutators update memory cache AND persist to storage
|
|
10
|
+
* - Caller MUST call initialize() before using sync getters
|
|
11
|
+
*/
|
|
12
|
+
import { NoopLogger } from '../adapters/index.js';
|
|
13
|
+
const DEFAULT_STORAGE_KEY = 'totem_wots_watermark';
|
|
14
|
+
const MAX_INDEX_PER_LEVEL = 64;
|
|
15
|
+
const TOTAL_INDICES = 64 * 64 * 64;
|
|
16
|
+
export class WatermarkStore {
|
|
17
|
+
constructor(storage, logger = new NoopLogger(), config = {}) {
|
|
18
|
+
this.state = null;
|
|
19
|
+
this._initialized = false;
|
|
20
|
+
this.storage = storage;
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
23
|
+
}
|
|
24
|
+
async initialize() {
|
|
25
|
+
if (this._initialized && this.state) {
|
|
26
|
+
return this.state;
|
|
27
|
+
}
|
|
28
|
+
const existing = await this.load();
|
|
29
|
+
if (existing) {
|
|
30
|
+
this._initialized = true;
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
const initialState = {
|
|
34
|
+
next_addressIndex: 0,
|
|
35
|
+
next_l1: 0,
|
|
36
|
+
next_l2: 0,
|
|
37
|
+
usedIndices: []
|
|
38
|
+
};
|
|
39
|
+
await this.save(initialState);
|
|
40
|
+
this._initialized = true;
|
|
41
|
+
return initialState;
|
|
42
|
+
}
|
|
43
|
+
isInitialized() {
|
|
44
|
+
return this._initialized;
|
|
45
|
+
}
|
|
46
|
+
async load() {
|
|
47
|
+
try {
|
|
48
|
+
const watermark = await this.storage.get(this.storageKey);
|
|
49
|
+
if (watermark) {
|
|
50
|
+
this.state = watermark;
|
|
51
|
+
this.logger.debug('Loaded watermark from storage');
|
|
52
|
+
return this.state;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.logger.error('Failed to load watermark:', error);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async save(watermark) {
|
|
62
|
+
try {
|
|
63
|
+
this.state = watermark;
|
|
64
|
+
await this.storage.set(this.storageKey, watermark);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.logger.error('Failed to save watermark:', error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async clear() {
|
|
72
|
+
try {
|
|
73
|
+
this.state = null;
|
|
74
|
+
this._initialized = false;
|
|
75
|
+
await this.storage.remove(this.storageKey);
|
|
76
|
+
this.logger.debug('Cleared watermark');
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
this.logger.error('Failed to clear watermark:', error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
getCurrent() {
|
|
84
|
+
return this.state;
|
|
85
|
+
}
|
|
86
|
+
getNextIndices() {
|
|
87
|
+
if (!this.state || this.isExhausted()) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
addressIndex: this.state.next_addressIndex,
|
|
92
|
+
l1: this.state.next_l1,
|
|
93
|
+
l2: this.state.next_l2
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
isExhausted() {
|
|
97
|
+
if (!this.state)
|
|
98
|
+
return false;
|
|
99
|
+
return this.state.next_addressIndex >= MAX_INDEX_PER_LEVEL;
|
|
100
|
+
}
|
|
101
|
+
hasAvailableIndices() {
|
|
102
|
+
return !this.isExhausted();
|
|
103
|
+
}
|
|
104
|
+
async markUsed(indices) {
|
|
105
|
+
if (!this.state) {
|
|
106
|
+
await this.load();
|
|
107
|
+
}
|
|
108
|
+
if (!this.state) {
|
|
109
|
+
throw new Error('Watermark not initialized');
|
|
110
|
+
}
|
|
111
|
+
const indexTuple = [indices.addressIndex, indices.l1, indices.l2];
|
|
112
|
+
const flatIndex = this.flattenIndex(indices.addressIndex, indices.l1, indices.l2);
|
|
113
|
+
const alreadyUsed = this.state.usedIndices.some(([addressIndex, l1, l2]) => addressIndex === indices.addressIndex && l1 === indices.l1 && l2 === indices.l2);
|
|
114
|
+
if (!alreadyUsed) {
|
|
115
|
+
this.logger.debug(`Marking indices as USED: (addressIndex=${indices.addressIndex}, l1=${indices.l1}, l2=${indices.l2}) [${flatIndex}/${TOTAL_INDICES}]`);
|
|
116
|
+
this.logger.debug(` total used: ${this.state.usedIndices.length + 1} / ${TOTAL_INDICES}`);
|
|
117
|
+
this.state.usedIndices.push(indexTuple);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
this.logger.warn(`Indices already marked as used: (addressIndex=${indices.addressIndex}, l1=${indices.l1}, l2=${indices.l2})`);
|
|
121
|
+
}
|
|
122
|
+
await this.save(this.state);
|
|
123
|
+
}
|
|
124
|
+
async advanceWatermark(indices) {
|
|
125
|
+
if (!this.state) {
|
|
126
|
+
await this.load();
|
|
127
|
+
}
|
|
128
|
+
if (!this.state) {
|
|
129
|
+
throw new Error('Watermark not initialized');
|
|
130
|
+
}
|
|
131
|
+
const currentFlat = this.flattenIndex(indices.addressIndex, indices.l1, indices.l2);
|
|
132
|
+
const nextIndices = this.calculateNextIndices(indices);
|
|
133
|
+
const nextFlat = this.flattenIndex(nextIndices.addressIndex, nextIndices.l1, nextIndices.l2);
|
|
134
|
+
this.logger.debug(`Advancing watermark...`);
|
|
135
|
+
this.logger.debug(` current: (addressIndex=${indices.addressIndex}, l1=${indices.l1}, l2=${indices.l2}) [${currentFlat}/${TOTAL_INDICES}]`);
|
|
136
|
+
this.logger.debug(` next: (addressIndex=${nextIndices.addressIndex}, l1=${nextIndices.l1}, l2=${nextIndices.l2}) [${nextFlat}/${TOTAL_INDICES}]`);
|
|
137
|
+
this.state.next_addressIndex = nextIndices.addressIndex;
|
|
138
|
+
this.state.next_l1 = nextIndices.l1;
|
|
139
|
+
this.state.next_l2 = nextIndices.l2;
|
|
140
|
+
await this.save(this.state);
|
|
141
|
+
this.logger.debug('Watermark advanced and persisted');
|
|
142
|
+
}
|
|
143
|
+
async updateFromServer(serverWatermark) {
|
|
144
|
+
if (!this.state) {
|
|
145
|
+
await this.load();
|
|
146
|
+
}
|
|
147
|
+
if (!this.state) {
|
|
148
|
+
await this.initialize();
|
|
149
|
+
}
|
|
150
|
+
const localIndexFlat = this.flattenIndex(this.state.next_addressIndex, this.state.next_l1, this.state.next_l2);
|
|
151
|
+
const serverIndexFlat = this.flattenIndex(serverWatermark.addressIndex, serverWatermark.l1, serverWatermark.l2);
|
|
152
|
+
const drift = serverIndexFlat - localIndexFlat;
|
|
153
|
+
const hasConflict = drift > 1;
|
|
154
|
+
const needsUpdate = this.shouldUseServerWatermark(this.state, serverWatermark);
|
|
155
|
+
if (needsUpdate) {
|
|
156
|
+
this.logger.debug(`Updating watermark from server (drift: ${drift})`);
|
|
157
|
+
this.state.next_addressIndex = serverWatermark.addressIndex;
|
|
158
|
+
this.state.next_l1 = serverWatermark.l1;
|
|
159
|
+
this.state.next_l2 = serverWatermark.l2;
|
|
160
|
+
this.state.serverWatermark = serverWatermark;
|
|
161
|
+
this.state.lastSyncTimestamp = Date.now();
|
|
162
|
+
await this.save(this.state);
|
|
163
|
+
return { updated: true, drift, hasConflict };
|
|
164
|
+
}
|
|
165
|
+
this.state.lastSyncTimestamp = Date.now();
|
|
166
|
+
await this.save(this.state);
|
|
167
|
+
return { updated: false, drift, hasConflict };
|
|
168
|
+
}
|
|
169
|
+
getUsageStats() {
|
|
170
|
+
const used = this.state?.usedIndices.length ?? 0;
|
|
171
|
+
return {
|
|
172
|
+
used,
|
|
173
|
+
total: TOTAL_INDICES,
|
|
174
|
+
percentage: (used / TOTAL_INDICES) * 100
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
calculateNextIndices(current) {
|
|
178
|
+
let { addressIndex, l1, l2 } = current;
|
|
179
|
+
l2++;
|
|
180
|
+
if (l2 >= MAX_INDEX_PER_LEVEL) {
|
|
181
|
+
l2 = 0;
|
|
182
|
+
l1++;
|
|
183
|
+
if (l1 >= MAX_INDEX_PER_LEVEL) {
|
|
184
|
+
l1 = 0;
|
|
185
|
+
addressIndex++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return { addressIndex, l1, l2 };
|
|
189
|
+
}
|
|
190
|
+
shouldUseServerWatermark(local, server) {
|
|
191
|
+
if (server.addressIndex > local.next_addressIndex)
|
|
192
|
+
return true;
|
|
193
|
+
if (server.addressIndex === local.next_addressIndex && server.l1 > local.next_l1)
|
|
194
|
+
return true;
|
|
195
|
+
if (server.addressIndex === local.next_addressIndex && server.l1 === local.next_l1 && server.l2 > local.next_l2)
|
|
196
|
+
return true;
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
flattenIndex(addressIndex, l1, l2) {
|
|
200
|
+
return (addressIndex * MAX_INDEX_PER_LEVEL * MAX_INDEX_PER_LEVEL) + (l1 * MAX_INDEX_PER_LEVEL) + l2;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/core/lease
|
|
3
|
+
* WOTS lease lifecycle management
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* - LeaseStore: Persistent storage for lease records with in-memory cache
|
|
7
|
+
* - WatermarkStore: WOTS index tracking with server sync
|
|
8
|
+
* - LeaseMonitor: Expiry monitoring and renewal
|
|
9
|
+
*/
|
|
10
|
+
export { LeaseStore, type StoredLease, type LeaseStatus, type LeaseStoreConfig, type WotsIndices as LeaseWotsIndices, } from './LeaseStore.js';
|
|
11
|
+
export { WatermarkStore, type WatermarkState, type SyncResult, type WatermarkStoreConfig, } from './WatermarkStore.js';
|
|
12
|
+
export type { WotsIndices } from './WatermarkStore.js';
|
|
13
|
+
export { LeaseMonitor, type LeaseExpiryEvent, type LeaseExpiryCallback, type LeaseMonitorConfig, } from './LeaseMonitor.js';
|
|
14
|
+
export { prepareLease, finalizeLease, flatIndexFromLanes, type PrepareArgs, type PrepareResp } from '../lease-client.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/core/lease
|
|
3
|
+
* WOTS lease lifecycle management
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* - LeaseStore: Persistent storage for lease records with in-memory cache
|
|
7
|
+
* - WatermarkStore: WOTS index tracking with server sync
|
|
8
|
+
* - LeaseMonitor: Expiry monitoring and renewal
|
|
9
|
+
*/
|
|
10
|
+
export { LeaseStore, } from './LeaseStore.js';
|
|
11
|
+
export { WatermarkStore, } from './WatermarkStore.js';
|
|
12
|
+
export { LeaseMonitor, } from './LeaseMonitor.js';
|
|
13
|
+
export { prepareLease, finalizeLease, flatIndexFromLanes } from '../lease-client.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Totem <-> Axia hardened WOTS helpers (no deps on server internals).
|
|
3
|
+
*/
|
|
4
|
+
export type PrepareArgs = {
|
|
5
|
+
txId: string;
|
|
6
|
+
rootPublicKey: string;
|
|
7
|
+
to: string;
|
|
8
|
+
amount: string;
|
|
9
|
+
tokenId?: string;
|
|
10
|
+
burn?: string | null;
|
|
11
|
+
digestL2?: string | null;
|
|
12
|
+
digestL3?: string | null;
|
|
13
|
+
ttlMs?: number;
|
|
14
|
+
};
|
|
15
|
+
export type PrepareResp = {
|
|
16
|
+
leaseToken: string;
|
|
17
|
+
lease: {
|
|
18
|
+
addressIndex: number;
|
|
19
|
+
l1: number;
|
|
20
|
+
l2: number;
|
|
21
|
+
};
|
|
22
|
+
txId: string;
|
|
23
|
+
digestTx?: string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function prepareLease(apiUrl: string, apiKey: string, args: PrepareArgs): Promise<PrepareResp>;
|
|
26
|
+
export declare function finalizeLease(apiUrl: string, apiKey: string, leaseToken: string, signedHex: string): Promise<{
|
|
27
|
+
status: number;
|
|
28
|
+
body: string;
|
|
29
|
+
}>;
|
|
30
|
+
/** lane tuple -> flat WOTS index (64^3 space) */
|
|
31
|
+
export declare function flatIndexFromLanes(addressIndex: number, l1: number, l2: number): number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export async function prepareLease(apiUrl, apiKey, args) {
|
|
2
|
+
const res = await fetch(`${apiUrl}/v1/wots-hardened/prepare`, {
|
|
3
|
+
method: 'POST',
|
|
4
|
+
headers: { 'content-type': 'application/json', 'x-api-key': apiKey },
|
|
5
|
+
body: JSON.stringify({
|
|
6
|
+
tokenId: "0x00",
|
|
7
|
+
burn: null,
|
|
8
|
+
digestL2: null,
|
|
9
|
+
digestL3: null,
|
|
10
|
+
ttlMs: 20000,
|
|
11
|
+
...args
|
|
12
|
+
})
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok)
|
|
15
|
+
throw new Error(`prepareLease ${res.status}`);
|
|
16
|
+
const raw = await res.json();
|
|
17
|
+
const lease = raw.lease ?? { addressIndex: raw.addressIndex ?? 0, l1: raw.l1 ?? 0, l2: raw.l2 ?? 0 };
|
|
18
|
+
return {
|
|
19
|
+
leaseToken: raw.leaseToken,
|
|
20
|
+
lease,
|
|
21
|
+
txId: raw.txId,
|
|
22
|
+
digestTx: raw.digestTx ?? null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function finalizeLease(apiUrl, apiKey, leaseToken, signedHex) {
|
|
26
|
+
const res = await fetch(`${apiUrl}/v1/wots-hardened/finalize`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'content-type': 'application/json', 'x-api-key': apiKey },
|
|
29
|
+
body: JSON.stringify({ leaseToken, signedHex })
|
|
30
|
+
});
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
return { status: res.status, body: text };
|
|
33
|
+
}
|
|
34
|
+
/** lane tuple -> flat WOTS index (64^3 space) */
|
|
35
|
+
export function flatIndexFromLanes(addressIndex, l1, l2) {
|
|
36
|
+
if (addressIndex | l1 | l2 & ~63)
|
|
37
|
+
throw new Error('lane out of range');
|
|
38
|
+
return (addressIndex * 64 * 64) + (l1 * 64) + l2;
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function encodeMxRadix32Frame(frame: Uint8Array): string;
|
|
2
|
+
export declare function decodeMxRadix32Frame(mx: string): Uint8Array;
|
|
3
|
+
/** Encode raw 32 bytes to Mx address with frame and checksum */
|
|
4
|
+
export declare function makeMxAddress(root32: Uint8Array): string;
|
|
5
|
+
/** Decode Mx address to raw 32 bytes, validating frame and checksum */
|
|
6
|
+
export declare function parseMxAddress(mx: string): Uint8Array;
|
|
7
|
+
/** Make an Mx address from a hex string (legacy API compatibility) */
|
|
8
|
+
export declare function hexToMx(hex: string): string;
|
|
9
|
+
/** Convert an Mx address back to uppercase hex (legacy API compatibility) */
|
|
10
|
+
export declare function mxToHex(mx: string): string;
|
|
11
|
+
/** @deprecated Use makeMxAddress */
|
|
12
|
+
export declare const encodeMx: typeof makeMxAddress;
|
|
13
|
+
/** @deprecated Use parseMxAddress */
|
|
14
|
+
export declare const decodeMx: typeof parseMxAddress;
|
|
15
|
+
/** @deprecated Use hexToMx */
|
|
16
|
+
export declare const makeMinimaAddress: typeof hexToMx;
|
|
17
|
+
/** @deprecated Use mxToHex */
|
|
18
|
+
export declare const convertMinimaAddress: typeof mxToHex;
|