minimal-xec-wallet 1.0.2 → 1.0.4

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.
@@ -72,29 +72,37 @@ class ConsolidateUtxos {
72
72
 
73
73
  async analyzeUtxos (options = {}) {
74
74
  try {
75
- const spendableUtxos = this.utxos.getSpendableXecUtxos()
75
+ const allUtxos = this.utxos.getSpendableXecUtxos()
76
76
 
77
- if (spendableUtxos.length < this.minUtxosForConsolidation) {
77
+ // CRITICAL: Filter out token UTXOs to prevent token burning
78
+ const pureXecUtxos = allUtxos.filter(utxo => !utxo.token)
79
+ const tokenUtxos = allUtxos.filter(utxo => utxo.token)
80
+
81
+ if (pureXecUtxos.length < this.minUtxosForConsolidation) {
78
82
  return {
79
83
  shouldConsolidate: false,
80
- reason: `Not enough UTXOs for consolidation (${spendableUtxos.length} < ${this.minUtxosForConsolidation})`,
81
- totalUtxos: spendableUtxos.length,
82
- totalValue: this._calculateTotalValue(spendableUtxos)
84
+ reason: `Not enough pure XEC UTXOs for consolidation (${pureXecUtxos.length} < ${this.minUtxosForConsolidation})`,
85
+ totalUtxos: pureXecUtxos.length,
86
+ totalValue: this._calculateTotalValue(pureXecUtxos),
87
+ tokenUtxos: tokenUtxos.length,
88
+ tokenUtxosSkipped: tokenUtxos.length > 0
83
89
  }
84
90
  }
85
91
 
86
92
  // Filter UTXOs that should be consolidated (smaller ones first)
87
- const utxosToConsolidate = spendableUtxos
88
- .filter(utxo => utxo.value <= options.consolidationThreshold)
89
- .sort((a, b) => a.value - b.value) // Sort by value ascending
93
+ const utxosToConsolidate = pureXecUtxos
94
+ .filter(utxo => this._getUtxoValue(utxo) <= options.consolidationThreshold)
95
+ .sort((a, b) => this._getUtxoValue(a) - this._getUtxoValue(b)) // Sort by value ascending
90
96
 
91
97
  if (utxosToConsolidate.length < this.minUtxosForConsolidation) {
92
98
  return {
93
99
  shouldConsolidate: false,
94
- reason: `Not enough small UTXOs to consolidate (${utxosToConsolidate.length} below ${options.consolidationThreshold} satoshis)`,
95
- totalUtxos: spendableUtxos.length,
96
- totalValue: this._calculateTotalValue(spendableUtxos),
97
- smallUtxos: utxosToConsolidate.length
100
+ reason: `Not enough small pure XEC UTXOs to consolidate (${utxosToConsolidate.length} below ${options.consolidationThreshold} satoshis)`,
101
+ totalUtxos: pureXecUtxos.length,
102
+ totalValue: this._calculateTotalValue(pureXecUtxos),
103
+ smallUtxos: utxosToConsolidate.length,
104
+ tokenUtxos: tokenUtxos.length,
105
+ tokenUtxosSkipped: tokenUtxos.length > 0
98
106
  }
99
107
  }
100
108
 
@@ -117,6 +125,8 @@ class ConsolidateUtxos {
117
125
  totalValue: this._calculateTotalValue(utxosToConsolidate),
118
126
  consolidationFee,
119
127
  potentialSavings: totalSavings,
128
+ tokenUtxos: tokenUtxos.length,
129
+ tokenUtxosSkipped: tokenUtxos.length > 0,
120
130
  consolidationPlans
121
131
  }
122
132
  } catch (err) {
@@ -167,6 +177,12 @@ class ConsolidateUtxos {
167
177
 
168
178
  for (const plan of consolidationPlans) {
169
179
  try {
180
+ // CRITICAL SAFETY CHECK: Ensure no token UTXOs are being consolidated
181
+ const tokenUtxosInPlan = plan.inputUtxos.filter(utxo => utxo.token)
182
+ if (tokenUtxosInPlan.length > 0) {
183
+ throw new Error(`SAFETY ABORT: Plan contains ${tokenUtxosInPlan.length} token UTXOs. Consolidation would burn tokens!`)
184
+ }
185
+
170
186
  // Create consolidation transaction - send all value to same address
171
187
  const outputs = [{
172
188
  address: this.wallet.walletInfo.xecAddress,
@@ -251,7 +267,17 @@ class ConsolidateUtxos {
251
267
  // Helper methods
252
268
 
253
269
  _calculateTotalValue (utxos) {
254
- return utxos.reduce((total, utxo) => total + utxo.value, 0)
270
+ return utxos.reduce((total, utxo) => total + this._getUtxoValue(utxo), 0)
271
+ }
272
+
273
+ _getUtxoValue (utxo) {
274
+ if (utxo.sats !== undefined) {
275
+ return typeof utxo.sats === 'bigint' ? Number(utxo.sats) : parseInt(utxo.sats)
276
+ }
277
+ if (utxo.value !== undefined) {
278
+ return typeof utxo.value === 'bigint' ? Number(utxo.value) : parseInt(utxo.value)
279
+ }
280
+ return 0
255
281
  }
256
282
 
257
283
  _calculateConsolidationFee (numInputs, numOutputs, satsPerByte) {
@@ -283,21 +309,30 @@ class ConsolidateUtxos {
283
309
 
284
310
  getUtxoDistribution () {
285
311
  try {
286
- const utxos = this.utxos.getSpendableXecUtxos()
312
+ const allUtxos = this.utxos.getSpendableXecUtxos()
313
+
314
+ // Separate pure XEC from token UTXOs
315
+ const pureXecUtxos = allUtxos.filter(utxo => !utxo.token)
316
+ const tokenUtxos = allUtxos.filter(utxo => utxo.token)
317
+
287
318
  const distribution = {
288
319
  dust: 0, // < 1000 sats
289
320
  small: 0, // 1000 - 10000 sats
290
321
  medium: 0, // 10000 - 100000 sats
291
322
  large: 0, // > 100000 sats
292
- total: utxos.length
323
+ total: pureXecUtxos.length,
324
+ tokenUtxos: tokenUtxos.length,
325
+ tokenUtxosSkipped: tokenUtxos.length > 0
293
326
  }
294
327
 
295
- for (const utxo of utxos) {
296
- if (utxo.value < 1000) {
328
+ // Only analyze pure XEC UTXOs for consolidation
329
+ for (const utxo of pureXecUtxos) {
330
+ const value = this._getUtxoValue(utxo)
331
+ if (value < 1000) {
297
332
  distribution.dust++
298
- } else if (utxo.value < 10000) {
333
+ } else if (value < 10000) {
299
334
  distribution.small++
300
- } else if (utxo.value < 100000) {
335
+ } else if (value < 100000) {
301
336
  distribution.medium++
302
337
  } else {
303
338
  distribution.large++
@@ -312,22 +347,33 @@ class ConsolidateUtxos {
312
347
 
313
348
  estimateOptimizationSavings () {
314
349
  try {
315
- const utxos = this.utxos.getSpendableXecUtxos()
350
+ const allUtxos = this.utxos.getSpendableXecUtxos()
316
351
 
317
- if (utxos.length < 2) {
318
- return { savings: 0, reason: 'No optimization needed' }
352
+ // Only analyze pure XEC UTXOs (tokens are preserved separately)
353
+ const pureXecUtxos = allUtxos.filter(utxo => !utxo.token)
354
+ const tokenUtxos = allUtxos.filter(utxo => utxo.token)
355
+
356
+ if (pureXecUtxos.length < 2) {
357
+ return {
358
+ savings: 0,
359
+ reason: 'No optimization needed for pure XEC UTXOs',
360
+ currentUtxos: pureXecUtxos.length,
361
+ tokenUtxos: tokenUtxos.length
362
+ }
319
363
  }
320
364
 
321
- const currentFee = this._estimateCurrentSpendingFee(utxos, this.defaultSatsPerByte)
322
- const optimalUtxoCount = Math.max(1, Math.ceil(utxos.length / 50)) // Optimal: ~50 UTXOs max
365
+ const currentFee = this._estimateCurrentSpendingFee(pureXecUtxos, this.defaultSatsPerByte)
366
+ const optimalUtxoCount = Math.max(1, Math.ceil(pureXecUtxos.length / 50)) // Optimal: ~50 UTXOs max
323
367
  const optimizedFee = this._estimateFutureSpendingFee(optimalUtxoCount, this.defaultSatsPerByte)
324
368
 
325
369
  return {
326
370
  savings: currentFee - optimizedFee,
327
- currentUtxos: utxos.length,
371
+ currentUtxos: pureXecUtxos.length,
328
372
  optimalUtxos: optimalUtxoCount,
329
373
  currentEstimatedFee: currentFee,
330
- optimizedEstimatedFee: optimizedFee
374
+ optimizedEstimatedFee: optimizedFee,
375
+ tokenUtxos: tokenUtxos.length,
376
+ tokenUtxosPreserved: tokenUtxos.length
331
377
  }
332
378
  } catch (err) {
333
379
  throw new Error(`Optimization savings estimation failed: ${err.message}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minimal-xec-wallet",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A minimalist eCash (XEC) wallet npm library, for use in web apps. Supports eTokens.",
5
5
  "main": "./index.js",
6
6
  "module": "./dist/minimal-xec-wallet.min.js",