smartledger-bsv 3.3.3 → 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.
- package/CHANGELOG.md +50 -28
- package/README.md +55 -36
- package/bsv-covenant.min.js +6 -6
- package/bsv-gdaf.min.js +6 -6
- package/bsv-ltp.min.js +6 -6
- package/bsv-mnemonic.min.js +4 -4
- package/bsv-smartcontract.min.js +5 -5
- package/bsv.bundle.js +5 -5
- package/bsv.min.js +5 -5
- package/demos/README.md +188 -0
- package/demos/architecture_demo.js +247 -0
- package/demos/browser-test.html +1208 -0
- package/demos/bsv_wallet_demo.js +242 -0
- package/demos/complete_ltp_demo.js +511 -0
- package/demos/debug_tools_demo.js +87 -0
- package/demos/demo_features.js +123 -0
- package/demos/easy_interface_demo.js +109 -0
- package/demos/ecies_demo.js +182 -0
- package/demos/gdaf_core_test.js +131 -0
- package/demos/gdaf_demo.js +237 -0
- package/demos/ltp_demo.js +361 -0
- package/demos/ltp_primitives_demo.js +403 -0
- package/demos/message_demo.js +209 -0
- package/demos/preimage_separation_demo.js +383 -0
- package/demos/script_helper_demo.js +289 -0
- package/demos/security_demo.js +287 -0
- package/demos/shamir_demo.js +121 -0
- package/demos/simple_demo.js +204 -0
- package/demos/simple_p2pkh_demo.js +169 -0
- package/demos/simple_utxo_preimage_demo.js +196 -0
- package/demos/smart_contract_demo.html +1347 -0
- package/demos/smart_contract_demo.js +910 -0
- package/demos/utxo_generator_demo.js +244 -0
- package/demos/validation_pipeline_demo.js +155 -0
- package/demos/web3keys.html +740 -0
- package/docs/BUNDLE_UPDATE_SUMMARY.md +40 -0
- package/docs/DOCUMENTATION_REVIEW_REPORT.md +11 -11
- package/docs/FIX_CREATEHMAC_ISSUE.md +91 -0
- package/docs/MODULE_REFERENCE_COMPLETE.md +28 -28
- package/docs/SMARTLEDGER_BSV_USAGE_ANSWERS.md +477 -0
- package/docs/SMARTLEDGER_BSV_USAGE_EXAMPLES.js +372 -0
- package/docs/SMARTLEDGER_BSV_USAGE_GUIDE.md +555 -0
- package/docs/SMART_CONTRACT_DEVELOPMENT_GUIDE.md +1459 -0
- 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/examples/complete_workflow_demo.js +783 -0
- package/examples/definitive_working_demo.js +261 -0
- package/examples/final_working_contracts.js +338 -0
- package/examples/smart_contract_templates.js +718 -0
- package/examples/working_smart_contracts.js +348 -0
- package/index.js +7 -0
- package/lib/browser-utxo-manager-es5.js +316 -0
- package/lib/browser-utxo-manager.js +533 -0
- package/lib/mnemonic/pbkdf2.browser.js +69 -0
- package/lib/mnemonic/pbkdf2.js +2 -68
- package/lib/mnemonic/pbkdf2.node.js +68 -0
- package/package.json +19 -8
- package/tests/browser-compatibility/README.md +35 -0
- package/tests/browser-compatibility/test-cdn-vs-local.html +186 -0
- package/tests/browser-compatibility/test-pbkdf2.html +51 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working Smart Contract Examples - Fixed Version
|
|
3
|
+
* ==============================================
|
|
4
|
+
*
|
|
5
|
+
* This file contains working, tested smart contract examples that properly
|
|
6
|
+
* integrate with smartledger-bsv's preimage validation system.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const bsv = require('../index.js')
|
|
10
|
+
|
|
11
|
+
// Enable colored output if available
|
|
12
|
+
let chalk
|
|
13
|
+
try {
|
|
14
|
+
chalk = require('chalk')
|
|
15
|
+
} catch (e) {
|
|
16
|
+
chalk = {
|
|
17
|
+
green: (text) => `✅ ${text}`,
|
|
18
|
+
red: (text) => `❌ ${text}`,
|
|
19
|
+
yellow: (text) => `⚠️ ${text}`,
|
|
20
|
+
blue: (text) => `ℹ️ ${text}`,
|
|
21
|
+
magenta: (text) => `🔮 ${text}`,
|
|
22
|
+
cyan: (text) => `🌊 ${text}`,
|
|
23
|
+
bold: (text) => `**${text}**`
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(chalk.bold.blue('\n🔧 Working Smart Contract Examples (Fixed)\n'))
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* WorkingAmountContract - A properly functioning amount validation contract
|
|
31
|
+
*/
|
|
32
|
+
class WorkingAmountContract {
|
|
33
|
+
constructor(expectedAmount) {
|
|
34
|
+
this.expectedAmount = expectedAmount
|
|
35
|
+
this.privateKey = new bsv.PrivateKey()
|
|
36
|
+
|
|
37
|
+
// Create the contract script using the quick covenant method
|
|
38
|
+
this.script = this._createWorkingScript()
|
|
39
|
+
this.address = bsv.SmartContract.utils.createCovenantAddress(this.script)
|
|
40
|
+
|
|
41
|
+
console.log(chalk.green(`✅ WorkingAmountContract created`))
|
|
42
|
+
console.log(chalk.blue(` Expected Amount: ${expectedAmount} satoshis`))
|
|
43
|
+
console.log(chalk.blue(` Contract Address: ${this.address.toString()}`))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_createWorkingScript() {
|
|
47
|
+
// Use the createQuickCovenant method which is known to work
|
|
48
|
+
const covenant = bsv.SmartContract.createQuickCovenant('value_lock', {
|
|
49
|
+
value: this.expectedAmount
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Convert the ASM to a proper BSV Script
|
|
53
|
+
return bsv.Script.fromASM(covenant.asm)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Test validation with a real preimage
|
|
57
|
+
validatePayment(preimageHex) {
|
|
58
|
+
try {
|
|
59
|
+
console.log(chalk.yellow('🔍 Testing payment validation with working script...'))
|
|
60
|
+
|
|
61
|
+
const result = bsv.SmartContract.testScript(
|
|
62
|
+
preimageHex,
|
|
63
|
+
this.script.toHex(),
|
|
64
|
+
{ verbose: true }
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if (result.valid) {
|
|
68
|
+
console.log(chalk.green('✅ Payment validation PASSED'))
|
|
69
|
+
} else {
|
|
70
|
+
console.log(chalk.red('❌ Payment validation FAILED'))
|
|
71
|
+
console.log(chalk.red(` Error: ${result.error || 'Unknown error'}`))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: result.valid,
|
|
76
|
+
error: result.error,
|
|
77
|
+
details: result
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log(chalk.red('❌ Validation error:', error.message))
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: error.message
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Create a proper test preimage for this contract
|
|
89
|
+
createTestPreimage(inputAmount) {
|
|
90
|
+
try {
|
|
91
|
+
console.log(chalk.yellow('📝 Creating test preimage...'))
|
|
92
|
+
|
|
93
|
+
// Create a mock transaction that would spend from this contract
|
|
94
|
+
const mockTx = new bsv.Transaction()
|
|
95
|
+
|
|
96
|
+
// Add input from contract (mock)
|
|
97
|
+
mockTx.addInput({
|
|
98
|
+
prevTxId: 'a'.repeat(64),
|
|
99
|
+
outputIndex: 0,
|
|
100
|
+
script: this.script.toHex(),
|
|
101
|
+
sequenceNumber: 0xffffffff
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Add output with the input amount
|
|
105
|
+
mockTx.addOutput({
|
|
106
|
+
script: bsv.Script.buildPublicKeyHashOut(new bsv.Address('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')).toHex(),
|
|
107
|
+
satoshis: inputAmount
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// Generate preimage
|
|
111
|
+
const preimage = bsv.Transaction.sighash.sighashPreimage(
|
|
112
|
+
mockTx,
|
|
113
|
+
bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID,
|
|
114
|
+
0,
|
|
115
|
+
this.script,
|
|
116
|
+
new bsv.crypto.BN(inputAmount + 1000) // Contract input amount
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
console.log(chalk.blue(` Generated preimage: ${preimage.toString('hex').substring(0, 64)}...`))
|
|
120
|
+
return preimage.toString('hex')
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(chalk.red('❌ Preimage generation error:', error.message))
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Complete test of the contract
|
|
129
|
+
runCompleteTest() {
|
|
130
|
+
console.log(chalk.cyan('\n🧪 Running Complete Contract Test'))
|
|
131
|
+
console.log(chalk.cyan('==================================='))
|
|
132
|
+
|
|
133
|
+
// Test with correct amount
|
|
134
|
+
console.log(chalk.yellow('\n1. Testing with CORRECT amount'))
|
|
135
|
+
const correctPreimage = this.createTestPreimage(this.expectedAmount)
|
|
136
|
+
if (correctPreimage) {
|
|
137
|
+
const correctResult = this.validatePayment(correctPreimage)
|
|
138
|
+
console.log(chalk.blue(` Result: ${correctResult.success ? 'PASS' : 'FAIL'}`))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Test with incorrect amount
|
|
142
|
+
console.log(chalk.yellow('\n2. Testing with INCORRECT amount'))
|
|
143
|
+
const incorrectPreimage = this.createTestPreimage(this.expectedAmount - 10000)
|
|
144
|
+
if (incorrectPreimage) {
|
|
145
|
+
const incorrectResult = this.validatePayment(incorrectPreimage)
|
|
146
|
+
console.log(chalk.blue(` Result: ${incorrectResult.success ? 'UNEXPECTED PASS' : 'EXPECTED FAIL'}`))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(chalk.cyan('\n✅ Complete test finished'))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getContractInfo() {
|
|
153
|
+
return {
|
|
154
|
+
expectedAmount: this.expectedAmount,
|
|
155
|
+
address: this.address.toString(),
|
|
156
|
+
script: {
|
|
157
|
+
hex: this.script.toHex(),
|
|
158
|
+
asm: this.script.toASM(),
|
|
159
|
+
size: this.script.toBuffer().length
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* SimpleFieldExtractionDemo - Shows how field extraction actually works
|
|
167
|
+
*/
|
|
168
|
+
class SimpleFieldExtractionDemo {
|
|
169
|
+
constructor() {
|
|
170
|
+
console.log(chalk.bold.magenta('\n🔬 Field Extraction Demo\n'))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
demonstrateFieldExtraction() {
|
|
174
|
+
console.log(chalk.cyan('Testing Field Extraction with Real Preimage'))
|
|
175
|
+
console.log(chalk.cyan('===========================================\n'))
|
|
176
|
+
|
|
177
|
+
// Create a simple transaction to get a real preimage
|
|
178
|
+
try {
|
|
179
|
+
const tx = new bsv.Transaction()
|
|
180
|
+
.from({
|
|
181
|
+
txId: 'a'.repeat(64),
|
|
182
|
+
outputIndex: 0,
|
|
183
|
+
script: bsv.Script.buildPublicKeyHashOut(new bsv.Address('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')).toHex(),
|
|
184
|
+
satoshis: 100000
|
|
185
|
+
})
|
|
186
|
+
.to('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', 90000)
|
|
187
|
+
|
|
188
|
+
const preimage = bsv.Transaction.sighash.sighashPreimage(
|
|
189
|
+
tx,
|
|
190
|
+
bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID,
|
|
191
|
+
0,
|
|
192
|
+
bsv.Script.buildPublicKeyHashOut(new bsv.Address('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')),
|
|
193
|
+
new bsv.crypto.BN(100000)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
console.log(chalk.blue(`Generated preimage: ${preimage.toString('hex').substring(0, 64)}...`))
|
|
197
|
+
|
|
198
|
+
// Test field extraction
|
|
199
|
+
const fields = ['value', 'nVersion', 'nLocktime', 'hashOutputs']
|
|
200
|
+
|
|
201
|
+
fields.forEach(field => {
|
|
202
|
+
console.log(chalk.yellow(`\nTesting ${field} extraction:`))
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const result = bsv.SmartContract.testFieldExtraction(
|
|
206
|
+
preimage.toString('hex'),
|
|
207
|
+
field
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if (result.success) {
|
|
211
|
+
console.log(chalk.green(`✅ ${field} extraction successful`))
|
|
212
|
+
if (result.fieldExtraction) {
|
|
213
|
+
console.log(chalk.blue(` Value: ${result.fieldExtraction.value}`))
|
|
214
|
+
if (result.fieldExtraction.interpretation) {
|
|
215
|
+
console.log(chalk.blue(` Interpreted: ${result.fieldExtraction.interpretation.description || result.fieldExtraction.interpretation.satoshis || 'N/A'}`))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
console.log(chalk.red(`❌ ${field} extraction failed`))
|
|
220
|
+
console.log(chalk.red(` Error: ${result.error || 'Unknown error'}`))
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.log(chalk.red(`❌ ${field} extraction error: ${error.message}`))
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.log(chalk.red('❌ Demo error:', error.message))
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* CovenantBuilderTest - Test the covenant builder functionality
|
|
235
|
+
*/
|
|
236
|
+
class CovenantBuilderTest {
|
|
237
|
+
constructor() {
|
|
238
|
+
console.log(chalk.bold.magenta('\n🏗️ Covenant Builder Test\n'))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
testCovenantBuilder() {
|
|
242
|
+
console.log(chalk.cyan('Testing CovenantBuilder Functionality'))
|
|
243
|
+
console.log(chalk.cyan('===================================\n'))
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Test 1: Simple covenant using CovenantTemplates
|
|
247
|
+
console.log(chalk.yellow('1. Testing CovenantTemplates.valueLock'))
|
|
248
|
+
|
|
249
|
+
const valueLockCovenant = bsv.SmartContract.CovenantTemplates.valueLock('50c3000000000000') // 50000 satoshis in little-endian hex
|
|
250
|
+
const builtCovenant = valueLockCovenant.build()
|
|
251
|
+
|
|
252
|
+
console.log(chalk.blue(` ASM: ${builtCovenant.cleanedASM}`))
|
|
253
|
+
console.log(chalk.blue(` Hex: ${builtCovenant.hex}`))
|
|
254
|
+
console.log(chalk.blue(` Size: ${builtCovenant.size} operations`))
|
|
255
|
+
|
|
256
|
+
// Test 2: Custom covenant using CovenantBuilder
|
|
257
|
+
console.log(chalk.yellow('\n2. Testing Custom CovenantBuilder'))
|
|
258
|
+
|
|
259
|
+
const customBuilder = new bsv.SmartContract.CovenantBuilder()
|
|
260
|
+
const customCovenant = customBuilder
|
|
261
|
+
.comment('Simple value validation')
|
|
262
|
+
.extractField('value')
|
|
263
|
+
.push(100000)
|
|
264
|
+
.greaterThanOrEqual()
|
|
265
|
+
.verify()
|
|
266
|
+
.build()
|
|
267
|
+
|
|
268
|
+
console.log(chalk.blue(` Custom ASM: ${customCovenant.cleanedASM}`))
|
|
269
|
+
console.log(chalk.blue(` Custom Hex: ${customCovenant.hex}`))
|
|
270
|
+
|
|
271
|
+
// Test 3: Quick covenant creation
|
|
272
|
+
console.log(chalk.yellow('\n3. Testing Quick Covenant Creation'))
|
|
273
|
+
|
|
274
|
+
const quickCovenant = bsv.SmartContract.createQuickCovenant('value_lock', {
|
|
275
|
+
value: 75000
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
console.log(chalk.blue(` Quick ASM: ${quickCovenant.asm}`))
|
|
279
|
+
console.log(chalk.blue(` Quick Hex: ${quickCovenant.hex}`))
|
|
280
|
+
|
|
281
|
+
console.log(chalk.green('\n✅ All covenant builder tests completed'))
|
|
282
|
+
|
|
283
|
+
} catch (error) {
|
|
284
|
+
console.log(chalk.red('❌ Covenant builder test error:', error.message))
|
|
285
|
+
console.error(error.stack)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Main execution function
|
|
292
|
+
*/
|
|
293
|
+
async function main() {
|
|
294
|
+
try {
|
|
295
|
+
console.log(chalk.bold.blue('Working Smart Contract Examples'))
|
|
296
|
+
console.log(chalk.blue('===============================\n'))
|
|
297
|
+
|
|
298
|
+
// 1. Test field extraction first
|
|
299
|
+
const fieldDemo = new SimpleFieldExtractionDemo()
|
|
300
|
+
fieldDemo.demonstrateFieldExtraction()
|
|
301
|
+
|
|
302
|
+
// 2. Test covenant builder
|
|
303
|
+
const builderTest = new CovenantBuilderTest()
|
|
304
|
+
builderTest.testCovenantBuilder()
|
|
305
|
+
|
|
306
|
+
// 3. Test working contract
|
|
307
|
+
console.log(chalk.bold.magenta('\n💰 Working Amount Contract Test\n'))
|
|
308
|
+
|
|
309
|
+
const workingContract = new WorkingAmountContract(100000) // 100k sats
|
|
310
|
+
workingContract.runCompleteTest()
|
|
311
|
+
|
|
312
|
+
// 4. Show contract info
|
|
313
|
+
console.log(chalk.bold.yellow('\n📋 Contract Information'))
|
|
314
|
+
console.log(chalk.yellow('======================='))
|
|
315
|
+
|
|
316
|
+
const info = workingContract.getContractInfo()
|
|
317
|
+
console.log(chalk.blue(`Expected Amount: ${info.expectedAmount} satoshis`))
|
|
318
|
+
console.log(chalk.blue(`Contract Address: ${info.address}`))
|
|
319
|
+
console.log(chalk.blue(`Script Size: ${info.script.size} bytes`))
|
|
320
|
+
console.log(chalk.blue(`Script ASM: ${info.script.asm}`))
|
|
321
|
+
|
|
322
|
+
console.log(chalk.bold.green('\n🎉 All working examples completed successfully!'))
|
|
323
|
+
|
|
324
|
+
// Usage instructions
|
|
325
|
+
console.log(chalk.bold.yellow('\n📚 Key Learnings:'))
|
|
326
|
+
console.log(chalk.yellow('1. Use bsv.SmartContract.createQuickCovenant() for simple contracts'))
|
|
327
|
+
console.log(chalk.yellow('2. Field extraction works with proper preimage format'))
|
|
328
|
+
console.log(chalk.yellow('3. CovenantBuilder.build() returns an object, not a Script'))
|
|
329
|
+
console.log(chalk.yellow('4. Convert covenant ASM to Script using bsv.Script.fromASM()'))
|
|
330
|
+
console.log(chalk.yellow('5. Test with real preimages for accurate validation'))
|
|
331
|
+
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error(chalk.bold.red('\nDemo Error:'), error.message)
|
|
334
|
+
console.error(chalk.red('Stack:'), error.stack)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Export for use in other modules
|
|
339
|
+
module.exports = {
|
|
340
|
+
WorkingAmountContract,
|
|
341
|
+
SimpleFieldExtractionDemo,
|
|
342
|
+
CovenantBuilderTest
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Run demo if executed directly
|
|
346
|
+
if (require.main === module) {
|
|
347
|
+
main().catch(console.error)
|
|
348
|
+
}
|
package/index.js
CHANGED
|
@@ -115,6 +115,13 @@ try {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Browser-compatible UTXO Manager (always available)
|
|
119
|
+
try {
|
|
120
|
+
bsv.BrowserUTXOManager = require('./lib/browser-utxo-manager-es5')
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// BrowserUTXOManager not available
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
// Node.js specific tools (advanced development tools)
|
|
119
126
|
if (typeof window === 'undefined' && typeof require === 'function') {
|
|
120
127
|
try {
|
|
@@ -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
|
+
}
|