@smartledger/bsv 3.1.1 → 3.2.1
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 +123 -1
- package/README.md +233 -277
- package/bsv.bundle.js +39 -0
- package/bsv.min.js +8 -8
- package/docs/ADVANCED_COVENANT_DEVELOPMENT.md +533 -0
- package/docs/COVENANT_DEVELOPMENT_RESOLVED.md +169 -0
- package/docs/CUSTOM_SCRIPT_DEVELOPMENT.md +320 -0
- package/docs/README.md +201 -0
- package/docs/block.md +46 -0
- package/docs/ecies.md +102 -0
- package/docs/index.md +104 -0
- package/docs/nchain.md +958 -0
- package/docs/networks.md +55 -0
- package/docs/preimage.md +126 -0
- package/docs/script.md +139 -0
- package/docs/transaction.md +174 -0
- package/docs/unspentoutput.md +32 -0
- package/examples/README.md +200 -0
- package/examples/basic/transaction-creation.js +534 -0
- package/examples/basic/transaction_signature_api_gap.js +178 -0
- package/examples/covenants/advanced_covenant_demo.js +219 -0
- package/examples/covenants/covenant_interface_demo.js +270 -0
- package/examples/covenants/covenant_manual_signature_resolved.js +212 -0
- package/examples/covenants/covenant_signature_template.js +117 -0
- package/examples/covenants2/covenant_bidirectional_example.js +262 -0
- package/examples/covenants2/covenant_utils_demo.js +120 -0
- package/examples/covenants2/preimage_covenant_utils.js +287 -0
- package/examples/covenants2/production_integration.js +256 -0
- package/examples/data/covenant_utxos.json +28 -0
- package/examples/data/utxos.json +26 -0
- package/examples/preimage/README.md +178 -0
- package/examples/preimage/extract_preimage_bidirectional.js +421 -0
- package/examples/preimage/generate_sample_preimage.js +208 -0
- package/examples/preimage/generate_sighash_examples.js +152 -0
- package/examples/preimage/parse_preimage.js +117 -0
- package/examples/preimage/test_preimage_extractor.js +53 -0
- package/examples/preimage/test_varint_extraction.js +95 -0
- package/examples/scripts/custom_script_helper_example.js +273 -0
- package/examples/scripts/custom_script_signature_test.js +344 -0
- package/examples/scripts/script_interpreter.js +193 -0
- package/examples/smart_contract/complete_workflow_demo.js +343 -0
- package/examples/smart_contract/covenant_builder_demo.js +176 -0
- package/examples/smart_contract/script_testing_integration.js +198 -0
- package/index.js +3 -0
- package/lib/covenant-interface.js +713 -0
- package/lib/opcode.js +14 -7
- package/lib/smart_contract/API_REFERENCE.md +862 -0
- package/lib/smart_contract/DOCUMENTATION_SUMMARY.md +201 -0
- package/lib/smart_contract/EXAMPLES.md +751 -0
- package/lib/smart_contract/QUICK_START.md +549 -0
- package/lib/smart_contract/README.md +395 -0
- package/lib/smart_contract/builder.js +452 -0
- package/lib/smart_contract/covenant.js +336 -0
- package/lib/smart_contract/covenant_builder.js +512 -0
- package/lib/smart_contract/index.js +350 -0
- package/lib/smart_contract/opcode_list.js +30 -0
- package/lib/smart_contract/opcode_map.js +1174 -0
- package/lib/smart_contract/opcodes.md +1173 -0
- package/lib/smart_contract/preimage.js +903 -0
- package/lib/smart_contract/script_interpreter.js +236 -0
- package/lib/smart_contract/script_tester.js +487 -0
- package/lib/smart_contract/script_utils.js +621 -0
- package/lib/smart_contract/sighash.js +310 -0
- package/lib/smart_contract/smartledger-opcode_review.md +70 -0
- package/lib/smart_contract/stack_examiner.js +129 -0
- package/lib/smart_contract/test_integration.js +269 -0
- package/lib/smart_contract/utxo_generator.js +367 -0
- package/package.json +43 -10
- package/utilities/blockchain-state.json +20478 -3
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartContract.Covenant Class
|
|
3
|
+
* ============================
|
|
4
|
+
*
|
|
5
|
+
* Production-ready covenant framework with:
|
|
6
|
+
* - Complete P2PKH → Covenant → Spending workflow
|
|
7
|
+
* - Local UTXO storage and portfolio management
|
|
8
|
+
* - WhatsOnChain API integration ready
|
|
9
|
+
* - BIP-143 preimage validation
|
|
10
|
+
* - Script.Interpreter compliance
|
|
11
|
+
*
|
|
12
|
+
* Based on examples/covenants2/preimage_covenant_utils.js
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict'
|
|
16
|
+
|
|
17
|
+
var bsv = require('../..')
|
|
18
|
+
var path = require('path')
|
|
19
|
+
|
|
20
|
+
// Optional fs dependency for Node.js environments
|
|
21
|
+
var fs
|
|
22
|
+
try {
|
|
23
|
+
fs = require('fs')
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Browser environment - fs operations will be disabled
|
|
26
|
+
fs = null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Covenant Class - Advanced covenant management
|
|
31
|
+
* @param {PrivateKey} privateKey - Private key for covenant operations
|
|
32
|
+
* @param {Object} options - Configuration options
|
|
33
|
+
*/
|
|
34
|
+
function Covenant(privateKey, options) {
|
|
35
|
+
if (!(this instanceof Covenant)) {
|
|
36
|
+
return new Covenant(privateKey, options)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.privateKey = privateKey
|
|
40
|
+
this.publicKey = privateKey.publicKey
|
|
41
|
+
this.address = privateKey.toAddress()
|
|
42
|
+
|
|
43
|
+
options = options || {}
|
|
44
|
+
this.storageDir = options.storageDir || (typeof process !== 'undefined' ? path.join(process.cwd(), '.bsv-covenants') : '.bsv-covenants')
|
|
45
|
+
this.covenantUtxoPath = path.join(this.storageDir, 'covenant_utxos.json')
|
|
46
|
+
|
|
47
|
+
this.sighashType = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID
|
|
48
|
+
|
|
49
|
+
// Ensure storage directory exists (Node.js only)
|
|
50
|
+
if (fs) {
|
|
51
|
+
this._ensureStorageDir()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Create covenant from P2PKH UTXO
|
|
57
|
+
* @param {Object} utxo - P2PKH UTXO object
|
|
58
|
+
* @returns {Object} Creation result with transaction and covenant UTXO
|
|
59
|
+
*/
|
|
60
|
+
Covenant.prototype.createFromP2PKH = function(utxo) {
|
|
61
|
+
// Reconstruct P2PKH script if not provided
|
|
62
|
+
if (!utxo.script) {
|
|
63
|
+
utxo.script = Covenant.reconstructP2pkhScript(this.address)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create covenant creation transaction
|
|
67
|
+
var creationTx = new bsv.Transaction()
|
|
68
|
+
.from({
|
|
69
|
+
txId: utxo.txid,
|
|
70
|
+
outputIndex: utxo.vout,
|
|
71
|
+
script: utxo.script,
|
|
72
|
+
satoshis: utxo.satoshis
|
|
73
|
+
})
|
|
74
|
+
.to(this.address, utxo.satoshis - 1000) // 1000 sat fee
|
|
75
|
+
|
|
76
|
+
// Get preimage for covenant creation
|
|
77
|
+
var p2pkhScript = bsv.Script.fromHex(utxo.script)
|
|
78
|
+
var creationPreimage = bsv.Transaction.sighash.sighashPreimage(
|
|
79
|
+
creationTx,
|
|
80
|
+
this.sighashType,
|
|
81
|
+
0,
|
|
82
|
+
p2pkhScript,
|
|
83
|
+
new bsv.crypto.BN(utxo.satoshis)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
var preimageHash = bsv.crypto.Hash.sha256sha256(creationPreimage)
|
|
87
|
+
|
|
88
|
+
// Build covenant locking script
|
|
89
|
+
var covenantLockingScript = new bsv.Script()
|
|
90
|
+
.add('OP_DUP')
|
|
91
|
+
.add('OP_HASH256')
|
|
92
|
+
.add(preimageHash)
|
|
93
|
+
.add('OP_EQUALVERIFY')
|
|
94
|
+
.add('OP_DROP') // Stack management
|
|
95
|
+
.add(this.publicKey.toBuffer())
|
|
96
|
+
.add('OP_CHECKSIG')
|
|
97
|
+
|
|
98
|
+
// Replace output script with covenant
|
|
99
|
+
creationTx.outputs[0].setScript(covenantLockingScript)
|
|
100
|
+
|
|
101
|
+
// Sign creation transaction (against P2PKH)
|
|
102
|
+
creationTx.sign(this.privateKey)
|
|
103
|
+
|
|
104
|
+
var covenantUtxo = {
|
|
105
|
+
txid: creationTx.id,
|
|
106
|
+
vout: 0,
|
|
107
|
+
satoshis: utxo.satoshis - 1000,
|
|
108
|
+
script: covenantLockingScript.toHex(),
|
|
109
|
+
scriptPubKey: covenantLockingScript.toHex(),
|
|
110
|
+
preimageHash: preimageHash.toString('hex'),
|
|
111
|
+
originalPreimage: creationPreimage.toString('hex'),
|
|
112
|
+
status: 'local',
|
|
113
|
+
createdAt: new Date().toISOString(),
|
|
114
|
+
type: 'preimage_covenant'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
transaction: creationTx,
|
|
119
|
+
covenantUtxo: covenantUtxo
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Create spending transaction for covenant UTXO
|
|
125
|
+
* @param {Object} covenantUtxo - Covenant UTXO to spend
|
|
126
|
+
* @param {Address|string} outputAddress - Output address (optional)
|
|
127
|
+
* @param {number} outputSatoshis - Output amount (optional)
|
|
128
|
+
* @returns {Transaction} Spending transaction
|
|
129
|
+
*/
|
|
130
|
+
Covenant.prototype.createSpendingTx = function(covenantUtxo, outputAddress, outputSatoshis) {
|
|
131
|
+
var outputAddr = outputAddress || this.address
|
|
132
|
+
var outputSats = outputSatoshis || (covenantUtxo.satoshis - 500) // 500 sat fee
|
|
133
|
+
|
|
134
|
+
// Create spending transaction - manual input for custom scripts
|
|
135
|
+
var spendingTx = new bsv.Transaction()
|
|
136
|
+
|
|
137
|
+
// Manually add input for custom covenant script
|
|
138
|
+
spendingTx.addInput(new bsv.Transaction.Input({
|
|
139
|
+
prevTxId: covenantUtxo.txid,
|
|
140
|
+
outputIndex: covenantUtxo.vout,
|
|
141
|
+
script: bsv.Script.empty() // Will be set after signing
|
|
142
|
+
}), bsv.Script.fromHex(covenantUtxo.script), covenantUtxo.satoshis)
|
|
143
|
+
|
|
144
|
+
// Add output
|
|
145
|
+
spendingTx.to(outputAddr, outputSats)
|
|
146
|
+
|
|
147
|
+
var covenantScript = bsv.Script.fromHex(covenantUtxo.script)
|
|
148
|
+
|
|
149
|
+
// Create signature against covenant script
|
|
150
|
+
var covenantSignature = bsv.Transaction.sighash.sign(
|
|
151
|
+
spendingTx,
|
|
152
|
+
this.privateKey,
|
|
153
|
+
this.sighashType,
|
|
154
|
+
0,
|
|
155
|
+
covenantScript,
|
|
156
|
+
new bsv.crypto.BN(covenantUtxo.satoshis)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
var fullSignature = Buffer.concat([
|
|
160
|
+
covenantSignature.toDER(),
|
|
161
|
+
Buffer.from([this.sighashType])
|
|
162
|
+
])
|
|
163
|
+
|
|
164
|
+
// Use original preimage from covenant creation
|
|
165
|
+
var originalPreimage = Buffer.from(covenantUtxo.originalPreimage, 'hex')
|
|
166
|
+
|
|
167
|
+
// Create unlocking script
|
|
168
|
+
var unlockingScript = new bsv.Script()
|
|
169
|
+
.add(fullSignature)
|
|
170
|
+
.add(originalPreimage)
|
|
171
|
+
|
|
172
|
+
spendingTx.inputs[0].setScript(unlockingScript)
|
|
173
|
+
|
|
174
|
+
return spendingTx
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Validate covenant transaction using Script.Interpreter
|
|
179
|
+
* @param {Transaction} spendingTx - Transaction to validate
|
|
180
|
+
* @param {Object} covenantUtxo - Original covenant UTXO
|
|
181
|
+
* @returns {Object} Validation result
|
|
182
|
+
*/
|
|
183
|
+
Covenant.prototype.validate = function(spendingTx, covenantUtxo) {
|
|
184
|
+
var interpreter = new bsv.Script.Interpreter()
|
|
185
|
+
var flags = bsv.Script.Interpreter.SCRIPT_VERIFY_P2SH |
|
|
186
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
187
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_DERSIG |
|
|
188
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_LOW_S |
|
|
189
|
+
bsv.Script.Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
|
|
190
|
+
|
|
191
|
+
var unlockingScript = spendingTx.inputs[0].script
|
|
192
|
+
var lockingScript = bsv.Script.fromHex(covenantUtxo.script)
|
|
193
|
+
|
|
194
|
+
var result = interpreter.verify(
|
|
195
|
+
unlockingScript,
|
|
196
|
+
lockingScript,
|
|
197
|
+
spendingTx,
|
|
198
|
+
0,
|
|
199
|
+
flags,
|
|
200
|
+
new bsv.crypto.BN(covenantUtxo.satoshis)
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
valid: result,
|
|
205
|
+
error: result ? null : interpreter.errstr
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Save covenant UTXO to local storage
|
|
211
|
+
* @param {Object} covenantUtxo - Covenant UTXO to save
|
|
212
|
+
*/
|
|
213
|
+
Covenant.prototype.save = function(covenantUtxo) {
|
|
214
|
+
if (!fs) {
|
|
215
|
+
console.warn('File system operations not available in browser environment')
|
|
216
|
+
return covenantUtxo
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var covenantUtxos = this.list()
|
|
220
|
+
covenantUtxos.push(covenantUtxo)
|
|
221
|
+
fs.writeFileSync(this.covenantUtxoPath, JSON.stringify(covenantUtxos, null, 2))
|
|
222
|
+
return covenantUtxo
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load covenant UTXO from storage
|
|
227
|
+
* @param {string} txid - Transaction ID (optional, loads latest if not provided)
|
|
228
|
+
* @returns {Object} Covenant UTXO
|
|
229
|
+
*/
|
|
230
|
+
Covenant.prototype.load = function(txid) {
|
|
231
|
+
var covenantUtxos = this.list()
|
|
232
|
+
|
|
233
|
+
if (txid) {
|
|
234
|
+
var utxo = covenantUtxos.find(function(u) { return u.txid === txid })
|
|
235
|
+
if (!utxo) {
|
|
236
|
+
throw new Error('Covenant UTXO with TXID ' + txid + ' not found')
|
|
237
|
+
}
|
|
238
|
+
return utxo
|
|
239
|
+
} else {
|
|
240
|
+
var utxo = covenantUtxos.find(function(u) { return u.type === 'preimage_covenant' })
|
|
241
|
+
if (!utxo) {
|
|
242
|
+
throw new Error('No preimage covenant UTXO found')
|
|
243
|
+
}
|
|
244
|
+
return utxo
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* List all stored covenant UTXOs
|
|
250
|
+
* @returns {Array} Array of covenant UTXOs
|
|
251
|
+
*/
|
|
252
|
+
Covenant.prototype.list = function() {
|
|
253
|
+
if (!fs.existsSync(this.covenantUtxoPath)) {
|
|
254
|
+
return []
|
|
255
|
+
}
|
|
256
|
+
return JSON.parse(fs.readFileSync(this.covenantUtxoPath, 'utf8'))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get covenant portfolio statistics
|
|
261
|
+
* @returns {Object} Portfolio statistics
|
|
262
|
+
*/
|
|
263
|
+
Covenant.prototype.getPortfolio = function() {
|
|
264
|
+
var covenants = this.list()
|
|
265
|
+
|
|
266
|
+
var portfolio = {
|
|
267
|
+
total: covenants.length,
|
|
268
|
+
totalValue: covenants.reduce(function(sum, c) { return sum + c.satoshis }, 0),
|
|
269
|
+
byStatus: {},
|
|
270
|
+
recent: covenants.slice(-5) // Last 5 created
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Group by status
|
|
274
|
+
covenants.forEach(function(covenant) {
|
|
275
|
+
var status = covenant.status || 'local'
|
|
276
|
+
if (!portfolio.byStatus[status]) {
|
|
277
|
+
portfolio.byStatus[status] = { count: 0, value: 0 }
|
|
278
|
+
}
|
|
279
|
+
portfolio.byStatus[status].count++
|
|
280
|
+
portfolio.byStatus[status].value += covenant.satoshis
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return portfolio
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Complete covenant flow: P2PKH → Covenant → Spending
|
|
288
|
+
* @param {Object} p2pkhUtxo - P2PKH UTXO to convert
|
|
289
|
+
* @param {Function} broadcastCallback - Optional broadcast callback
|
|
290
|
+
* @returns {Object} Complete flow result
|
|
291
|
+
*/
|
|
292
|
+
Covenant.prototype.completeFlow = function(p2pkhUtxo, broadcastCallback) {
|
|
293
|
+
// Phase 1: Create covenant from P2PKH
|
|
294
|
+
var creation = this.createFromP2PKH(p2pkhUtxo)
|
|
295
|
+
var covenantUtxo = this.save(creation.covenantUtxo)
|
|
296
|
+
|
|
297
|
+
// Optional: Broadcast creation transaction
|
|
298
|
+
if (broadcastCallback) {
|
|
299
|
+
broadcastCallback(creation.transaction, 'creation')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Phase 2: Spend covenant UTXO
|
|
303
|
+
var spendingTx = this.createSpendingTx(covenantUtxo)
|
|
304
|
+
var validation = this.validate(spendingTx, covenantUtxo)
|
|
305
|
+
|
|
306
|
+
// Optional: Broadcast spending transaction
|
|
307
|
+
if (broadcastCallback && validation.valid) {
|
|
308
|
+
broadcastCallback(spendingTx, 'spending')
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
creationTx: creation.transaction,
|
|
313
|
+
spendingTx: spendingTx,
|
|
314
|
+
covenantUtxo: covenantUtxo,
|
|
315
|
+
validation: validation,
|
|
316
|
+
success: validation.valid
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Private helper methods
|
|
322
|
+
*/
|
|
323
|
+
Covenant.prototype._ensureStorageDir = function() {
|
|
324
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
325
|
+
fs.mkdirSync(this.storageDir, { recursive: true })
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Static utility methods
|
|
331
|
+
*/
|
|
332
|
+
Covenant.reconstructP2pkhScript = function(address) {
|
|
333
|
+
return bsv.Script.buildPublicKeyHashOut(address).toHex()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = Covenant
|