@smartledger/bsv 3.3.4 → 3.4.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/CHANGELOG.md +30 -21
- package/README.md +169 -40
- package/anchor-entry.js +1 -0
- package/bin/cli.js +349 -0
- package/bsv-covenant.min.js +5 -5
- package/bsv-gdaf.min.js +6 -6
- package/bsv-ltp.min.js +6 -6
- package/bsv-smartcontract.min.js +9 -9
- package/bsv.bundle.js +5 -5
- package/bsv.min.js +8 -8
- package/build/webpack.anchor.config.js +21 -0
- package/build/webpack.didweb.config.js +21 -0
- package/build/webpack.statuslist.config.js +22 -0
- package/build/webpack.vcjwt.config.js +21 -0
- package/demos/README.md +1 -1
- package/demos/browser-test.html +1208 -0
- package/demos/smart_contract_demo.html +1 -1
- package/demos/smart_contract_demo.js +1 -1
- package/demos/web3keys.html +3 -3
- package/didweb-entry.js +1 -0
- package/docs/DOCUMENTATION_REVIEW_REPORT.md +11 -11
- package/docs/FIX_CREATEHMAC_ISSUE.md +1 -1
- package/docs/MODULE_REFERENCE_COMPLETE.md +28 -28
- package/docs/SMARTLEDGER_BSV_USAGE_ANSWERS.md +4 -4
- package/docs/SMARTLEDGER_BSV_USAGE_EXAMPLES.js +2 -2
- package/docs/SMARTLEDGER_BSV_USAGE_GUIDE.md +3 -3
- package/docs/SMART_CONTRACT_DEVELOPMENT_GUIDE.md +1 -1
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +2 -2
- package/docs/getting-started/INSTALLATION.md +25 -25
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/docs/technical/roadmap.md +3 -3
- package/index.js +35 -0
- package/lib/anchor/index.js +102 -0
- package/lib/browser-utxo-manager-es5.js +316 -0
- package/lib/browser-utxo-manager.js +533 -0
- package/lib/didweb/index.js +177 -0
- package/lib/statuslist/index.js +164 -0
- package/lib/vcjwt/index.js +189 -0
- package/package.json +13 -5
- package/statuslist-entry.js +1 -0
- package/tests/browser-compatibility/test-cdn-vs-local.html +2 -2
- package/vcjwt-entry.js +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BSV Anchor Module
|
|
5
|
+
* Hash anchoring helpers for on-chain evidence (no PII)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var crypto = require('crypto')
|
|
9
|
+
|
|
10
|
+
// SHA-256 hex hash
|
|
11
|
+
function sha256Hex(data) {
|
|
12
|
+
var buffer
|
|
13
|
+
if (typeof data === 'string') {
|
|
14
|
+
buffer = Buffer.from(data, 'utf8')
|
|
15
|
+
} else if (Buffer.isBuffer(data)) {
|
|
16
|
+
buffer = data
|
|
17
|
+
} else if (data instanceof Uint8Array) {
|
|
18
|
+
buffer = Buffer.from(data)
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error('Data must be string, Buffer, or Uint8Array')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return crypto.createHash('sha256').update(buffer).digest('hex')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Build anchor payload for OP_RETURN
|
|
27
|
+
function buildAnchorPayload(params) {
|
|
28
|
+
if (!params.kind || !params.hash || !params.issuerDid) {
|
|
29
|
+
throw new Error('kind, hash, and issuerDid are required')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var validKinds = ['VC_ANCHOR_SHA256', 'STATUSLIST_SHA256', 'PRESENTATION_SHA256']
|
|
33
|
+
if (validKinds.indexOf(params.kind) === -1) {
|
|
34
|
+
throw new Error('Invalid kind. Must be one of: ' + validKinds.join(', '))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Validate hash format (64 hex characters)
|
|
38
|
+
if (!/^[a-fA-F0-9]{64}$/.test(params.hash)) {
|
|
39
|
+
throw new Error('Invalid hash format. Must be 64 hex characters')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var payload = {
|
|
43
|
+
protocol: 'SmartLedger',
|
|
44
|
+
version: '1.0',
|
|
45
|
+
type: params.kind,
|
|
46
|
+
hash: params.hash,
|
|
47
|
+
issuer: params.issuerDid,
|
|
48
|
+
timestamp: params.issuedAt || new Date().toISOString()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
json: JSON.stringify(payload)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Verify anchor hash against original data
|
|
57
|
+
function verifyAnchorHash(originalData, anchorHash) {
|
|
58
|
+
var computed = sha256Hex(originalData)
|
|
59
|
+
return computed === anchorHash
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract anchor info from OP_RETURN data
|
|
63
|
+
function parseAnchorPayload(opReturnData) {
|
|
64
|
+
try {
|
|
65
|
+
var parsed = JSON.parse(opReturnData)
|
|
66
|
+
|
|
67
|
+
if (parsed.protocol !== 'SmartLedger') {
|
|
68
|
+
return { valid: false, error: 'Invalid protocol' }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var validTypes = ['VC_ANCHOR_SHA256', 'STATUSLIST_SHA256', 'PRESENTATION_SHA256']
|
|
72
|
+
if (validTypes.indexOf(parsed.type) === -1) {
|
|
73
|
+
return { valid: false, error: 'Invalid anchor type' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!/^[a-fA-F0-9]{64}$/.test(parsed.hash)) {
|
|
77
|
+
return { valid: false, error: 'Invalid hash format' }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
valid: true,
|
|
82
|
+
protocol: parsed.protocol,
|
|
83
|
+
version: parsed.version,
|
|
84
|
+
type: parsed.type,
|
|
85
|
+
hash: parsed.hash,
|
|
86
|
+
issuer: parsed.issuer,
|
|
87
|
+
timestamp: parsed.timestamp
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: 'Failed to parse anchor payload: ' + error.message
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
sha256Hex: sha256Hex,
|
|
99
|
+
buildAnchorPayload: buildAnchorPayload,
|
|
100
|
+
verifyAnchorHash: verifyAnchorHash,
|
|
101
|
+
parseAnchorPayload: parseAnchorPayload
|
|
102
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser-Compatible UTXO Manager (ES5 Compatible)
|
|
5
|
+
* Lightweight UTXO management for browser environments with configurable storage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var STORAGE_TYPES = {
|
|
9
|
+
MEMORY: 'memory',
|
|
10
|
+
SESSION: 'session',
|
|
11
|
+
LOCAL: 'local'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function BrowserUTXOManager(options) {
|
|
15
|
+
options = options || {}
|
|
16
|
+
|
|
17
|
+
this.options = {
|
|
18
|
+
storage: options.storage || STORAGE_TYPES.MEMORY,
|
|
19
|
+
storageKey: options.storageKey || 'smartledger-utxos',
|
|
20
|
+
autoSave: options.autoSave !== false,
|
|
21
|
+
maxUTXOs: options.maxUTXOs || 1000
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate storage type
|
|
25
|
+
var validTypes = [STORAGE_TYPES.MEMORY, STORAGE_TYPES.SESSION, STORAGE_TYPES.LOCAL]
|
|
26
|
+
if (validTypes.indexOf(this.options.storage) === -1) {
|
|
27
|
+
throw new Error('Invalid storage type: ' + this.options.storage)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.utxos = new Map()
|
|
31
|
+
this.addressIndex = new Map()
|
|
32
|
+
this.spentUTXOs = new Map()
|
|
33
|
+
this.metadata = {
|
|
34
|
+
totalUTXOs: 0,
|
|
35
|
+
totalValue: 0,
|
|
36
|
+
createdAt: new Date().toISOString(),
|
|
37
|
+
lastModified: new Date().toISOString()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.loadFromStorage()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
BrowserUTXOManager.prototype.getStorage = function() {
|
|
44
|
+
if (this.options.storage === STORAGE_TYPES.SESSION) {
|
|
45
|
+
return typeof sessionStorage !== 'undefined' ? sessionStorage : null
|
|
46
|
+
} else if (this.options.storage === STORAGE_TYPES.LOCAL) {
|
|
47
|
+
return typeof localStorage !== 'undefined' ? localStorage : null
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
BrowserUTXOManager.prototype.loadFromStorage = function() {
|
|
53
|
+
var storage = this.getStorage()
|
|
54
|
+
if (!storage) return
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
var data = storage.getItem(this.options.storageKey)
|
|
58
|
+
if (!data) return
|
|
59
|
+
|
|
60
|
+
var parsed = JSON.parse(data)
|
|
61
|
+
if (!parsed.utxos) return
|
|
62
|
+
|
|
63
|
+
var self = this
|
|
64
|
+
parsed.utxos.forEach(function(utxoData) {
|
|
65
|
+
var key = utxoData.txid + ':' + utxoData.vout
|
|
66
|
+
self.utxos.set(key, utxoData)
|
|
67
|
+
|
|
68
|
+
if (!self.addressIndex.has(utxoData.address)) {
|
|
69
|
+
self.addressIndex.set(utxoData.address, new Set())
|
|
70
|
+
}
|
|
71
|
+
self.addressIndex.get(utxoData.address).add(key)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (parsed.metadata) {
|
|
75
|
+
this.metadata = parsed.metadata
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log('✅ BrowserUTXOManager: Loaded ' + this.utxos.size + ' UTXOs from ' + this.options.storage + ' storage')
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.error('Failed to load UTXOs from storage:', e)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
BrowserUTXOManager.prototype.saveToStorage = function() {
|
|
85
|
+
if (this.options.storage === STORAGE_TYPES.MEMORY) return
|
|
86
|
+
|
|
87
|
+
var storage = this.getStorage()
|
|
88
|
+
if (!storage) return
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
var data = {
|
|
92
|
+
utxos: Array.from(this.utxos.values()),
|
|
93
|
+
metadata: this.metadata,
|
|
94
|
+
version: '1.0.0',
|
|
95
|
+
timestamp: new Date().toISOString()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
storage.setItem(this.options.storageKey, JSON.stringify(data))
|
|
99
|
+
console.log('💾 BrowserUTXOManager: Saved ' + this.utxos.size + ' UTXOs to ' + this.options.storage + ' storage')
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error('Failed to save UTXOs to storage:', e)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
BrowserUTXOManager.prototype.addUTXO = function(utxo) {
|
|
106
|
+
if (!utxo || !utxo.txid || typeof utxo.vout !== 'number') {
|
|
107
|
+
throw new Error('Invalid UTXO: missing txid or vout')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var key = utxo.txid + ':' + utxo.vout
|
|
111
|
+
|
|
112
|
+
if (this.utxos.has(key)) {
|
|
113
|
+
console.log('⚠️ UTXO already exists: ' + key)
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add required fields
|
|
118
|
+
var utxoData = {
|
|
119
|
+
txid: utxo.txid,
|
|
120
|
+
vout: utxo.vout,
|
|
121
|
+
address: utxo.address || '',
|
|
122
|
+
satoshis: utxo.satoshis || 0,
|
|
123
|
+
script: utxo.script || '',
|
|
124
|
+
status: 'available',
|
|
125
|
+
addedAt: new Date().toISOString()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.utxos.set(key, utxoData)
|
|
129
|
+
|
|
130
|
+
// Update address index
|
|
131
|
+
if (utxoData.address) {
|
|
132
|
+
if (!this.addressIndex.has(utxoData.address)) {
|
|
133
|
+
this.addressIndex.set(utxoData.address, new Set())
|
|
134
|
+
}
|
|
135
|
+
this.addressIndex.get(utxoData.address).add(key)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.updateMetadata()
|
|
139
|
+
if (this.options.autoSave) {
|
|
140
|
+
this.saveToStorage()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
BrowserUTXOManager.prototype.getUTXOsForAddress = function(address) {
|
|
147
|
+
var utxoKeys = this.addressIndex.get(address)
|
|
148
|
+
if (!utxoKeys) return []
|
|
149
|
+
|
|
150
|
+
var results = []
|
|
151
|
+
var self = this
|
|
152
|
+
utxoKeys.forEach(function(key) {
|
|
153
|
+
var utxo = self.utxos.get(key)
|
|
154
|
+
if (utxo && utxo.status === 'available') {
|
|
155
|
+
results.push(utxo)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return results
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
BrowserUTXOManager.prototype.getBalance = function(address) {
|
|
163
|
+
var utxos = this.getUTXOsForAddress(address)
|
|
164
|
+
return utxos.reduce(function(sum, utxo) {
|
|
165
|
+
return sum + (utxo.satoshis || 0)
|
|
166
|
+
}, 0)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
BrowserUTXOManager.prototype.spendUTXOs = function(inputs, spentInTx) {
|
|
170
|
+
spentInTx = spentInTx || 'browser-spend'
|
|
171
|
+
var spentUTXOs = []
|
|
172
|
+
|
|
173
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
174
|
+
var input = inputs[i]
|
|
175
|
+
var key = input.txid + ':' + input.vout
|
|
176
|
+
var utxo = this.utxos.get(key)
|
|
177
|
+
|
|
178
|
+
if (utxo && utxo.status === 'available') {
|
|
179
|
+
utxo.status = 'spent'
|
|
180
|
+
utxo.spentAt = new Date().toISOString()
|
|
181
|
+
utxo.spentInTx = spentInTx
|
|
182
|
+
|
|
183
|
+
this.spentUTXOs.set(key, {
|
|
184
|
+
txid: utxo.txid,
|
|
185
|
+
vout: utxo.vout,
|
|
186
|
+
spentInTx: spentInTx,
|
|
187
|
+
spentAt: utxo.spentAt
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
spentUTXOs.push(utxo)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.updateMetadata()
|
|
195
|
+
if (this.options.autoSave) {
|
|
196
|
+
this.saveToStorage()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return spentUTXOs
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
BrowserUTXOManager.prototype.getUTXO = function(txid, vout) {
|
|
203
|
+
var key = txid + ':' + vout
|
|
204
|
+
var utxo = this.utxos.get(key)
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
exists: !!utxo,
|
|
208
|
+
utxo: utxo,
|
|
209
|
+
status: utxo ? utxo.status : 'not-found'
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
BrowserUTXOManager.prototype.createMockUTXOs = function(address, count, satoshis) {
|
|
214
|
+
count = count || 5
|
|
215
|
+
satoshis = satoshis || 100000
|
|
216
|
+
var mockUTXOs = []
|
|
217
|
+
|
|
218
|
+
for (var i = 0; i < count; i++) {
|
|
219
|
+
// Generate random-like txid
|
|
220
|
+
var txid = ''
|
|
221
|
+
for (var j = 0; j < 64; j++) {
|
|
222
|
+
txid += Math.floor(Math.random() * 16).toString(16)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
var mockUTXO = {
|
|
226
|
+
txid: txid,
|
|
227
|
+
vout: i,
|
|
228
|
+
address: address,
|
|
229
|
+
satoshis: satoshis + Math.floor(Math.random() * 10000),
|
|
230
|
+
script: '',
|
|
231
|
+
status: 'available'
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.addUTXO(mockUTXO)
|
|
235
|
+
mockUTXOs.push(mockUTXO)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return mockUTXOs
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
BrowserUTXOManager.prototype.getStats = function() {
|
|
242
|
+
var totalValue = 0
|
|
243
|
+
var availableCount = 0
|
|
244
|
+
var spentCount = 0
|
|
245
|
+
|
|
246
|
+
this.utxos.forEach(function(utxo) {
|
|
247
|
+
totalValue += utxo.satoshis || 0
|
|
248
|
+
if (utxo.status === 'available') {
|
|
249
|
+
availableCount++
|
|
250
|
+
} else if (utxo.status === 'spent') {
|
|
251
|
+
spentCount++
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
totalUTXOs: this.utxos.size,
|
|
257
|
+
totalAvailable: availableCount,
|
|
258
|
+
totalSpent: spentCount,
|
|
259
|
+
totalValue: totalValue,
|
|
260
|
+
totalAddresses: this.addressIndex.size,
|
|
261
|
+
storageType: this.options.storage,
|
|
262
|
+
metadata: this.metadata
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
BrowserUTXOManager.prototype.updateMetadata = function() {
|
|
267
|
+
var stats = this.getStats()
|
|
268
|
+
this.metadata.totalUTXOs = stats.totalUTXOs
|
|
269
|
+
this.metadata.totalValue = stats.totalValue
|
|
270
|
+
this.metadata.lastModified = new Date().toISOString()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
BrowserUTXOManager.prototype.exportData = function() {
|
|
274
|
+
return JSON.stringify({
|
|
275
|
+
utxos: Array.from(this.utxos.values()),
|
|
276
|
+
metadata: this.metadata,
|
|
277
|
+
version: '1.0.0',
|
|
278
|
+
exportedAt: new Date().toISOString()
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
BrowserUTXOManager.prototype.importData = function(jsonData, merge) {
|
|
283
|
+
merge = merge || false
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
var data = JSON.parse(jsonData)
|
|
287
|
+
|
|
288
|
+
if (!merge) {
|
|
289
|
+
this.utxos.clear()
|
|
290
|
+
this.addressIndex.clear()
|
|
291
|
+
this.spentUTXOs.clear()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (data.utxos && Array.isArray(data.utxos)) {
|
|
295
|
+
var self = this
|
|
296
|
+
data.utxos.forEach(function(utxo) {
|
|
297
|
+
self.addUTXO(utxo)
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log('✅ BrowserUTXOManager: Imported ' + (data.utxos && data.utxos.length || 0) + ' UTXOs')
|
|
302
|
+
return true
|
|
303
|
+
} catch (e) {
|
|
304
|
+
console.error('Failed to import UTXO data:', e)
|
|
305
|
+
return false
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Export
|
|
310
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
311
|
+
module.exports = BrowserUTXOManager
|
|
312
|
+
module.exports.STORAGE_TYPES = STORAGE_TYPES
|
|
313
|
+
} else if (typeof window !== 'undefined') {
|
|
314
|
+
window.BrowserUTXOManager = BrowserUTXOManager
|
|
315
|
+
window.BrowserUTXOManager.STORAGE_TYPES = STORAGE_TYPES
|
|
316
|
+
}
|