@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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionLifecycle
|
|
3
|
+
* Orchestrates the complete transaction flow with lease and watermark management
|
|
4
|
+
*
|
|
5
|
+
* Integrates:
|
|
6
|
+
* - TransactionService for RPC calls
|
|
7
|
+
* - LeaseStore for lease persistence
|
|
8
|
+
* - WatermarkStore for index tracking
|
|
9
|
+
* - TransactionReceiptStore for history
|
|
10
|
+
*/
|
|
11
|
+
import type { LoggerAdapter, MetricsAdapter } from '../adapters/index.js';
|
|
12
|
+
import type { LeaseStore, WatermarkStore } from '../lease/index.js';
|
|
13
|
+
import type { TransactionService } from './TransactionService.js';
|
|
14
|
+
import type { TransactionReceiptStore } from './TransactionReceiptStore.js';
|
|
15
|
+
import type { PrepareRequest, PrepareResponse, FinalizeResponse, TransactionMetadata, SignResult } from './types.js';
|
|
16
|
+
import type { WotsSigningDependencies } from './TransactionService.js';
|
|
17
|
+
export declare class TransactionLifecycleError extends Error {
|
|
18
|
+
code: number;
|
|
19
|
+
userMessage: string;
|
|
20
|
+
constructor(message: string, code: number, userMessage: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class WatermarkExhaustedError extends Error {
|
|
23
|
+
constructor();
|
|
24
|
+
}
|
|
25
|
+
export interface TransactionLifecycleConfig {
|
|
26
|
+
validateWatermarkBeforePrepare?: boolean;
|
|
27
|
+
syncWatermarkBeforePrepare?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface WatermarkSyncFunction {
|
|
30
|
+
(rootPublicKey: string): Promise<{
|
|
31
|
+
updated: boolean;
|
|
32
|
+
multiDeviceConflict: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface PrepareResult extends PrepareResponse {
|
|
36
|
+
metadata: TransactionMetadata;
|
|
37
|
+
}
|
|
38
|
+
export declare class TransactionLifecycle {
|
|
39
|
+
private readonly txService;
|
|
40
|
+
private readonly leaseStore;
|
|
41
|
+
private readonly watermarkStore;
|
|
42
|
+
private readonly receiptStore;
|
|
43
|
+
private readonly logger;
|
|
44
|
+
private readonly metrics;
|
|
45
|
+
private readonly config;
|
|
46
|
+
private syncWatermark?;
|
|
47
|
+
constructor(txService: TransactionService, leaseStore: LeaseStore, watermarkStore: WatermarkStore, receiptStore: TransactionReceiptStore, logger?: LoggerAdapter, metrics?: MetricsAdapter, config?: TransactionLifecycleConfig);
|
|
48
|
+
setSyncWatermarkFunction(fn: WatermarkSyncFunction): void;
|
|
49
|
+
prepare(params: PrepareRequest, rootPublicKey: string): Promise<PrepareResult>;
|
|
50
|
+
sign(prepareResult: PrepareResult, seed: Uint8Array, deps: WotsSigningDependencies, paramSetName?: string): Promise<SignResult>;
|
|
51
|
+
finalize(leaseToken: string, signedHex: string, metadata: TransactionMetadata): Promise<FinalizeResponse>;
|
|
52
|
+
cancelLease(leaseToken: string): Promise<void>;
|
|
53
|
+
private validateWatermark;
|
|
54
|
+
private parseAxiaError;
|
|
55
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionLifecycle
|
|
3
|
+
* Orchestrates the complete transaction flow with lease and watermark management
|
|
4
|
+
*
|
|
5
|
+
* Integrates:
|
|
6
|
+
* - TransactionService for RPC calls
|
|
7
|
+
* - LeaseStore for lease persistence
|
|
8
|
+
* - WatermarkStore for index tracking
|
|
9
|
+
* - TransactionReceiptStore for history
|
|
10
|
+
*/
|
|
11
|
+
import { NoopLogger, NoopMetrics } from '../adapters/index.js';
|
|
12
|
+
export class TransactionLifecycleError extends Error {
|
|
13
|
+
constructor(message, code, userMessage) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.userMessage = userMessage;
|
|
17
|
+
this.name = 'TransactionLifecycleError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class WatermarkExhaustedError extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super('All WOTS signatures have been used. Please create a new wallet.');
|
|
23
|
+
this.name = 'WatermarkExhaustedError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class TransactionLifecycle {
|
|
27
|
+
constructor(txService, leaseStore, watermarkStore, receiptStore, logger = new NoopLogger(), metrics = new NoopMetrics(), config = {}) {
|
|
28
|
+
this.txService = txService;
|
|
29
|
+
this.leaseStore = leaseStore;
|
|
30
|
+
this.watermarkStore = watermarkStore;
|
|
31
|
+
this.receiptStore = receiptStore;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
this.metrics = metrics;
|
|
34
|
+
this.config = {
|
|
35
|
+
validateWatermarkBeforePrepare: config.validateWatermarkBeforePrepare ?? true,
|
|
36
|
+
syncWatermarkBeforePrepare: config.syncWatermarkBeforePrepare ?? true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
setSyncWatermarkFunction(fn) {
|
|
40
|
+
this.syncWatermark = fn;
|
|
41
|
+
}
|
|
42
|
+
async prepare(params, rootPublicKey) {
|
|
43
|
+
if (this.config.validateWatermarkBeforePrepare) {
|
|
44
|
+
await this.validateWatermark(rootPublicKey);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const result = await this.txService.prepare(params, rootPublicKey);
|
|
48
|
+
await this.leaseStore.save({
|
|
49
|
+
leaseId: result.leaseId,
|
|
50
|
+
leaseToken: result.leaseToken,
|
|
51
|
+
indices: { addressIndex: result.addressIndex, l1: result.l1, l2: result.l2 },
|
|
52
|
+
expiresAt: Date.now() + result.leaseTTL,
|
|
53
|
+
status: 'active',
|
|
54
|
+
createdAt: Date.now(),
|
|
55
|
+
txId: result.txId,
|
|
56
|
+
leaseTTL: result.leaseTTL,
|
|
57
|
+
});
|
|
58
|
+
this.logger.debug(`Lease persisted: ${result.leaseId}`);
|
|
59
|
+
return {
|
|
60
|
+
...result,
|
|
61
|
+
metadata: {
|
|
62
|
+
to: params.to,
|
|
63
|
+
amount: params.amount,
|
|
64
|
+
tokenId: params.tokenId || '0x00',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const parsed = this.parseAxiaError(error);
|
|
70
|
+
throw new TransactionLifecycleError(error.message, parsed.code, parsed.userMessage);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async sign(prepareResult, seed, deps, paramSetName) {
|
|
74
|
+
const request = {
|
|
75
|
+
addressIndex: prepareResult.addressIndex,
|
|
76
|
+
l1: prepareResult.l1,
|
|
77
|
+
l2: prepareResult.l2,
|
|
78
|
+
digestTx: prepareResult.digestTx,
|
|
79
|
+
};
|
|
80
|
+
return this.txService.sign(request, seed, deps, paramSetName);
|
|
81
|
+
}
|
|
82
|
+
async finalize(leaseToken, signedHex, metadata) {
|
|
83
|
+
const lease = this.leaseStore.getByToken(leaseToken);
|
|
84
|
+
if (!lease) {
|
|
85
|
+
throw new TransactionLifecycleError('Lease not found', 404, 'Transaction lease not found. Please try again.');
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const result = await this.txService.finalize({ leaseToken, signedHex });
|
|
89
|
+
await this.watermarkStore.markUsed(lease.indices);
|
|
90
|
+
await this.watermarkStore.advanceWatermark(lease.indices);
|
|
91
|
+
await this.receiptStore.initialize();
|
|
92
|
+
await this.receiptStore.add({
|
|
93
|
+
txpowid: result.txpowid,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
to: metadata.to,
|
|
96
|
+
amount: metadata.amount,
|
|
97
|
+
tokenId: metadata.tokenId,
|
|
98
|
+
indices: lease.indices,
|
|
99
|
+
status: 'confirmed',
|
|
100
|
+
txId: lease.txId,
|
|
101
|
+
leaseId: lease.leaseId,
|
|
102
|
+
});
|
|
103
|
+
await this.leaseStore.updateStatus(lease.leaseId, 'finalized');
|
|
104
|
+
await this.leaseStore.delete(lease.leaseId);
|
|
105
|
+
this.logger.debug(`Transaction finalized: ${result.txpowid}`);
|
|
106
|
+
this.logger.debug('Watermark advanced and receipt stored');
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const parsed = this.parseAxiaError(error);
|
|
111
|
+
throw new TransactionLifecycleError(error.message, parsed.code, parsed.userMessage);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async cancelLease(leaseToken) {
|
|
115
|
+
const lease = this.leaseStore.getByToken(leaseToken);
|
|
116
|
+
if (lease) {
|
|
117
|
+
await this.leaseStore.updateStatus(lease.leaseId, 'cancelled');
|
|
118
|
+
await this.leaseStore.delete(lease.leaseId);
|
|
119
|
+
this.logger.debug(`Lease cancelled: ${lease.leaseId}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async validateWatermark(rootPublicKey) {
|
|
123
|
+
if (!this.watermarkStore.isInitialized()) {
|
|
124
|
+
await this.watermarkStore.initialize();
|
|
125
|
+
}
|
|
126
|
+
if (this.watermarkStore.isExhausted()) {
|
|
127
|
+
throw new WatermarkExhaustedError();
|
|
128
|
+
}
|
|
129
|
+
if (this.config.syncWatermarkBeforePrepare && this.syncWatermark) {
|
|
130
|
+
try {
|
|
131
|
+
this.logger.debug('Syncing watermark before transaction prepare...');
|
|
132
|
+
const syncResult = await this.syncWatermark(rootPublicKey);
|
|
133
|
+
if (syncResult.updated) {
|
|
134
|
+
this.logger.debug('Watermark synced before prepare');
|
|
135
|
+
}
|
|
136
|
+
if (syncResult.multiDeviceConflict) {
|
|
137
|
+
this.logger.warn('Multi-device conflict detected before prepare');
|
|
138
|
+
}
|
|
139
|
+
if (this.watermarkStore.isExhausted()) {
|
|
140
|
+
throw new WatermarkExhaustedError();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error instanceof WatermarkExhaustedError) {
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
this.logger.warn('Watermark sync failed before prepare:', error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
parseAxiaError(error) {
|
|
152
|
+
const message = error.message || error.toString();
|
|
153
|
+
if (error?.code === 429) {
|
|
154
|
+
const limit = error.limit ?? 10;
|
|
155
|
+
return {
|
|
156
|
+
code: 429,
|
|
157
|
+
userMessage: `Daily signing limit reached (${limit}/day). Resets at midnight UTC.`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (message.includes('403') || error.code === 403) {
|
|
161
|
+
return {
|
|
162
|
+
code: 403,
|
|
163
|
+
userMessage: 'Access denied. Your project may not have permission for this operation.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (message.includes('409') || error.code === 409) {
|
|
167
|
+
return {
|
|
168
|
+
code: 409,
|
|
169
|
+
userMessage: 'WOTS indices exhausted. Please create a new wallet.',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (message.includes('410') || error.code === 410) {
|
|
173
|
+
return {
|
|
174
|
+
code: 410,
|
|
175
|
+
userMessage: 'Transaction lease expired. Please try again.',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (message.includes('502') || error.code === 502) {
|
|
179
|
+
return {
|
|
180
|
+
code: 502,
|
|
181
|
+
userMessage: 'Failed to post transaction to network. Please try again.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
code: 500,
|
|
186
|
+
userMessage: 'An error occurred. Please try again.',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionReceiptStore
|
|
3
|
+
* Persistent storage for transaction receipts
|
|
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
|
+
*/
|
|
11
|
+
import type { StorageAdapter, LoggerAdapter } from '../adapters/index.js';
|
|
12
|
+
import type { TransactionReceipt } from './types.js';
|
|
13
|
+
export interface TransactionReceiptStoreConfig {
|
|
14
|
+
storageKey?: string;
|
|
15
|
+
maxReceipts?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class TransactionReceiptStore {
|
|
18
|
+
private receipts;
|
|
19
|
+
private _initialized;
|
|
20
|
+
private readonly storageKey;
|
|
21
|
+
private readonly maxReceipts;
|
|
22
|
+
private readonly storage;
|
|
23
|
+
private readonly logger;
|
|
24
|
+
constructor(storage: StorageAdapter, logger?: LoggerAdapter, config?: TransactionReceiptStoreConfig);
|
|
25
|
+
initialize(): Promise<void>;
|
|
26
|
+
isInitialized(): boolean;
|
|
27
|
+
private load;
|
|
28
|
+
private persist;
|
|
29
|
+
add(receipt: TransactionReceipt): Promise<void>;
|
|
30
|
+
getAll(): TransactionReceipt[];
|
|
31
|
+
getByTxpowid(txpowid: string): TransactionReceipt | undefined;
|
|
32
|
+
getRecent(count?: number): TransactionReceipt[];
|
|
33
|
+
updateStatus(txpowid: string, status: TransactionReceipt['status']): Promise<void>;
|
|
34
|
+
clear(): Promise<void>;
|
|
35
|
+
count(): number;
|
|
36
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionReceiptStore
|
|
3
|
+
* Persistent storage for transaction receipts
|
|
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
|
+
*/
|
|
11
|
+
import { NoopLogger } from '../adapters/index.js';
|
|
12
|
+
const DEFAULT_STORAGE_KEY = 'totem_transaction_receipts';
|
|
13
|
+
const DEFAULT_MAX_RECEIPTS = 1000;
|
|
14
|
+
export class TransactionReceiptStore {
|
|
15
|
+
constructor(storage, logger = new NoopLogger(), config = {}) {
|
|
16
|
+
this.receipts = [];
|
|
17
|
+
this._initialized = false;
|
|
18
|
+
this.storage = storage;
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
21
|
+
this.maxReceipts = config.maxReceipts ?? DEFAULT_MAX_RECEIPTS;
|
|
22
|
+
}
|
|
23
|
+
async initialize() {
|
|
24
|
+
if (this._initialized)
|
|
25
|
+
return;
|
|
26
|
+
await this.load();
|
|
27
|
+
this._initialized = true;
|
|
28
|
+
}
|
|
29
|
+
isInitialized() {
|
|
30
|
+
return this._initialized;
|
|
31
|
+
}
|
|
32
|
+
async load() {
|
|
33
|
+
try {
|
|
34
|
+
const receipts = await this.storage.get(this.storageKey);
|
|
35
|
+
if (receipts) {
|
|
36
|
+
this.receipts = receipts;
|
|
37
|
+
this.logger.debug(`Loaded ${this.receipts.length} transaction receipts`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.logger.error('Failed to load receipts:', error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async persist() {
|
|
45
|
+
try {
|
|
46
|
+
const trimmedReceipts = this.receipts.slice(-this.maxReceipts);
|
|
47
|
+
await this.storage.set(this.storageKey, trimmedReceipts);
|
|
48
|
+
this.receipts = trimmedReceipts;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.logger.error('Failed to persist receipts:', error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async add(receipt) {
|
|
56
|
+
this.receipts.push(receipt);
|
|
57
|
+
this.logger.debug(`Added receipt: ${receipt.txpowid}`);
|
|
58
|
+
await this.persist();
|
|
59
|
+
}
|
|
60
|
+
getAll() {
|
|
61
|
+
return [...this.receipts].reverse();
|
|
62
|
+
}
|
|
63
|
+
getByTxpowid(txpowid) {
|
|
64
|
+
return this.receipts.find(r => r.txpowid === txpowid);
|
|
65
|
+
}
|
|
66
|
+
getRecent(count = 50) {
|
|
67
|
+
return this.getAll().slice(0, count);
|
|
68
|
+
}
|
|
69
|
+
async updateStatus(txpowid, status) {
|
|
70
|
+
const receipt = this.receipts.find(r => r.txpowid === txpowid);
|
|
71
|
+
if (receipt) {
|
|
72
|
+
receipt.status = status;
|
|
73
|
+
this.logger.debug(`Updated receipt status: ${txpowid} → ${status}`);
|
|
74
|
+
await this.persist();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async clear() {
|
|
78
|
+
this.receipts = [];
|
|
79
|
+
await this.storage.remove(this.storageKey);
|
|
80
|
+
this.logger.debug('Cleared all receipts');
|
|
81
|
+
}
|
|
82
|
+
count() {
|
|
83
|
+
return this.receipts.length;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionService
|
|
3
|
+
* Handles WOTS signing flow: prepare → sign → finalize
|
|
4
|
+
*
|
|
5
|
+
* Platform-agnostic implementation using HttpClient adapter.
|
|
6
|
+
*
|
|
7
|
+
* Sign architecture (2026-02): uses per-address TreeKey (depth=3, keysPerLevel=64)
|
|
8
|
+
* matching Minima Wallet.java exactly. Each address has its own TreeKey derived
|
|
9
|
+
* from the wallet seed. Signing produces 3 proofs (Root→L1→L2→DATA).
|
|
10
|
+
*/
|
|
11
|
+
import type { HttpClient, LoggerAdapter, MetricsAdapter } from '../adapters/index.js';
|
|
12
|
+
import type { PrepareRequest, PrepareResponse, SignRequest, SignResult, FinalizeRequest, FinalizeResponse } from './types.js';
|
|
13
|
+
export interface TransactionServiceConfig {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
paramSet?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated WotsSigningDependencies is no longer used by TransactionService.sign().
|
|
20
|
+
* The service now derives everything from the seed and indices directly using
|
|
21
|
+
* the built-in TreeKey implementation. This interface is kept for backward compatibility
|
|
22
|
+
* only and will be removed in a future version.
|
|
23
|
+
*/
|
|
24
|
+
export interface WotsSigningDependencies {
|
|
25
|
+
wotsSign?: (seed: Uint8Array, index: number, message: Uint8Array, paramSet: any) => Uint8Array;
|
|
26
|
+
fromHex?: (hex: string) => Uint8Array;
|
|
27
|
+
getParamSet?: (name: string) => any;
|
|
28
|
+
defaultParamSet?: any;
|
|
29
|
+
}
|
|
30
|
+
export declare class TransactionService {
|
|
31
|
+
private readonly http;
|
|
32
|
+
private readonly logger;
|
|
33
|
+
private readonly metrics;
|
|
34
|
+
private readonly config;
|
|
35
|
+
constructor(http: HttpClient, config: TransactionServiceConfig, logger?: LoggerAdapter, metrics?: MetricsAdapter);
|
|
36
|
+
prepare(params: PrepareRequest, rootPublicKey: string): Promise<PrepareResponse>;
|
|
37
|
+
/**
|
|
38
|
+
* Sign a transaction using per-address TreeKey architecture.
|
|
39
|
+
*
|
|
40
|
+
* Produces 3 proofs (Root→L1→L2→DATA) matching Minima's TreeKey.sign() exactly.
|
|
41
|
+
*
|
|
42
|
+
* @param request - Indices and digestTx from the /prepare response
|
|
43
|
+
* @param seed - 32-byte wallet base seed (from mnemonic)
|
|
44
|
+
* @param _deps - Deprecated, unused. Pass null or omit.
|
|
45
|
+
* @param _paramSet - Deprecated, unused. TreeKey uses its own param set.
|
|
46
|
+
*/
|
|
47
|
+
sign(request: SignRequest, seed: Uint8Array, _deps?: WotsSigningDependencies | null, _paramSet?: string): Promise<SignResult>;
|
|
48
|
+
finalize(params: FinalizeRequest): Promise<FinalizeResponse>;
|
|
49
|
+
private bytesToHex;
|
|
50
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module TransactionService
|
|
3
|
+
* Handles WOTS signing flow: prepare → sign → finalize
|
|
4
|
+
*
|
|
5
|
+
* Platform-agnostic implementation using HttpClient adapter.
|
|
6
|
+
*
|
|
7
|
+
* Sign architecture (2026-02): uses per-address TreeKey (depth=3, keysPerLevel=64)
|
|
8
|
+
* matching Minima Wallet.java exactly. Each address has its own TreeKey derived
|
|
9
|
+
* from the wallet seed. Signing produces 3 proofs (Root→L1→L2→DATA).
|
|
10
|
+
*/
|
|
11
|
+
import { NoopLogger, NoopMetrics } from '../adapters/index.js';
|
|
12
|
+
import { createPerAddressTreeKey, serializeTreeSignature } from '../treekey.js';
|
|
13
|
+
import { serializeMMRProof } from '../mmr.js';
|
|
14
|
+
import { fromHex } from '../wots.js';
|
|
15
|
+
export class TransactionService {
|
|
16
|
+
constructor(http, config, logger = new NoopLogger(), metrics = new NoopMetrics()) {
|
|
17
|
+
this.http = http;
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.metrics = metrics;
|
|
21
|
+
}
|
|
22
|
+
async prepare(params, rootPublicKey) {
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
const url = `${this.config.baseUrl.replace(/\/$/, '')}/v1/wots-hardened/prepare`;
|
|
25
|
+
const txId = params.txId || `tx-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
26
|
+
this.logger.debug('Requesting lease for transaction...');
|
|
27
|
+
this.logger.debug(` txId: ${txId}, to: ${params.to}, amount: ${params.amount}`);
|
|
28
|
+
if (typeof params.addressIndex !== 'number' || params.addressIndex < 0 || params.addressIndex >= 64) {
|
|
29
|
+
throw new Error('params.addressIndex is required and must be 0-63');
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const response = await this.http.post(url, {
|
|
33
|
+
txId,
|
|
34
|
+
rootPublicKey,
|
|
35
|
+
to: params.to,
|
|
36
|
+
amount: params.amount,
|
|
37
|
+
tokenId: params.tokenId || '0x00',
|
|
38
|
+
burn: params.burn || null,
|
|
39
|
+
paramSet: this.config.paramSet || 'v2-spec',
|
|
40
|
+
addressIndex: params.addressIndex,
|
|
41
|
+
}, {
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
'x-api-key': this.config.apiKey,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
if (response.status === 429) {
|
|
48
|
+
const body = response.data;
|
|
49
|
+
const err = new Error(body?.error || 'Daily signing limit reached');
|
|
50
|
+
err.code = 429;
|
|
51
|
+
err.limit = body?.limit;
|
|
52
|
+
err.used = body?.used;
|
|
53
|
+
err.retryAfter = body?.retryAfter;
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const body = response.data;
|
|
58
|
+
const msg = body?.error || body?.message || `HTTP ${response.status}`;
|
|
59
|
+
const err = new Error(msg);
|
|
60
|
+
err.code = response.status;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
const result = response.data;
|
|
64
|
+
const elapsed = Date.now() - startTime;
|
|
65
|
+
this.metrics.timing('tx_prepare_duration_ms', elapsed);
|
|
66
|
+
this.metrics.increment('tx_prepare_success');
|
|
67
|
+
this.logger.debug(`Lease acquired (${elapsed}ms)`);
|
|
68
|
+
this.logger.debug(` leaseId: ${result.leaseId}`);
|
|
69
|
+
this.logger.debug(` indices: (addressIndex=${result.addressIndex}, l1=${result.l1}, l2=${result.l2})`);
|
|
70
|
+
this.logger.debug(` TTL: ${result.leaseTTL}ms`);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code === 429 || error.code === 400 || error.code === 403 || error.code === 409) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
this.metrics.increment('tx_prepare_error');
|
|
78
|
+
this.logger.error('Lease acquisition failed:', error);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sign a transaction using per-address TreeKey architecture.
|
|
84
|
+
*
|
|
85
|
+
* Produces 3 proofs (Root→L1→L2→DATA) matching Minima's TreeKey.sign() exactly.
|
|
86
|
+
*
|
|
87
|
+
* @param request - Indices and digestTx from the /prepare response
|
|
88
|
+
* @param seed - 32-byte wallet base seed (from mnemonic)
|
|
89
|
+
* @param _deps - Deprecated, unused. Pass null or omit.
|
|
90
|
+
* @param _paramSet - Deprecated, unused. TreeKey uses its own param set.
|
|
91
|
+
*/
|
|
92
|
+
async sign(request, seed, _deps, _paramSet) {
|
|
93
|
+
const startTime = Date.now();
|
|
94
|
+
const { addressIndex, l1, l2, digestTx } = request;
|
|
95
|
+
this.logger.debug('Starting per-address TreeKey signing...');
|
|
96
|
+
this.logger.debug(` indices: (addressIndex=${addressIndex}, l1=${l1}, l2=${l2})`);
|
|
97
|
+
if (addressIndex < 0 || addressIndex >= 64) {
|
|
98
|
+
throw new Error(`Invalid addressIndex: ${addressIndex}. Must be 0-63.`);
|
|
99
|
+
}
|
|
100
|
+
const digestBytes = fromHex(digestTx);
|
|
101
|
+
if (digestBytes.length !== 32) {
|
|
102
|
+
throw new Error(`Invalid digest length: ${digestBytes.length}, expected 32 bytes`);
|
|
103
|
+
}
|
|
104
|
+
// Derive per-address TreeKey matching Minima Wallet.java
|
|
105
|
+
this.logger.debug(` Deriving per-address TreeKey for index ${addressIndex}...`);
|
|
106
|
+
const treeKey = createPerAddressTreeKey(seed, addressIndex);
|
|
107
|
+
// Convert (l1, l2) to Java-compatible 'uses' counter
|
|
108
|
+
// Java's TreeKey.sign() uses baseConversion(uses) to compute path
|
|
109
|
+
const KEYS_PER_LEVEL = 64;
|
|
110
|
+
const uses = l1 * KEYS_PER_LEVEL + l2;
|
|
111
|
+
this.logger.debug(` Converting indices to uses: (l1=${l1}, l2=${l2}) → uses=${uses}`);
|
|
112
|
+
// setUses() + sign() matches Java pattern: tk.setUses(uses) → tk.sign(zData)
|
|
113
|
+
treeKey.setUses(uses);
|
|
114
|
+
const treeSignature = treeKey.sign(digestBytes);
|
|
115
|
+
const newUses = treeKey.getUses();
|
|
116
|
+
this.logger.debug(` TreeKey signing complete, proofs: ${treeSignature.proofs.length}, newUses: ${newUses}`);
|
|
117
|
+
// Build HierarchicalWitnessBundle with unified naming
|
|
118
|
+
const treeKeyPubkey = treeKey.getPublicKey();
|
|
119
|
+
const witnessBundle = {
|
|
120
|
+
addressIndex,
|
|
121
|
+
l1,
|
|
122
|
+
l2,
|
|
123
|
+
rootPublicKey: `0x${this.bytesToHex(treeKeyPubkey)}`,
|
|
124
|
+
proofs: treeSignature.proofs.map((proof) => ({
|
|
125
|
+
leafPubkey: `0x${this.bytesToHex(proof.leafPubkey)}`,
|
|
126
|
+
signature: `0x${this.bytesToHex(proof.signature)}`,
|
|
127
|
+
mmrProof: `0x${this.bytesToHex(serializeMMRProof(proof.mmrProof))}`,
|
|
128
|
+
})),
|
|
129
|
+
};
|
|
130
|
+
// Serialize to byte-exact Java-compatible wire format
|
|
131
|
+
const serializedBytes = serializeTreeSignature(treeSignature);
|
|
132
|
+
const signedHex = `0x${this.bytesToHex(serializedBytes)}`;
|
|
133
|
+
const elapsed = Date.now() - startTime;
|
|
134
|
+
this.metrics.timing('tx_sign_duration_ms', elapsed);
|
|
135
|
+
this.logger.debug(`Signature created (${elapsed}ms), serialized: ${serializedBytes.length} bytes`);
|
|
136
|
+
return { witnessBundle, signedHex };
|
|
137
|
+
}
|
|
138
|
+
async finalize(params) {
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
const url = `${this.config.baseUrl.replace(/\/$/, '')}/v1/wots-hardened/finalize`;
|
|
141
|
+
this.logger.debug('Submitting signed transaction to network...');
|
|
142
|
+
this.logger.debug(` leaseToken: ${params.leaseToken.slice(0, 16)}...`);
|
|
143
|
+
try {
|
|
144
|
+
const response = await this.http.post(url, params, {
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
'x-api-key': this.config.apiKey,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
if (!response.ok) {
|
|
151
|
+
const body = response.data;
|
|
152
|
+
const msg = body?.error || body?.message || `HTTP ${response.status}`;
|
|
153
|
+
const err = new Error(msg);
|
|
154
|
+
err.code = response.status;
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
const result = response.data;
|
|
158
|
+
const elapsed = Date.now() - startTime;
|
|
159
|
+
this.metrics.timing('tx_finalize_duration_ms', elapsed);
|
|
160
|
+
this.metrics.increment('tx_finalize_success');
|
|
161
|
+
this.logger.debug(`Transaction finalized (${elapsed}ms)`);
|
|
162
|
+
this.logger.debug(` txpowid: ${result.txpowid}, leaseId: ${result.leaseId}`);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (error.code !== undefined)
|
|
167
|
+
throw error;
|
|
168
|
+
this.metrics.increment('tx_finalize_error');
|
|
169
|
+
this.logger.error('Finalization failed:', error);
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
bytesToHex(bytes) {
|
|
174
|
+
return Array.from(bytes)
|
|
175
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
176
|
+
.join('');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/core/tx
|
|
3
|
+
* Transaction service and lifecycle management
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* - TransactionService: WOTS signing flow (prepare → sign → finalize)
|
|
7
|
+
* - TransactionLifecycle: Orchestrates flow with lease/watermark stores
|
|
8
|
+
* - TransactionReceiptStore: Persistent transaction history
|
|
9
|
+
*/
|
|
10
|
+
export { TransactionService, type TransactionServiceConfig, type WotsSigningDependencies, } from './TransactionService.js';
|
|
11
|
+
export { TransactionLifecycle, TransactionLifecycleError, WatermarkExhaustedError, type TransactionLifecycleConfig, type WatermarkSyncFunction, type PrepareResult, } from './TransactionLifecycle.js';
|
|
12
|
+
export { TransactionReceiptStore, type TransactionReceiptStoreConfig, } from './TransactionReceiptStore.js';
|
|
13
|
+
export type { WotsIndices, PrepareRequest, PrepareResponse, SignRequest, SignResult, HierarchicalWitnessBundle, WitnessBundle, FinalizeRequest, FinalizeResponse, TransactionMetadata, TransactionReceipt, TransactionError, } from './types.js';
|
package/dist/tx/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @totemsdk/core/tx
|
|
3
|
+
* Transaction service and lifecycle management
|
|
4
|
+
*
|
|
5
|
+
* This module provides:
|
|
6
|
+
* - TransactionService: WOTS signing flow (prepare → sign → finalize)
|
|
7
|
+
* - TransactionLifecycle: Orchestrates flow with lease/watermark stores
|
|
8
|
+
* - TransactionReceiptStore: Persistent transaction history
|
|
9
|
+
*/
|
|
10
|
+
export { TransactionService, } from './TransactionService.js';
|
|
11
|
+
export { TransactionLifecycle, TransactionLifecycleError, WatermarkExhaustedError, } from './TransactionLifecycle.js';
|
|
12
|
+
export { TransactionReceiptStore, } from './TransactionReceiptStore.js';
|