@smartledger/bsv 3.3.4 → 3.3.5

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.
@@ -0,0 +1,533 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Browser-Compatible UTXO Manager
5
+ * Lightweight UTXO management for browser environments with configurable storage
6
+ */
7
+
8
+ /**
9
+ * Storage types available for browser UTXO management
10
+ */
11
+ var STORAGE_TYPES = {
12
+ MEMORY: 'memory', // In-memory only (lost on page reload)
13
+ SESSION: 'session', // sessionStorage (lost when tab closes)
14
+ LOCAL: 'local' // localStorage (persists until cleared)
15
+ }
16
+
17
+ /**
18
+ * Browser-compatible UTXO Manager
19
+ * Provides UTXO tracking and management for browser applications
20
+ */
21
+ function BrowserUTXOManager(options) {
22
+ options = options || {}
23
+ /**
24
+ * Create a new browser UTXO manager
25
+ * @param {Object} options - Configuration options
26
+ * @param {string} options.storage - Storage type: 'memory', 'session', or 'local' (default: 'memory')
27
+ * @param {string} options.storageKey - Key for browser storage (default: 'smartledger-utxos')
28
+ * @param {boolean} options.autoSave - Auto-save after each operation (default: true)
29
+ * @param {number} options.maxUTXOs - Maximum UTXOs to store (default: 1000)
30
+ */
31
+ constructor(options = {}) {
32
+ this.options = {
33
+ storage: options.storage || STORAGE_TYPES.MEMORY,
34
+ storageKey: options.storageKey || 'smartledger-utxos',
35
+ autoSave: options.autoSave !== false,
36
+ maxUTXOs: options.maxUTXOs || 1000,
37
+ ...options
38
+ }
39
+
40
+ // Validate storage type
41
+ if (!Object.values(STORAGE_TYPES).includes(this.options.storage)) {
42
+ throw new Error(`Invalid storage type: ${this.options.storage}. Must be one of: ${Object.values(STORAGE_TYPES).join(', ')}`)
43
+ }
44
+
45
+ // Initialize storage
46
+ this.utxos = new Map() // Main UTXO store: key = "txid:vout", value = utxo object
47
+ this.addressIndex = new Map() // Address index: key = address, value = Set of utxo keys
48
+ this.spentUTXOs = new Map() // Spent UTXO tracking
49
+ this.metadata = {
50
+ totalUTXOs: 0,
51
+ totalValue: 0,
52
+ createdAt: new Date().toISOString(),
53
+ lastUpdated: new Date().toISOString()
54
+ }
55
+
56
+ // Load existing data
57
+ this.loadFromStorage()
58
+ }
59
+
60
+ /**
61
+ * Get storage interface based on configuration
62
+ * @returns {Object} Storage interface (memory, sessionStorage, or localStorage)
63
+ * @private
64
+ */
65
+ _getStorage() {
66
+ switch (this.options.storage) {
67
+ case STORAGE_TYPES.MEMORY:
68
+ return null // Memory storage handled by class properties
69
+ case STORAGE_TYPES.SESSION:
70
+ return typeof sessionStorage !== 'undefined' ? sessionStorage : null
71
+ case STORAGE_TYPES.LOCAL:
72
+ return typeof localStorage !== 'undefined' ? localStorage : null
73
+ default:
74
+ return null
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Load UTXOs from configured storage
80
+ */
81
+ loadFromStorage() {
82
+ try {
83
+ if (this.options.storage === STORAGE_TYPES.MEMORY) {
84
+ // Memory storage - nothing to load, start fresh
85
+ return
86
+ }
87
+
88
+ const storage = this._getStorage()
89
+ if (!storage) {
90
+ console.warn('BrowserUTXOManager: Storage not available, using memory mode')
91
+ return
92
+ }
93
+
94
+ const stored = storage.getItem(this.options.storageKey)
95
+ if (!stored) {
96
+ return // No existing data
97
+ }
98
+
99
+ const data = JSON.parse(stored)
100
+
101
+ // Restore UTXOs
102
+ if (data.utxos) {
103
+ data.utxos.forEach(utxoData => {
104
+ const key = `${utxoData.txid}:${utxoData.vout}`
105
+ this.utxos.set(key, utxoData)
106
+ })
107
+ }
108
+
109
+ // Restore address index
110
+ if (data.addressIndex) {
111
+ Object.entries(data.addressIndex).forEach(([address, utxoKeys]) => {
112
+ this.addressIndex.set(address, new Set(utxoKeys))
113
+ })
114
+ }
115
+
116
+ // Restore spent UTXOs
117
+ if (data.spentUTXOs) {
118
+ data.spentUTXOs.forEach(spentData => {
119
+ const key = `${spentData.txid}:${spentData.vout}`
120
+ this.spentUTXOs.set(key, spentData)
121
+ })
122
+ }
123
+
124
+ // Restore metadata
125
+ if (data.metadata) {
126
+ this.metadata = { ...this.metadata, ...data.metadata }
127
+ }
128
+
129
+ this._updateMetadata()
130
+ console.log(`✅ BrowserUTXOManager: Loaded ${this.utxos.size} UTXOs from ${this.options.storage} storage`)
131
+
132
+ } catch (error) {
133
+ console.error('BrowserUTXOManager: Error loading from storage:', error.message)
134
+ // Continue with empty state
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Save UTXOs to configured storage
140
+ */
141
+ saveToStorage() {
142
+ try {
143
+ if (this.options.storage === STORAGE_TYPES.MEMORY) {
144
+ return // Memory storage - nothing to persist
145
+ }
146
+
147
+ const storage = this._getStorage()
148
+ if (!storage) {
149
+ return // Storage not available
150
+ }
151
+
152
+ // Prepare data for serialization
153
+ const data = {
154
+ utxos: Array.from(this.utxos.values()),
155
+ addressIndex: {},
156
+ spentUTXOs: Array.from(this.spentUTXOs.values()),
157
+ metadata: this.metadata
158
+ }
159
+
160
+ // Convert address index to serializable format
161
+ this.addressIndex.forEach((utxoKeys, address) => {
162
+ data.addressIndex[address] = Array.from(utxoKeys)
163
+ })
164
+
165
+ storage.setItem(this.options.storageKey, JSON.stringify(data))
166
+ console.log(`💾 BrowserUTXOManager: Saved ${this.utxos.size} UTXOs to ${this.options.storage} storage`)
167
+
168
+ } catch (error) {
169
+ console.error('BrowserUTXOManager: Error saving to storage:', error.message)
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Add a UTXO to the manager
175
+ * @param {Object} utxo - UTXO object {txid, vout, address, satoshis, script}
176
+ * @returns {boolean} - true if added, false if already exists or limit exceeded
177
+ */
178
+ addUTXO(utxo) {
179
+ try {
180
+ // Validate UTXO
181
+ if (!utxo.txid || typeof utxo.vout !== 'number' || !utxo.address || typeof utxo.satoshis !== 'number') {
182
+ throw new Error('Invalid UTXO: missing required fields (txid, vout, address, satoshis)')
183
+ }
184
+
185
+ const key = `${utxo.txid}:${utxo.vout}`
186
+
187
+ // Check if already exists
188
+ if (this.utxos.has(key)) {
189
+ console.log(`⚠️ UTXO already exists: ${key}`)
190
+ return false
191
+ }
192
+
193
+ // Check limits
194
+ if (this.utxos.size >= this.options.maxUTXOs) {
195
+ console.warn(`⚠️ Maximum UTXO limit reached (${this.options.maxUTXOs})`)
196
+ return false
197
+ }
198
+
199
+ // Add timestamp
200
+ const utxoWithMeta = {
201
+ ...utxo,
202
+ addedAt: new Date().toISOString()
203
+ }
204
+
205
+ // Store UTXO
206
+ this.utxos.set(key, utxoWithMeta)
207
+
208
+ // Update address index
209
+ if (!this.addressIndex.has(utxo.address)) {
210
+ this.addressIndex.set(utxo.address, new Set())
211
+ }
212
+ this.addressIndex.get(utxo.address).add(key)
213
+
214
+ this._updateMetadata()
215
+
216
+ if (this.options.autoSave) {
217
+ this.saveToStorage()
218
+ }
219
+
220
+ console.log(`✅ UTXO added: ${key} (${utxo.satoshis} sats)`)
221
+ return true
222
+
223
+ } catch (error) {
224
+ console.error('BrowserUTXOManager: Error adding UTXO:', error.message)
225
+ return false
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Get all UTXOs for a specific address
231
+ * @param {string} address - Bitcoin address
232
+ * @returns {Array} Array of UTXO objects
233
+ */
234
+ getUTXOsForAddress(address) {
235
+ try {
236
+ const utxoKeys = this.addressIndex.get(address)
237
+ if (!utxoKeys) {
238
+ return []
239
+ }
240
+
241
+ const utxos = []
242
+ utxoKeys.forEach(key => {
243
+ const utxo = this.utxos.get(key)
244
+ if (utxo) {
245
+ utxos.push(utxo)
246
+ }
247
+ })
248
+
249
+ return utxos.sort((a, b) => b.satoshis - a.satoshis) // Sort by value descending
250
+
251
+ } catch (error) {
252
+ console.error('BrowserUTXOManager: Error getting UTXOs for address:', error.message)
253
+ return []
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Get total balance for an address
259
+ * @param {string} address - Bitcoin address
260
+ * @returns {number} Total satoshis
261
+ */
262
+ getBalance(address) {
263
+ const utxos = this.getUTXOsForAddress(address)
264
+ return utxos.reduce((total, utxo) => total + utxo.satoshis, 0)
265
+ }
266
+
267
+ /**
268
+ * Spend UTXOs (mark as spent and remove from available set)
269
+ * @param {Array} inputs - Array of input objects {txid, vout} or {txid, vout, spentInTx}
270
+ * @param {string} spentInTx - Optional transaction ID where UTXOs were spent
271
+ * @returns {Array} Array of spent UTXO objects
272
+ */
273
+ spendUTXOs(inputs, spentInTx = 'browser-spend') {
274
+ const spentUTXOs = []
275
+
276
+ try {
277
+ inputs.forEach(input => {
278
+ const key = `${input.txid}:${input.vout}`
279
+ const utxo = this.utxos.get(key)
280
+
281
+ if (!utxo) {
282
+ console.warn(`⚠️ UTXO not found: ${key}`)
283
+ return
284
+ }
285
+
286
+ // Mark as spent
287
+ const spentUTXO = {
288
+ ...utxo,
289
+ spentAt: new Date().toISOString(),
290
+ spentInTx: input.spentInTx || spentInTx
291
+ }
292
+
293
+ this.spentUTXOs.set(key, spentUTXO)
294
+ spentUTXOs.push(spentUTXO)
295
+
296
+ // Remove from available UTXOs
297
+ this.utxos.delete(key)
298
+
299
+ // Update address index
300
+ const addressSet = this.addressIndex.get(utxo.address)
301
+ if (addressSet) {
302
+ addressSet.delete(key)
303
+ if (addressSet.size === 0) {
304
+ this.addressIndex.delete(utxo.address)
305
+ }
306
+ }
307
+
308
+ console.log(`❌ UTXO spent: ${key} in ${spentUTXO.spentInTx}`)
309
+ })
310
+
311
+ this._updateMetadata()
312
+
313
+ if (this.options.autoSave) {
314
+ this.saveToStorage()
315
+ }
316
+
317
+ } catch (error) {
318
+ console.error('BrowserUTXOManager: Error spending UTXOs:', error.message)
319
+ }
320
+
321
+ return spentUTXOs
322
+ }
323
+
324
+ /**
325
+ * Check if a UTXO is available (unspent)
326
+ * @param {string} txid - Transaction ID
327
+ * @param {number} vout - Output index
328
+ * @returns {boolean} True if UTXO is available
329
+ */
330
+ isUTXOAvailable(txid, vout) {
331
+ const key = `${txid}:${vout}`
332
+ return this.utxos.has(key)
333
+ }
334
+
335
+ /**
336
+ * Get UTXO details
337
+ * @param {string} txid - Transaction ID
338
+ * @param {number} vout - Output index
339
+ * @returns {Object|null} UTXO object or null if not found
340
+ */
341
+ getUTXO(txid, vout) {
342
+ const key = `${txid}:${vout}`
343
+
344
+ // Check if available
345
+ if (this.utxos.has(key)) {
346
+ return { status: 'available', utxo: this.utxos.get(key) }
347
+ }
348
+
349
+ // Check if spent
350
+ if (this.spentUTXOs.has(key)) {
351
+ return { status: 'spent', utxo: this.spentUTXOs.get(key) }
352
+ }
353
+
354
+ return { status: 'not_found', utxo: null }
355
+ }
356
+
357
+ /**
358
+ * Create mock UTXOs for testing (browser-compatible)
359
+ * @param {string} address - Target address
360
+ * @param {number} count - Number of UTXOs to create
361
+ * @param {number} satoshis - Satoshis per UTXO
362
+ * @returns {Array} Array of created UTXOs
363
+ */
364
+ createMockUTXOs(address, count = 5, satoshis = 100000) {
365
+ const mockUTXOs = []
366
+
367
+ try {
368
+ for (let i = 0; i < count; i++) {
369
+ // Generate random txid using Web Crypto API
370
+ const txidArray = new Uint8Array(32)
371
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
372
+ window.crypto.getRandomValues(txidArray)
373
+ } else {
374
+ // Fallback for environments without Web Crypto
375
+ for (let j = 0; j < 32; j++) {
376
+ txidArray[j] = Math.floor(Math.random() * 256)
377
+ }
378
+ }
379
+
380
+ const txid = Array.from(txidArray).map(b => b.toString(16).padStart(2, '0')).join('')
381
+ const script = `76a914${Array.from(new Uint8Array(20)).map(b => Math.floor(Math.random() * 256).toString(16).padStart(2, '0')).join('')}88ac`
382
+
383
+ const utxo = {
384
+ txid,
385
+ vout: i,
386
+ address,
387
+ satoshis,
388
+ script,
389
+ isMock: true
390
+ }
391
+
392
+ if (this.addUTXO(utxo)) {
393
+ mockUTXOs.push(utxo)
394
+ }
395
+ }
396
+
397
+ } catch (error) {
398
+ console.error('BrowserUTXOManager: Error creating mock UTXOs:', error.message)
399
+ }
400
+
401
+ return mockUTXOs
402
+ }
403
+
404
+ /**
405
+ * Get manager statistics
406
+ * @returns {Object} Statistics object
407
+ */
408
+ getStats() {
409
+ const addresses = Array.from(this.addressIndex.keys())
410
+ const balancesByAddress = {}
411
+
412
+ addresses.forEach(address => {
413
+ balancesByAddress[address] = this.getBalance(address)
414
+ })
415
+
416
+ return {
417
+ totalUTXOs: this.utxos.size,
418
+ totalSpent: this.spentUTXOs.size,
419
+ totalValue: this.metadata.totalValue,
420
+ totalAddresses: addresses.length,
421
+ storageType: this.options.storage,
422
+ storageKey: this.options.storageKey,
423
+ balancesByAddress,
424
+ createdAt: this.metadata.createdAt,
425
+ lastUpdated: this.metadata.lastUpdated
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Clear all UTXOs and reset state
431
+ * @param {boolean} clearStorage - Also clear browser storage (default: true)
432
+ */
433
+ reset(clearStorage = true) {
434
+ this.utxos.clear()
435
+ this.addressIndex.clear()
436
+ this.spentUTXOs.clear()
437
+
438
+ this.metadata = {
439
+ totalUTXOs: 0,
440
+ totalValue: 0,
441
+ createdAt: new Date().toISOString(),
442
+ lastUpdated: new Date().toISOString()
443
+ }
444
+
445
+ if (clearStorage && this.options.storage !== STORAGE_TYPES.MEMORY) {
446
+ const storage = this._getStorage()
447
+ if (storage) {
448
+ storage.removeItem(this.options.storageKey)
449
+ console.log(`🔄 Cleared ${this.options.storage} storage`)
450
+ }
451
+ }
452
+
453
+ console.log('🔄 BrowserUTXOManager reset complete')
454
+ }
455
+
456
+ /**
457
+ * Update internal metadata
458
+ * @private
459
+ */
460
+ _updateMetadata() {
461
+ this.metadata.totalUTXOs = this.utxos.size
462
+ this.metadata.totalValue = Array.from(this.utxos.values())
463
+ .reduce((total, utxo) => total + utxo.satoshis, 0)
464
+ this.metadata.lastUpdated = new Date().toISOString()
465
+ }
466
+
467
+ /**
468
+ * Export UTXOs as JSON
469
+ * @returns {string} JSON string of all data
470
+ */
471
+ exportData() {
472
+ const data = {
473
+ utxos: Array.from(this.utxos.values()),
474
+ spentUTXOs: Array.from(this.spentUTXOs.values()),
475
+ metadata: this.metadata,
476
+ exportedAt: new Date().toISOString()
477
+ }
478
+ return JSON.stringify(data, null, 2)
479
+ }
480
+
481
+ /**
482
+ * Import UTXOs from JSON
483
+ * @param {string} jsonData - JSON string to import
484
+ * @param {boolean} merge - Merge with existing data (default: false)
485
+ * @returns {boolean} Success status
486
+ */
487
+ importData(jsonData, merge = false) {
488
+ try {
489
+ const data = JSON.parse(jsonData)
490
+
491
+ if (!merge) {
492
+ this.reset(false) // Don't clear storage yet
493
+ }
494
+
495
+ // Import UTXOs
496
+ if (data.utxos && Array.isArray(data.utxos)) {
497
+ data.utxos.forEach(utxo => {
498
+ this.addUTXO(utxo)
499
+ })
500
+ }
501
+
502
+ // Import spent UTXOs
503
+ if (data.spentUTXOs && Array.isArray(data.spentUTXOs)) {
504
+ data.spentUTXOs.forEach(spentUTXO => {
505
+ const key = `${spentUTXO.txid}:${spentUTXO.vout}`
506
+ this.spentUTXOs.set(key, spentUTXO)
507
+ })
508
+ }
509
+
510
+ this._updateMetadata()
511
+
512
+ if (this.options.autoSave) {
513
+ this.saveToStorage()
514
+ }
515
+
516
+ console.log('✅ BrowserUTXOManager: Imported ' + (data.utxos && data.utxos.length || 0) + ' UTXOs')
517
+ return true
518
+
519
+ } catch (error) {
520
+ console.error('BrowserUTXOManager: Error importing data:', error.message)
521
+ return false
522
+ }
523
+ }
524
+ }
525
+
526
+ // Export for both CommonJS and ES modules
527
+ if (typeof module !== 'undefined' && module.exports) {
528
+ module.exports = BrowserUTXOManager
529
+ module.exports.STORAGE_TYPES = STORAGE_TYPES
530
+ } else if (typeof window !== 'undefined') {
531
+ window.BrowserUTXOManager = BrowserUTXOManager
532
+ window.BrowserUTXOManager.STORAGE_TYPES = STORAGE_TYPES
533
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartledger/bsv",
3
- "version": "3.3.4",
3
+ "version": "3.3.5",
4
4
  "description": "🚀 Complete Bitcoin SV development framework with Legal Token Protocol (LTP), Global Digital Attestation Framework (GDAF), Shamir Secret Sharing, and 12 flexible loading options. Includes primitives-only architecture for maximum integration flexibility, SmartContract framework, covenant builder, and comprehensive Bitcoin SV API. Perfect for legal tokens, DeFi, smart contracts, and secure Bitcoin applications.",
5
5
  "author": "SmartLedger Technology <hello@smartledger.technology> (https://smartledger.technology)",
6
6
  "homepage": "https://github.com/codenlighten/smartledger-bsv#readme",
@@ -72,7 +72,7 @@
72
72
  "bsv.min.js",
73
73
  "bsv.bundle.js",
74
74
  "bsv-ecies.min.js",
75
- "bsv-message.min.js",
75
+ "bsv-message.min.js",
76
76
  "bsv-mnemonic.min.js",
77
77
  "bsv-shamir.min.js",
78
78
  "bsv-gdaf.min.js",
@@ -99,7 +99,7 @@
99
99
  ],
100
100
  "keywords": [
101
101
  "bitcoin",
102
- "bitcoin-sv",
102
+ "bitcoin-sv",
103
103
  "bsv",
104
104
  "legal-token-protocol",
105
105
  "ltp",
@@ -58,10 +58,10 @@
58
58
 
59
59
  // Load CDN scripts
60
60
  const bsvScript = document.createElement('script');
61
- bsvScript.src = 'https://cdn.jsdelivr.net/npm/smartledger-bsv@3.3.3/bsv.min.js';
61
+ bsvScript.src = 'https://cdn.jsdelivr.net/npm/smartledger-bsv@3.3.4/bsv.min.js';
62
62
  bsvScript.onload = () => {
63
63
  const mnemonicScript = document.createElement('script');
64
- mnemonicScript.src = 'https://cdn.jsdelivr.net/npm/smartledger-bsv@3.3.3/bsv-mnemonic.min.js';
64
+ mnemonicScript.src = 'https://cdn.jsdelivr.net/npm/smartledger-bsv@3.3.4/bsv-mnemonic.min.js';
65
65
  mnemonicScript.onload = () => {
66
66
  testMnemonicGeneration('cdn-results', 'CDN');
67
67
  };