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.
- package/lib/consolidate-utxos.js +72 -26
- package/package.json +1 -1
package/lib/consolidate-utxos.js
CHANGED
|
@@ -72,29 +72,37 @@ class ConsolidateUtxos {
|
|
|
72
72
|
|
|
73
73
|
async analyzeUtxos (options = {}) {
|
|
74
74
|
try {
|
|
75
|
-
const
|
|
75
|
+
const allUtxos = this.utxos.getSpendableXecUtxos()
|
|
76
76
|
|
|
77
|
-
|
|
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 (${
|
|
81
|
-
totalUtxos:
|
|
82
|
-
totalValue: this._calculateTotalValue(
|
|
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 =
|
|
88
|
-
.filter(utxo => utxo
|
|
89
|
-
.sort((a, b) => a
|
|
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:
|
|
96
|
-
totalValue: this._calculateTotalValue(
|
|
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
|
|
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
|
|
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:
|
|
323
|
+
total: pureXecUtxos.length,
|
|
324
|
+
tokenUtxos: tokenUtxos.length,
|
|
325
|
+
tokenUtxosSkipped: tokenUtxos.length > 0
|
|
293
326
|
}
|
|
294
327
|
|
|
295
|
-
|
|
296
|
-
|
|
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 (
|
|
333
|
+
} else if (value < 10000) {
|
|
299
334
|
distribution.small++
|
|
300
|
-
} else if (
|
|
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
|
|
350
|
+
const allUtxos = this.utxos.getSpendableXecUtxos()
|
|
316
351
|
|
|
317
|
-
|
|
318
|
-
|
|
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(
|
|
322
|
-
const optimalUtxoCount = Math.max(1, Math.ceil(
|
|
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:
|
|
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