smartledger-bsv 3.1.0 ā 3.2.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 +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 +754 -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 +311 -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_tester.js +487 -0
- package/lib/smart_contract/script_utils.js +609 -0
- package/lib/smart_contract/sighash.js +310 -0
- package/lib/smart_contract/smartledger-opcode_review.md +70 -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,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demonstration of Preimage Covenant Utils
|
|
5
|
+
*
|
|
6
|
+
* Shows the clean API for proper covenant flow
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const bsv = require('../../index.js');
|
|
10
|
+
const PreimageCovenantUtils = require('./preimage_covenant_utils');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
console.log('š Preimage Covenant Utils Demonstration');
|
|
15
|
+
console.log('=======================================\n');
|
|
16
|
+
|
|
17
|
+
// Initialize with private key
|
|
18
|
+
const privateKey = bsv.PrivateKey.fromWIF('L5JREiiMfP5enqsRuhyNEv8SRjrjnMXNhkQEgNvzDRhGTaHG9ZFm');
|
|
19
|
+
const covenantUtils = new PreimageCovenantUtils(privateKey);
|
|
20
|
+
|
|
21
|
+
console.log('Wallet:', covenantUtils.address.toString());
|
|
22
|
+
console.log('');
|
|
23
|
+
|
|
24
|
+
// Load a P2PKH UTXO (from WhatsOnChain in real scenario)
|
|
25
|
+
function getP2pkhUtxo() {
|
|
26
|
+
const utxoPath = path.join(__dirname, '../data/utxos.json');
|
|
27
|
+
const utxos = JSON.parse(fs.readFileSync(utxoPath, 'utf8'));
|
|
28
|
+
|
|
29
|
+
const utxo = utxos.find(u => u.status === 'confirmed' && u.satoshis >= 10000);
|
|
30
|
+
if (!utxo) {
|
|
31
|
+
throw new Error('No suitable P2PKH UTXO found');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return utxo;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Example broadcast callback (would integrate with WhatsOnChain/etc in production)
|
|
38
|
+
function broadcastCallback(tx, phase) {
|
|
39
|
+
console.log(`š” Would broadcast ${phase} transaction:`, tx.id);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
console.log('- Size:', tx.toString('hex').length / 2, 'bytes'); // ā
Using toString('hex')
|
|
43
|
+
console.log('- Fee:', tx.getFee(), 'satoshis');
|
|
44
|
+
console.log('- Valid:', tx.verify());
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.log('- Validation pending (custom script)');
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
// In production: POST to WhatsOnChain or other broadcast service
|
|
51
|
+
// fetch('https://api.whatsonchain.com/v1/bsv/main/tx/raw', {
|
|
52
|
+
// method: 'POST',
|
|
53
|
+
// body: tx.toString('hex') // ā
Using toString('hex')
|
|
54
|
+
// });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
console.log('Example 1: Complete Covenant Flow');
|
|
59
|
+
console.log('=================================');
|
|
60
|
+
|
|
61
|
+
const p2pkhUtxo = getP2pkhUtxo();
|
|
62
|
+
console.log('š¦ Using P2PKH UTXO:', p2pkhUtxo.txid);
|
|
63
|
+
console.log('');
|
|
64
|
+
|
|
65
|
+
const result = covenantUtils.completeCovenantFlow(p2pkhUtxo, broadcastCallback);
|
|
66
|
+
|
|
67
|
+
if (result.success) {
|
|
68
|
+
console.log('š Complete success!\n');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log('Example 2: Working with Stored Covenant UTXOs');
|
|
72
|
+
console.log('=============================================');
|
|
73
|
+
|
|
74
|
+
// List all stored covenant UTXOs
|
|
75
|
+
const storedUtxos = covenantUtils.listCovenantUtxos();
|
|
76
|
+
console.log('š Stored covenant UTXOs:', storedUtxos.length);
|
|
77
|
+
|
|
78
|
+
storedUtxos.forEach((utxo, i) => {
|
|
79
|
+
console.log(`- UTXO ${i + 1}:`);
|
|
80
|
+
console.log(` - TXID: ${utxo.txid}`);
|
|
81
|
+
console.log(` - Satoshis: ${utxo.satoshis}`);
|
|
82
|
+
console.log(` - Created: ${utxo.createdAt}`);
|
|
83
|
+
console.log(` - Status: ${utxo.status}`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log('');
|
|
87
|
+
|
|
88
|
+
console.log('Example 3: Spending Existing Covenant UTXO');
|
|
89
|
+
console.log('==========================================');
|
|
90
|
+
|
|
91
|
+
// Load and spend the most recent covenant UTXO
|
|
92
|
+
if (storedUtxos.length > 0) {
|
|
93
|
+
const latestUtxo = storedUtxos[storedUtxos.length - 1];
|
|
94
|
+
console.log('š Spending covenant UTXO:', latestUtxo.txid);
|
|
95
|
+
|
|
96
|
+
const spendingTx = covenantUtils.createCovenantSpendingTx(latestUtxo);
|
|
97
|
+
const validation = covenantUtils.validateCovenantTx(spendingTx, latestUtxo);
|
|
98
|
+
|
|
99
|
+
console.log('- Spending TX:', spendingTx.id);
|
|
100
|
+
console.log('- Valid:', validation.valid ? 'ā
SUCCESS' : 'ā FAILED');
|
|
101
|
+
console.log('- Error:', validation.error || 'none');
|
|
102
|
+
|
|
103
|
+
if (validation.valid) {
|
|
104
|
+
broadcastCallback(spendingTx, 'covenant spending');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('\nšÆ Production Integration Notes:');
|
|
109
|
+
console.log('==============================');
|
|
110
|
+
console.log('ā
P2PKH UTXOs: Fetch from WhatsOnChain API');
|
|
111
|
+
console.log('ā
Script Reconstruction: Built-in with address');
|
|
112
|
+
console.log('ā
Covenant Storage: Local JSON with custom scripts');
|
|
113
|
+
console.log('ā
Broadcasting: Integrate with WhatsOnChain/other APIs');
|
|
114
|
+
console.log('ā
Preimage Validation: Automatic with original preimage');
|
|
115
|
+
console.log('ā
Signature Context: Proper covenant script signing');
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.log('ā Error:', error.message);
|
|
119
|
+
console.log('Stack:', error.stack);
|
|
120
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Preimage Covenant Utilities
|
|
5
|
+
*
|
|
6
|
+
* Provides clean interfaces for the proper covenant flow:
|
|
7
|
+
* 1. P2PKH UTXO ā Covenant Creation ā Local Storage
|
|
8
|
+
* 2. Local Covenant UTXO ā Covenant Spending ā Broadcast
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const bsv = require('../../index.js');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
class PreimageCovenantUtils {
|
|
16
|
+
constructor(privateKey) {
|
|
17
|
+
this.privateKey = privateKey;
|
|
18
|
+
this.publicKey = privateKey.publicKey;
|
|
19
|
+
this.address = privateKey.toAddress();
|
|
20
|
+
this.covenantUtxoPath = path.join(__dirname, '../data/covenant_utxos.json');
|
|
21
|
+
this.sighashType = bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* š¦ Reconstruct P2PKH script from address
|
|
26
|
+
* (As you mentioned: all UTXOs from WhatsOnChain are P2PKH)
|
|
27
|
+
*/
|
|
28
|
+
static reconstructP2pkhScript(address) {
|
|
29
|
+
return bsv.Script.buildPublicKeyHashOut(address).toHex();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* šļø Create covenant from P2PKH UTXO
|
|
34
|
+
*/
|
|
35
|
+
createCovenantFromP2pkh(utxo) {
|
|
36
|
+
console.log('šļø Creating covenant from P2PKH UTXO...');
|
|
37
|
+
|
|
38
|
+
// Reconstruct P2PKH script
|
|
39
|
+
utxo.script = PreimageCovenantUtils.reconstructP2pkhScript(this.address);
|
|
40
|
+
|
|
41
|
+
// Create covenant creation transaction
|
|
42
|
+
const creationTx = new bsv.Transaction()
|
|
43
|
+
.from({
|
|
44
|
+
txId: utxo.txid,
|
|
45
|
+
outputIndex: utxo.vout,
|
|
46
|
+
script: utxo.script,
|
|
47
|
+
satoshis: utxo.satoshis
|
|
48
|
+
})
|
|
49
|
+
.to(this.address, utxo.satoshis - 1000); // 1000 sat fee
|
|
50
|
+
|
|
51
|
+
// Get preimage for covenant creation
|
|
52
|
+
const p2pkhScript = bsv.Script.fromHex(utxo.script);
|
|
53
|
+
const creationPreimage = bsv.Transaction.sighash.sighash(
|
|
54
|
+
creationTx,
|
|
55
|
+
this.sighashType,
|
|
56
|
+
0,
|
|
57
|
+
p2pkhScript,
|
|
58
|
+
new bsv.crypto.BN(utxo.satoshis)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const preimageHash = bsv.crypto.Hash.sha256sha256(creationPreimage);
|
|
62
|
+
|
|
63
|
+
// Build covenant locking script with OP_DROP fix
|
|
64
|
+
const covenantLockingScript = new bsv.Script()
|
|
65
|
+
.add('OP_DUP')
|
|
66
|
+
.add('OP_HASH256')
|
|
67
|
+
.add(preimageHash)
|
|
68
|
+
.add('OP_EQUALVERIFY')
|
|
69
|
+
.add('OP_DROP') // ā
Stack management
|
|
70
|
+
.add(this.publicKey.toBuffer())
|
|
71
|
+
.add('OP_CHECKSIG');
|
|
72
|
+
|
|
73
|
+
// Replace output script with covenant
|
|
74
|
+
creationTx.outputs[0].setScript(covenantLockingScript);
|
|
75
|
+
|
|
76
|
+
// Sign creation transaction (against P2PKH)
|
|
77
|
+
creationTx.sign(this.privateKey);
|
|
78
|
+
|
|
79
|
+
console.log('ā
Covenant creation transaction valid:', creationTx.verify());
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
transaction: creationTx,
|
|
83
|
+
covenantUtxo: {
|
|
84
|
+
txid: creationTx.id,
|
|
85
|
+
vout: 0,
|
|
86
|
+
satoshis: utxo.satoshis - 1000,
|
|
87
|
+
script: covenantLockingScript.toHex(),
|
|
88
|
+
scriptPubKey: covenantLockingScript.toHex(),
|
|
89
|
+
preimageHash: preimageHash.toString('hex'),
|
|
90
|
+
originalPreimage: creationPreimage.toString('hex'),
|
|
91
|
+
status: 'local',
|
|
92
|
+
createdAt: new Date().toISOString(),
|
|
93
|
+
type: 'preimage_covenant'
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* š¾ Save covenant UTXO to local storage
|
|
100
|
+
*/
|
|
101
|
+
saveCovenantUtxo(covenantUtxo) {
|
|
102
|
+
let covenantUtxos = [];
|
|
103
|
+
if (fs.existsSync(this.covenantUtxoPath)) {
|
|
104
|
+
covenantUtxos = JSON.parse(fs.readFileSync(this.covenantUtxoPath, 'utf8'));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Ensure data directory exists
|
|
108
|
+
const dataDir = path.dirname(this.covenantUtxoPath);
|
|
109
|
+
if (!fs.existsSync(dataDir)) {
|
|
110
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
covenantUtxos.push(covenantUtxo);
|
|
114
|
+
fs.writeFileSync(this.covenantUtxoPath, JSON.stringify(covenantUtxos, null, 2));
|
|
115
|
+
console.log('ā
Covenant UTXO saved to local storage');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* š Load covenant UTXO from local storage
|
|
120
|
+
*/
|
|
121
|
+
loadCovenantUtxo(txid = null) {
|
|
122
|
+
if (!fs.existsSync(this.covenantUtxoPath)) {
|
|
123
|
+
throw new Error('No covenant UTXOs found. Create one first.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const covenantUtxos = JSON.parse(fs.readFileSync(this.covenantUtxoPath, 'utf8'));
|
|
127
|
+
|
|
128
|
+
if (txid) {
|
|
129
|
+
const utxo = covenantUtxos.find(u => u.txid === txid);
|
|
130
|
+
if (!utxo) {
|
|
131
|
+
throw new Error(`Covenant UTXO with TXID ${txid} not found`);
|
|
132
|
+
}
|
|
133
|
+
return utxo;
|
|
134
|
+
} else {
|
|
135
|
+
const utxo = covenantUtxos.find(u => u.type === 'preimage_covenant');
|
|
136
|
+
if (!utxo) {
|
|
137
|
+
throw new Error('No preimage covenant UTXO found');
|
|
138
|
+
}
|
|
139
|
+
return utxo;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* š Create spending transaction for covenant UTXO
|
|
145
|
+
*/
|
|
146
|
+
createCovenantSpendingTx(covenantUtxo, outputAddress = null, outputSatoshis = null) {
|
|
147
|
+
console.log('š Creating covenant spending transaction...');
|
|
148
|
+
|
|
149
|
+
const outputAddr = outputAddress || this.address;
|
|
150
|
+
const outputSats = outputSatoshis || (covenantUtxo.satoshis - 500); // 500 sat fee
|
|
151
|
+
|
|
152
|
+
// Create spending transaction - don't use .from() for custom scripts
|
|
153
|
+
const spendingTx = new bsv.Transaction();
|
|
154
|
+
|
|
155
|
+
// Manually add input for custom covenant script
|
|
156
|
+
spendingTx.addInput(new bsv.Transaction.Input({
|
|
157
|
+
prevTxId: covenantUtxo.txid,
|
|
158
|
+
outputIndex: covenantUtxo.vout,
|
|
159
|
+
script: bsv.Script.empty() // Will be set after signing
|
|
160
|
+
}), bsv.Script.fromHex(covenantUtxo.script), covenantUtxo.satoshis);
|
|
161
|
+
|
|
162
|
+
// Add output
|
|
163
|
+
spendingTx.to(outputAddr, outputSats);
|
|
164
|
+
|
|
165
|
+
const covenantScript = bsv.Script.fromHex(covenantUtxo.script);
|
|
166
|
+
|
|
167
|
+
// Create signature against covenant script (not P2PKH!)
|
|
168
|
+
const covenantSignature = bsv.Transaction.sighash.sign(
|
|
169
|
+
spendingTx,
|
|
170
|
+
this.privateKey,
|
|
171
|
+
this.sighashType,
|
|
172
|
+
0,
|
|
173
|
+
covenantScript, // ā
Sign against covenant script
|
|
174
|
+
new bsv.crypto.BN(covenantUtxo.satoshis)
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const fullSignature = Buffer.concat([
|
|
178
|
+
covenantSignature.toDER(),
|
|
179
|
+
Buffer.from([this.sighashType])
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
// Use original preimage from covenant creation
|
|
183
|
+
const originalPreimage = Buffer.from(covenantUtxo.originalPreimage, 'hex');
|
|
184
|
+
|
|
185
|
+
// Create unlocking script
|
|
186
|
+
const unlockingScript = new bsv.Script()
|
|
187
|
+
.add(fullSignature)
|
|
188
|
+
.add(originalPreimage);
|
|
189
|
+
|
|
190
|
+
spendingTx.inputs[0].setScript(unlockingScript);
|
|
191
|
+
|
|
192
|
+
return spendingTx;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* ā
Validate covenant transaction with Script.Interpreter
|
|
197
|
+
*/
|
|
198
|
+
validateCovenantTx(spendingTx, covenantUtxo) {
|
|
199
|
+
const interpreter = new bsv.Script.Interpreter();
|
|
200
|
+
const flags = bsv.Script.Interpreter.SCRIPT_VERIFY_P2SH |
|
|
201
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
202
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_DERSIG |
|
|
203
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_LOW_S |
|
|
204
|
+
bsv.Script.Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID;
|
|
205
|
+
|
|
206
|
+
const unlockingScript = spendingTx.inputs[0].script;
|
|
207
|
+
const lockingScript = bsv.Script.fromHex(covenantUtxo.script);
|
|
208
|
+
|
|
209
|
+
const result = interpreter.verify(
|
|
210
|
+
unlockingScript,
|
|
211
|
+
lockingScript,
|
|
212
|
+
spendingTx,
|
|
213
|
+
0,
|
|
214
|
+
flags,
|
|
215
|
+
new bsv.crypto.BN(covenantUtxo.satoshis)
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!result) {
|
|
219
|
+
console.log('ā Validation failed:', interpreter.errstr);
|
|
220
|
+
} else {
|
|
221
|
+
console.log('ā
Covenant transaction validated successfully');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return { valid: result, error: interpreter.errstr };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* šÆ Complete covenant flow: P2PKH ā Covenant ā Spending
|
|
229
|
+
*/
|
|
230
|
+
completeCovenantFlow(p2pkhUtxo, broadcastCallback = null) {
|
|
231
|
+
console.log('šÆ Starting complete covenant flow...\n');
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Phase 1: Create covenant from P2PKH
|
|
235
|
+
const { transaction: creationTx, covenantUtxo } = this.createCovenantFromP2pkh(p2pkhUtxo);
|
|
236
|
+
this.saveCovenantUtxo(covenantUtxo);
|
|
237
|
+
|
|
238
|
+
console.log('Phase 1 Complete: P2PKH ā Covenant ā
');
|
|
239
|
+
console.log('- Creation TX:', creationTx.id);
|
|
240
|
+
console.log('- Covenant UTXO saved locally\n');
|
|
241
|
+
|
|
242
|
+
// Optional: Broadcast creation transaction
|
|
243
|
+
if (broadcastCallback) {
|
|
244
|
+
broadcastCallback(creationTx, 'creation');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Phase 2: Spend covenant UTXO
|
|
248
|
+
const spendingTx = this.createCovenantSpendingTx(covenantUtxo);
|
|
249
|
+
const validation = this.validateCovenantTx(spendingTx, covenantUtxo);
|
|
250
|
+
|
|
251
|
+
console.log('Phase 2 Complete: Covenant ā Spending ā
');
|
|
252
|
+
console.log('- Spending TX:', spendingTx.id);
|
|
253
|
+
console.log('- Validation:', validation.valid ? 'ā
SUCCESS' : 'ā FAILED');
|
|
254
|
+
|
|
255
|
+
// Optional: Broadcast spending transaction
|
|
256
|
+
if (broadcastCallback && validation.valid) {
|
|
257
|
+
broadcastCallback(spendingTx, 'covenant spending');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
creationTx,
|
|
262
|
+
spendingTx,
|
|
263
|
+
covenantUtxo,
|
|
264
|
+
validation,
|
|
265
|
+
success: validation.valid
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.log('ā Flow failed:', error.message);
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* š List all stored covenant UTXOs
|
|
276
|
+
*/
|
|
277
|
+
listCovenantUtxos() {
|
|
278
|
+
if (!fs.existsSync(this.covenantUtxoPath)) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const covenantUtxos = JSON.parse(fs.readFileSync(this.covenantUtxoPath, 'utf8'));
|
|
283
|
+
return covenantUtxos;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = PreimageCovenantUtils;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* š Production-Ready Preimage Covenant Integration
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates how the covenant flow integrates with:
|
|
7
|
+
* 1. WhatsOnChain API for UTXO fetching
|
|
8
|
+
* 2. Broadcasting transactions
|
|
9
|
+
* 3. Managing multiple covenant types
|
|
10
|
+
* 4. Real mainnet scenarios
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const bsv = require('../../index.js');
|
|
14
|
+
const PreimageCovenantUtils = require('./preimage_covenant_utils.js');
|
|
15
|
+
|
|
16
|
+
console.log('š Production Preimage Covenant Integration');
|
|
17
|
+
console.log('==========================================\n');
|
|
18
|
+
|
|
19
|
+
class ProductionCovenantManager {
|
|
20
|
+
constructor(privateKey) {
|
|
21
|
+
this.covenantUtils = new PreimageCovenantUtils(privateKey);
|
|
22
|
+
this.address = privateKey.toAddress().toString();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* š” Fetch UTXOs from WhatsOnChain (mainnet integration)
|
|
27
|
+
*/
|
|
28
|
+
async fetchUtxosFromWhatsOnChain() {
|
|
29
|
+
console.log('š” Fetching UTXOs from WhatsOnChain...');
|
|
30
|
+
|
|
31
|
+
// In production, this would be:
|
|
32
|
+
// const response = await fetch(`https://api.whatsonchain.com/v1/bsv/main/address/${this.address}/unspent`);
|
|
33
|
+
// const utxos = await response.json();
|
|
34
|
+
|
|
35
|
+
// For demo, simulate the response structure:
|
|
36
|
+
const mockUtxos = [
|
|
37
|
+
{
|
|
38
|
+
"height": 850234,
|
|
39
|
+
"tx_pos": 1,
|
|
40
|
+
"tx_hash": "98697480789ca50f967b9b324b44838d6b256e0db22206bf5c58f02fa652c864",
|
|
41
|
+
"value": 917852
|
|
42
|
+
}
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// Convert WhatsOnChain format to our format
|
|
46
|
+
const convertedUtxos = mockUtxos.map(utxo => ({
|
|
47
|
+
txid: utxo.tx_hash,
|
|
48
|
+
vout: utxo.tx_pos,
|
|
49
|
+
satoshis: utxo.value,
|
|
50
|
+
height: utxo.height,
|
|
51
|
+
status: 'confirmed',
|
|
52
|
+
// Script will be reconstructed from address (as you mentioned)
|
|
53
|
+
script: PreimageCovenantUtils.reconstructP2pkhScript(
|
|
54
|
+
bsv.Address.fromString(this.address)
|
|
55
|
+
)
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
console.log(`ā
Found ${convertedUtxos.length} UTXOs for address ${this.address}`);
|
|
59
|
+
return convertedUtxos;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* š¤ Broadcast transaction to WhatsOnChain
|
|
64
|
+
*/
|
|
65
|
+
async broadcastTransaction(tx, description) {
|
|
66
|
+
console.log(`š¤ Broadcasting ${description}...`);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const txHex = tx.toString('hex'); // ā
Using toString('hex') instead of serialize()
|
|
70
|
+
console.log(`- Transaction ID: ${tx.id}`);
|
|
71
|
+
console.log(`- Size: ${txHex.length / 2} bytes`);
|
|
72
|
+
console.log(`- Fee: ${tx.getFee()} satoshis`);
|
|
73
|
+
|
|
74
|
+
// In production, this would be:
|
|
75
|
+
// const response = await fetch('https://api.whatsonchain.com/v1/bsv/main/tx/raw', {
|
|
76
|
+
// method: 'POST',
|
|
77
|
+
// headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
// body: JSON.stringify({ txhex: txHex })
|
|
79
|
+
// });
|
|
80
|
+
|
|
81
|
+
// Simulate successful broadcast
|
|
82
|
+
console.log(`ā
${description} broadcast successfully`);
|
|
83
|
+
console.log(`- Mainnet TXID: ${tx.id}`);
|
|
84
|
+
|
|
85
|
+
return { success: true, txid: tx.id };
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log(`ā Broadcast failed: ${error.message}`);
|
|
89
|
+
return { success: false, error: error.message };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* š Create covenant from available UTXOs
|
|
95
|
+
*/
|
|
96
|
+
async createCovenantFromUtxos(minSatoshis = 10000) {
|
|
97
|
+
console.log('š Creating covenant from available UTXOs...\n');
|
|
98
|
+
|
|
99
|
+
const utxos = await this.fetchUtxosFromWhatsOnChain();
|
|
100
|
+
|
|
101
|
+
const suitableUtxo = utxos.find(utxo =>
|
|
102
|
+
utxo.satoshis >= minSatoshis &&
|
|
103
|
+
utxo.status === 'confirmed'
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!suitableUtxo) {
|
|
107
|
+
throw new Error(`No UTXO with at least ${minSatoshis} satoshis found`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`š¦ Selected UTXO: ${suitableUtxo.txid}`);
|
|
111
|
+
console.log(`- Satoshis: ${suitableUtxo.satoshis}`);
|
|
112
|
+
console.log(`- Height: ${suitableUtxo.height}\n`);
|
|
113
|
+
|
|
114
|
+
// Create covenant using utility class
|
|
115
|
+
const { transaction: creationTx, covenantUtxo } =
|
|
116
|
+
this.covenantUtils.createCovenantFromP2pkh(suitableUtxo);
|
|
117
|
+
|
|
118
|
+
// Broadcast covenant creation transaction
|
|
119
|
+
const broadcastResult = await this.broadcastTransaction(
|
|
120
|
+
creationTx,
|
|
121
|
+
'covenant creation'
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (broadcastResult.success) {
|
|
125
|
+
// Update covenant UTXO with broadcast info
|
|
126
|
+
covenantUtxo.status = 'broadcast';
|
|
127
|
+
covenantUtxo.broadcastTxid = broadcastResult.txid;
|
|
128
|
+
covenantUtxo.broadcastAt = new Date().toISOString();
|
|
129
|
+
|
|
130
|
+
// Save to local storage
|
|
131
|
+
this.covenantUtils.saveCovenantUtxo(covenantUtxo);
|
|
132
|
+
|
|
133
|
+
console.log('ā
Covenant UTXO created and stored with broadcast info\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { creationTx, covenantUtxo, broadcastResult };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* šÆ Spend covenant with custom output
|
|
141
|
+
*/
|
|
142
|
+
async spendCovenantUtxo(covenantTxid, outputAddress = null, outputSatoshis = null) {
|
|
143
|
+
console.log(`šÆ Spending covenant UTXO: ${covenantTxid}...\n`);
|
|
144
|
+
|
|
145
|
+
// Load covenant from storage
|
|
146
|
+
const covenantUtxo = this.covenantUtils.loadCovenantUtxo(covenantTxid);
|
|
147
|
+
|
|
148
|
+
console.log(`š¦ Loaded covenant UTXO:`);
|
|
149
|
+
console.log(`- Satoshis: ${covenantUtxo.satoshis}`);
|
|
150
|
+
console.log(`- Created: ${covenantUtxo.createdAt}`);
|
|
151
|
+
console.log(`- Status: ${covenantUtxo.status}\n`);
|
|
152
|
+
|
|
153
|
+
// Create spending transaction
|
|
154
|
+
const spendingTx = this.covenantUtils.createCovenantSpendingTx(
|
|
155
|
+
covenantUtxo,
|
|
156
|
+
outputAddress,
|
|
157
|
+
outputSatoshis
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Validate covenant spending
|
|
161
|
+
const validation = this.covenantUtils.validateCovenantTx(spendingTx, covenantUtxo);
|
|
162
|
+
|
|
163
|
+
if (!validation.valid) {
|
|
164
|
+
throw new Error(`Covenant validation failed: ${validation.error}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log('ā
Covenant spending transaction validated\n');
|
|
168
|
+
|
|
169
|
+
// Broadcast spending transaction
|
|
170
|
+
const broadcastResult = await this.broadcastTransaction(
|
|
171
|
+
spendingTx,
|
|
172
|
+
'covenant spending'
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return { spendingTx, validation, broadcastResult };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* š Get covenant portfolio status
|
|
180
|
+
*/
|
|
181
|
+
getCovenantPortfolio() {
|
|
182
|
+
const covenants = this.covenantUtils.listCovenantUtxos();
|
|
183
|
+
|
|
184
|
+
const portfolio = {
|
|
185
|
+
total: covenants.length,
|
|
186
|
+
totalValue: covenants.reduce((sum, c) => sum + c.satoshis, 0),
|
|
187
|
+
byStatus: {},
|
|
188
|
+
recent: covenants.slice(-5) // Last 5 created
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Group by status
|
|
192
|
+
covenants.forEach(covenant => {
|
|
193
|
+
const status = covenant.status || 'local';
|
|
194
|
+
if (!portfolio.byStatus[status]) {
|
|
195
|
+
portfolio.byStatus[status] = { count: 0, value: 0 };
|
|
196
|
+
}
|
|
197
|
+
portfolio.byStatus[status].count++;
|
|
198
|
+
portfolio.byStatus[status].value += covenant.satoshis;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
return portfolio;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Demo usage
|
|
206
|
+
async function demonstrateProductionFlow() {
|
|
207
|
+
const privateKey = bsv.PrivateKey.fromWIF('L5JREiiMfP5enqsRuhyNEv8SRjrjnMXNhkQEgNvzDRhGTaHG9ZFm');
|
|
208
|
+
const manager = new ProductionCovenantManager(privateKey);
|
|
209
|
+
|
|
210
|
+
console.log(`Wallet Address: ${manager.address}\n`);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
console.log('Scenario 1: Create New Covenant from P2PKH UTXO');
|
|
214
|
+
console.log('===============================================');
|
|
215
|
+
|
|
216
|
+
const creation = await manager.createCovenantFromUtxos(50000);
|
|
217
|
+
console.log(`Created covenant: ${creation.covenantUtxo.txid}\n`);
|
|
218
|
+
|
|
219
|
+
console.log('Scenario 2: Check Covenant Portfolio');
|
|
220
|
+
console.log('===================================');
|
|
221
|
+
|
|
222
|
+
const portfolio = manager.getCovenantPortfolio();
|
|
223
|
+
console.log('š Covenant Portfolio:');
|
|
224
|
+
console.log(`- Total Covenants: ${portfolio.total}`);
|
|
225
|
+
console.log(`- Total Value: ${portfolio.totalValue} satoshis`);
|
|
226
|
+
console.log('- By Status:');
|
|
227
|
+
|
|
228
|
+
Object.entries(portfolio.byStatus).forEach(([status, stats]) => {
|
|
229
|
+
console.log(` - ${status}: ${stats.count} covenants, ${stats.value} sats`);
|
|
230
|
+
});
|
|
231
|
+
console.log('');
|
|
232
|
+
|
|
233
|
+
console.log('Scenario 3: Spend Latest Covenant');
|
|
234
|
+
console.log('=================================');
|
|
235
|
+
|
|
236
|
+
if (portfolio.recent.length > 0) {
|
|
237
|
+
const latestCovenant = portfolio.recent[portfolio.recent.length - 1];
|
|
238
|
+
const spending = await manager.spendCovenantUtxo(latestCovenant.txid);
|
|
239
|
+
console.log(`Spent covenant: ${spending.spendingTx.id}\n`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log('š Production integration demonstration complete!');
|
|
243
|
+
console.log('\nš Ready for mainnet deployment with:');
|
|
244
|
+
console.log('ā
WhatsOnChain API integration');
|
|
245
|
+
console.log('ā
Automatic P2PKH script reconstruction');
|
|
246
|
+
console.log('ā
Local covenant UTXO storage');
|
|
247
|
+
console.log('ā
Broadcast transaction management');
|
|
248
|
+
console.log('ā
Portfolio tracking and management');
|
|
249
|
+
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log('ā Error:', error.message);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Run the demonstration
|
|
256
|
+
demonstrateProductionFlow();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"txid": "8e1ee45889ffe3795867f4aeaed210986ade118a1fd395f3a2236706dbee3423",
|
|
4
|
+
"vout": 0,
|
|
5
|
+
"satoshis": 916852,
|
|
6
|
+
"script": "76aa209888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351887521024fbc086ee073b74ecca97e7f39245b268cc3594140fe561f2d4876ebc3631d2aac",
|
|
7
|
+
"scriptPubKey": "76aa209888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351887521024fbc086ee073b74ecca97e7f39245b268cc3594140fe561f2d4876ebc3631d2aac",
|
|
8
|
+
"preimageHash": "9888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351",
|
|
9
|
+
"originalPreimage": "af93d3d37a5050d450f75b6085a820aa123cafeeafedc01e1f70f4d19a0acbdd",
|
|
10
|
+
"status": "local",
|
|
11
|
+
"createdAt": "2025-10-20T00:30:10.836Z",
|
|
12
|
+
"type": "preimage_covenant"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"txid": "8e1ee45889ffe3795867f4aeaed210986ade118a1fd395f3a2236706dbee3423",
|
|
16
|
+
"vout": 0,
|
|
17
|
+
"satoshis": 916852,
|
|
18
|
+
"script": "76aa209888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351887521024fbc086ee073b74ecca97e7f39245b268cc3594140fe561f2d4876ebc3631d2aac",
|
|
19
|
+
"scriptPubKey": "76aa209888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351887521024fbc086ee073b74ecca97e7f39245b268cc3594140fe561f2d4876ebc3631d2aac",
|
|
20
|
+
"preimageHash": "9888e1517445f2a81880d485cf008819d1cbd78c0a94163e8ac9ab2049606351",
|
|
21
|
+
"originalPreimage": "af93d3d37a5050d450f75b6085a820aa123cafeeafedc01e1f70f4d19a0acbdd",
|
|
22
|
+
"status": "broadcast",
|
|
23
|
+
"createdAt": "2025-10-20T00:30:33.822Z",
|
|
24
|
+
"type": "preimage_covenant",
|
|
25
|
+
"broadcastTxid": "8e1ee45889ffe3795867f4aeaed210986ade118a1fd395f3a2236706dbee3423",
|
|
26
|
+
"broadcastAt": "2025-10-20T00:30:33.823Z"
|
|
27
|
+
}
|
|
28
|
+
]
|