minimal-xec-wallet 1.0.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/minimal-xec-wallet.js +66268 -0
  4. package/dist/minimal-xec-wallet.min.js +55 -0
  5. package/examples/README.md +380 -0
  6. package/examples/advanced/browser-compatibility-test.js +263 -0
  7. package/examples/advanced/get-xec-price.js +149 -0
  8. package/examples/advanced/optimize-utxos.js +255 -0
  9. package/examples/advanced/send-op-return.js +216 -0
  10. package/examples/browser-test.html +350 -0
  11. package/examples/key-management/derive-addresses.js +191 -0
  12. package/examples/key-management/export-to-wif.js +114 -0
  13. package/examples/key-management/validate-address.js +214 -0
  14. package/examples/optimization/simple-consolidation-test.js +79 -0
  15. package/examples/optimization/test-utxo-consolidation.js +179 -0
  16. package/examples/test-examples.js +1204 -0
  17. package/examples/tokens/burn-tokens.js +293 -0
  18. package/examples/tokens/get-token-balance.js +169 -0
  19. package/examples/tokens/get-token-info.js +269 -0
  20. package/examples/tokens/list-all-tokens.js +162 -0
  21. package/examples/tokens/send-any-token.js +260 -0
  22. package/examples/tokens/test-main-wallet-integration.js +193 -0
  23. package/examples/transactions/send-all-xec.js +205 -0
  24. package/examples/transactions/send-to-multiple.js +217 -0
  25. package/examples/transactions/send-xec.js +191 -0
  26. package/examples/utils/show-qr.js +119 -0
  27. package/examples/utils/wallet-helper.js +176 -0
  28. package/examples/validation/comprehensive-infrastructure-test.js +210 -0
  29. package/examples/wallet-creation/create-new-wallet.js +67 -0
  30. package/examples/wallet-creation/import-from-wif.js +135 -0
  31. package/examples/wallet-creation/restore-from-mnemonic.js +100 -0
  32. package/examples/wallet-info/get-balance.js +99 -0
  33. package/examples/wallet-info/get-transactions.js +157 -0
  34. package/examples/wallet-info/get-utxos.js +145 -0
  35. package/examples/wallet.json +11 -0
  36. package/lib/adapters/robust-chronik-router.js +507 -0
  37. package/lib/adapters/router.js +651 -0
  38. package/lib/alp-token-handler.js +581 -0
  39. package/lib/browser-wasm-loader.js +271 -0
  40. package/lib/consolidate-utxos.js +338 -0
  41. package/lib/hybrid-token-manager.js +322 -0
  42. package/lib/key-derivation.js +466 -0
  43. package/lib/op-return.js +314 -0
  44. package/lib/security.js +270 -0
  45. package/lib/send-xec.js +396 -0
  46. package/lib/slp-token-handler.js +572 -0
  47. package/lib/token-protocol-detector.js +307 -0
  48. package/lib/utxos.js +303 -0
  49. package/package.json +125 -0
@@ -0,0 +1,396 @@
1
+ /*
2
+ Simplified XEC transaction creation for minimal wallet
3
+
4
+ Core functionality only:
5
+ - Create transactions with single/multiple outputs
6
+ - Simple fee calculation
7
+ - Basic UTXO selection (largest first)
8
+ - Transaction signing and broadcasting
9
+ */
10
+
11
+ const { TxBuilder, P2PKHSignatory, fromHex, toHex, Ecc, Script, ALL_BIP143 } = require('ecash-lib')
12
+ const { decodeCashAddress } = require('ecashaddrjs')
13
+ const KeyDerivation = require('./key-derivation')
14
+ const SecurityValidator = require('./security')
15
+
16
+ class SendXEC {
17
+ constructor (localConfig = {}) {
18
+ this.chronik = localConfig.chronik
19
+ this.ar = localConfig.ar
20
+
21
+ if (!this.chronik) {
22
+ throw new Error('Chronik client required for XEC transactions')
23
+ }
24
+
25
+ if (!this.ar) {
26
+ throw new Error('AdapterRouter required for XEC transactions')
27
+ }
28
+
29
+ // Initialize components
30
+ this.keyDerivation = new KeyDerivation()
31
+ this.security = new SecurityValidator(localConfig.security)
32
+
33
+ // Initialize ECC for ecash-lib
34
+ try {
35
+ this.ecc = new Ecc()
36
+ } catch (err) {
37
+ throw new Error(`Ecc initialization failed: ${err.message}`)
38
+ }
39
+
40
+ // Simple configuration - XEC dust limit is 546 satoshis (5.46 XEC)
41
+ this.dustLimit = localConfig.dustLimit || 546
42
+ this.maxRetries = localConfig.maxRetries || 3
43
+ this.defaultSatsPerByte = localConfig.defaultSatsPerByte || 1.2
44
+ }
45
+
46
+ /**
47
+ * Create transaction with single or multiple outputs
48
+ * @param {Array|Object} outputs - Output(s) to send to
49
+ * @param {Object} walletInfo - Wallet information
50
+ * @param {Array} utxos - Available UTXOs
51
+ * @returns {string} - Transaction hex
52
+ */
53
+ async createTransaction (outputs, walletInfo, utxos) {
54
+ try {
55
+ // Normalize outputs to array
56
+ const normalizedOutputs = Array.isArray(outputs) ? outputs : [outputs]
57
+
58
+ // Validate outputs
59
+ const outputValidation = this.security.validateOutputs(normalizedOutputs)
60
+ if (!outputValidation.isValid) {
61
+ throw new Error(`Invalid outputs: ${outputValidation.errors.join(', ')}`)
62
+ }
63
+
64
+ // Select UTXOs
65
+ const selection = this._selectUtxos(normalizedOutputs, utxos)
66
+
67
+ // Build transaction
68
+ const txHex = await this._buildTransaction(
69
+ selection.selectedUtxos,
70
+ normalizedOutputs,
71
+ selection.change,
72
+ walletInfo
73
+ )
74
+
75
+ return txHex
76
+ } catch (err) {
77
+ throw new Error(`Transaction creation failed: ${err.message}`)
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Create send-all transaction
83
+ * @param {string} address - Destination address
84
+ * @param {Object} walletInfo - Wallet information
85
+ * @param {Array} utxos - Available UTXOs
86
+ * @returns {string} - Transaction hex
87
+ */
88
+ async createSendAllTx (address, walletInfo, utxos) {
89
+ try {
90
+ // Validate address
91
+ if (!this.security.isValidAddress(address)) {
92
+ throw new Error(`Invalid destination address: ${address}`)
93
+ }
94
+
95
+ // Filter secure UTXOs (allow unconfirmed if needed)
96
+ let secureUtxos = this.security.filterSecureUtxos(utxos, { excludeDustAttack: false })
97
+
98
+ // If no confirmed UTXOs available, allow unconfirmed ones
99
+ if (secureUtxos.length === 0) {
100
+ console.warn('No confirmed UTXOs available, including unconfirmed UTXOs')
101
+ secureUtxos = this.security.filterSecureUtxos(utxos, { includeUnconfirmed: true, excludeDustAttack: false })
102
+ }
103
+
104
+ if (secureUtxos.length === 0) {
105
+ throw new Error('No spendable UTXOs available')
106
+ }
107
+
108
+ // Calculate total available
109
+ const totalSats = secureUtxos.reduce((sum, utxo) => sum + this._getUtxoValue(utxo), 0)
110
+
111
+ // Estimate fee
112
+ const estimatedFee = this._calculateFee(secureUtxos.length, 1) // All inputs, 1 output
113
+
114
+ const sendAmount = totalSats - estimatedFee
115
+ if (sendAmount <= this.dustLimit) {
116
+ throw new Error('Amount too small after fees')
117
+ }
118
+
119
+ // Create output
120
+ const outputs = [{
121
+ address,
122
+ amountSat: sendAmount
123
+ }]
124
+
125
+ // Build transaction (no change output needed)
126
+ const txHex = await this._buildTransaction(secureUtxos, outputs, 0, walletInfo)
127
+
128
+ return txHex
129
+ } catch (err) {
130
+ throw new Error(`Send-all transaction failed: ${err.message}`)
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Legacy method for backward compatibility
136
+ * @param {Array} outputs - Transaction outputs
137
+ * @param {Array} utxos - Available UTXOs
138
+ * @returns {Object} - UTXO selection result
139
+ */
140
+ getNecessaryUtxosAndChange (outputs, utxos) {
141
+ try {
142
+ const selection = this._selectUtxos(outputs, utxos)
143
+
144
+ return {
145
+ necessaryUtxos: selection.selectedUtxos,
146
+ totalAmount: selection.totalAmount,
147
+ change: selection.change,
148
+ estimatedFee: selection.estimatedFee
149
+ }
150
+ } catch (err) {
151
+ throw new Error(`UTXO selection failed: ${err.message}`)
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Public fee calculation method
157
+ * @param {number} inputCount - Number of inputs
158
+ * @param {number} outputCount - Number of outputs
159
+ * @param {number} feeRate - Fee rate in sats/byte
160
+ * @returns {number} - Fee in satoshis
161
+ */
162
+ calculateFee (inputCount, outputCount, feeRate = null) {
163
+ const rate = feeRate !== null ? feeRate : this.defaultSatsPerByte
164
+ return this._calculateFee(inputCount, outputCount, rate)
165
+ }
166
+
167
+ /**
168
+ * Sort UTXOs by size
169
+ * @param {Array} utxos - UTXOs to sort
170
+ * @param {string} order - 'asc' or 'desc'
171
+ * @returns {Array} - Sorted UTXOs
172
+ */
173
+ sortUtxosBySize (utxos, order = 'desc') {
174
+ return [...utxos].sort((a, b) => {
175
+ const aValue = this._getUtxoValue(a)
176
+ const bValue = this._getUtxoValue(b)
177
+ return order === 'desc' ? bValue - aValue : aValue - bValue
178
+ })
179
+ }
180
+
181
+ /**
182
+ * Legacy sendAllXec method
183
+ * @param {string} address - Destination address
184
+ * @param {Object} walletInfo - Wallet information
185
+ * @param {Array} utxos - Available UTXOs
186
+ * @returns {string} - Transaction ID
187
+ */
188
+ async sendAllXec (address, walletInfo, utxos) {
189
+ try {
190
+ const txHex = await this.createSendAllTx(address, walletInfo, utxos)
191
+
192
+ // Broadcast transaction
193
+ const txid = await this.ar.sendTx(txHex)
194
+ return txid
195
+ } catch (err) {
196
+ throw new Error(`Send all XEC failed: ${err.message}`)
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Get key pair from wallet info using ecash-lib
202
+ * @param {Object} walletInfo - Wallet information
203
+ * @returns {Object} - Key pair object with privateKey, publicKey, and address
204
+ */
205
+ async getKeyPairFromMnemonic (walletInfo) {
206
+ try {
207
+ // Use ecash-lib based key derivation
208
+ if (walletInfo.mnemonic) {
209
+ return this.keyDerivation.deriveFromMnemonic(walletInfo.mnemonic, walletInfo.hdPath)
210
+ } else if (walletInfo.privateKey) {
211
+ return this.keyDerivation.deriveFromWif(walletInfo.privateKey)
212
+ } else {
213
+ throw new Error('No private key or mnemonic provided')
214
+ }
215
+ } catch (err) {
216
+ throw new Error(`Key pair creation failed: ${err.message}`)
217
+ }
218
+ }
219
+
220
+ /**
221
+ * High-level sendXec method
222
+ * @param {Array|Object} outputs - Outputs to send
223
+ * @param {Object} walletInfo - Wallet information
224
+ * @param {Array} utxos - Available UTXOs
225
+ * @returns {string} - Transaction ID
226
+ */
227
+ async sendXec (outputs, walletInfo, utxos) {
228
+ try {
229
+ const txHex = await this.createTransaction(outputs, walletInfo, utxos)
230
+
231
+ // Broadcast transaction
232
+ const txid = await this.ar.sendTx(txHex)
233
+ return txid
234
+ } catch (err) {
235
+ throw new Error(`Send XEC failed: ${err.message}`)
236
+ }
237
+ }
238
+
239
+ // Private methods
240
+
241
+ /**
242
+ * Simple UTXO selection (largest first)
243
+ * @param {Array} outputs - Transaction outputs
244
+ * @param {Array} utxos - Available UTXOs
245
+ * @returns {Object} - Selection result
246
+ */
247
+ _selectUtxos (outputs, utxos) {
248
+ // Calculate target amount
249
+ const targetAmount = outputs.reduce((sum, output) => sum + (output.amountSat || output.amount), 0)
250
+
251
+ // Filter secure UTXOs (allow unconfirmed if needed)
252
+ let secureUtxos = this.security.filterSecureUtxos(utxos, { excludeDustAttack: false })
253
+
254
+ // If no confirmed UTXOs available, allow unconfirmed ones
255
+ if (secureUtxos.length === 0) {
256
+ console.warn('No confirmed UTXOs available, including unconfirmed UTXOs')
257
+ secureUtxos = this.security.filterSecureUtxos(utxos, { includeUnconfirmed: true, excludeDustAttack: false })
258
+ }
259
+
260
+ if (secureUtxos.length === 0) {
261
+ throw new Error('No spendable UTXOs available')
262
+ }
263
+
264
+ // Sort by value descending (largest first)
265
+ const sortedUtxos = secureUtxos.sort((a, b) => {
266
+ return this._getUtxoValue(b) - this._getUtxoValue(a)
267
+ })
268
+
269
+ // Greedy selection
270
+ const selectedUtxos = []
271
+ let totalAmount = 0
272
+
273
+ for (const utxo of sortedUtxos) {
274
+ const utxoValue = this._getUtxoValue(utxo)
275
+ selectedUtxos.push(utxo)
276
+ totalAmount += utxoValue
277
+
278
+ // Estimate fee with current selection
279
+ const estimatedFee = this._calculateFee(selectedUtxos.length, outputs.length)
280
+
281
+ if (totalAmount >= targetAmount + estimatedFee) {
282
+ // We have enough
283
+ const finalFee = this._calculateFee(selectedUtxos.length, outputs.length)
284
+ const change = totalAmount - targetAmount - finalFee
285
+
286
+ return {
287
+ selectedUtxos,
288
+ totalAmount,
289
+ estimatedFee: finalFee,
290
+ change
291
+ }
292
+ }
293
+ }
294
+
295
+ throw new Error('Insufficient funds')
296
+ }
297
+
298
+ /**
299
+ * Calculate transaction fee
300
+ * @param {number} inputCount - Number of inputs
301
+ * @param {number} outputCount - Number of outputs
302
+ * @param {number} feeRate - Fee rate in sats/byte
303
+ * @returns {number} - Fee in satoshis
304
+ */
305
+ _calculateFee (inputCount, outputCount, feeRate = null) {
306
+ const rate = feeRate !== null ? feeRate : this.defaultSatsPerByte
307
+
308
+ // Handle zero fee rate explicitly
309
+ if (rate === 0) {
310
+ return 0
311
+ }
312
+
313
+ const inputSize = 148 // P2PKH input size
314
+ const outputSize = 34 // P2PKH output size
315
+ const overhead = 10 // Version, locktime, etc.
316
+
317
+ const txSize = (inputCount * inputSize) + (outputCount * outputSize) + overhead
318
+ return Math.ceil(txSize * rate)
319
+ }
320
+
321
+ /**
322
+ * Build and sign transaction using ecash-lib properly
323
+ * @param {Array} selectedUtxos - UTXOs to spend
324
+ * @param {Array} outputs - Transaction outputs
325
+ * @param {number} changeAmount - Change amount
326
+ * @param {Object} walletInfo - Wallet information
327
+ * @returns {string} - Transaction hex
328
+ */
329
+ async _buildTransaction (selectedUtxos, outputs, changeAmount, walletInfo) {
330
+ try {
331
+ // Get private key (prefer mnemonic)
332
+ let privateKeyHex
333
+ if (walletInfo.mnemonic) {
334
+ const keyData = this.keyDerivation.deriveFromMnemonic(walletInfo.mnemonic, walletInfo.hdPath)
335
+ privateKeyHex = keyData.privateKey
336
+ } else {
337
+ privateKeyHex = walletInfo.privateKey
338
+ }
339
+
340
+ const sk = fromHex(privateKeyHex)
341
+ const pk = this.ecc.derivePubkey(sk)
342
+
343
+ // Build outputs
344
+ const txOutputs = []
345
+
346
+ // Add main outputs
347
+ for (const output of outputs) {
348
+ const decoded = decodeCashAddress(output.address)
349
+ txOutputs.push({
350
+ sats: BigInt(output.amountSat || output.amount),
351
+ script: Script.p2pkh(fromHex(decoded.hash))
352
+ })
353
+ }
354
+
355
+ // Add change address for automatic calculation
356
+ const walletDecoded = decodeCashAddress(walletInfo.xecAddress)
357
+ txOutputs.push(Script.p2pkh(fromHex(walletDecoded.hash)))
358
+
359
+ const inputs = selectedUtxos.map(utxo => ({
360
+ input: {
361
+ prevOut: {
362
+ txid: utxo.outpoint.txid,
363
+ outIdx: utxo.outpoint.outIdx
364
+ },
365
+ signData: {
366
+ sats: BigInt(this._getUtxoValue(utxo)),
367
+ outputScript: Script.p2pkh(fromHex(walletDecoded.hash))
368
+ }
369
+ },
370
+ signatory: P2PKHSignatory(sk, pk, ALL_BIP143)
371
+ }))
372
+
373
+ const txBuilder = new TxBuilder({ inputs, outputs: txOutputs })
374
+ const tx = txBuilder.sign({
375
+ feePerKb: BigInt(1200),
376
+ dustSats: BigInt(546)
377
+ })
378
+
379
+ return toHex(tx.ser())
380
+ } catch (err) {
381
+ throw new Error(`Transaction failed: ${err.message}`)
382
+ }
383
+ }
384
+
385
+ _getUtxoValue (utxo) {
386
+ if (utxo.sats !== undefined) {
387
+ return typeof utxo.sats === 'bigint' ? Number(utxo.sats) : parseInt(utxo.sats)
388
+ }
389
+ if (utxo.value !== undefined) {
390
+ return typeof utxo.value === 'bigint' ? Number(utxo.value) : parseInt(utxo.value)
391
+ }
392
+ return 0
393
+ }
394
+ }
395
+
396
+ module.exports = SendXEC