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,307 @@
1
+ /*
2
+ Token Protocol Detection for hybrid SLP + ALP support.
3
+ Categorizes UTXOs by protocol and provides detection utilities.
4
+ */
5
+
6
+ class TokenProtocolDetector {
7
+ /**
8
+ * Detect the protocol of a token from a UTXO
9
+ * @param {Object} utxo - UTXO object from chronik
10
+ * @returns {string} - 'XEC', 'SLP', or 'ALP'
11
+ */
12
+ static detectProtocol (utxo) {
13
+ if (!utxo || !utxo.token) {
14
+ return 'XEC'
15
+ }
16
+
17
+ const protocol = utxo.token.tokenType?.protocol
18
+ switch (protocol) {
19
+ case 'SLP':
20
+ return 'SLP'
21
+ case 'ALP':
22
+ return 'ALP'
23
+ default:
24
+ throw new Error(`Unknown token protocol: ${protocol}`)
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Detect token protocol from token metadata
30
+ * @param {Object} tokenInfo - Token info from chronik.token()
31
+ * @returns {string} - 'SLP' or 'ALP'
32
+ */
33
+ static detectProtocolFromMetadata (tokenInfo) {
34
+ if (!tokenInfo || !tokenInfo.tokenType) {
35
+ throw new Error('Invalid token metadata')
36
+ }
37
+
38
+ const protocol = tokenInfo.tokenType.protocol
39
+ switch (protocol) {
40
+ case 'SLP':
41
+ return 'SLP'
42
+ case 'ALP':
43
+ return 'ALP'
44
+ default:
45
+ throw new Error(`Unknown token protocol: ${protocol}`)
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Categorize UTXOs by protocol type
51
+ * @param {Array} utxos - Array of UTXO objects
52
+ * @returns {Object} - Categorized UTXOs by protocol
53
+ */
54
+ static categorizeUtxos (utxos) {
55
+ const result = {
56
+ xecUtxos: [],
57
+ slpUtxos: [],
58
+ alpUtxos: [],
59
+ tokenUtxosByProtocol: new Map(), // protocol -> UTXOs[]
60
+ tokenUtxosById: new Map(), // tokenId -> UTXOs[]
61
+ protocolSummary: {
62
+ xec: { count: 0, totalSats: 0 },
63
+ slp: { count: 0, tokenTypes: new Set() },
64
+ alp: { count: 0, tokenTypes: new Set() }
65
+ }
66
+ }
67
+
68
+ for (const utxo of utxos) {
69
+ if (!utxo) continue
70
+ try {
71
+ const protocol = this.detectProtocol(utxo)
72
+
73
+ switch (protocol) {
74
+ case 'XEC':
75
+ result.xecUtxos.push(utxo)
76
+ this._addToProtocolMap(result.tokenUtxosByProtocol, 'XEC', utxo)
77
+ result.protocolSummary.xec.count++
78
+ result.protocolSummary.xec.totalSats += this._extractSats(utxo)
79
+ break
80
+
81
+ case 'SLP':
82
+ result.slpUtxos.push(utxo)
83
+ this._addToProtocolMap(result.tokenUtxosByProtocol, 'SLP', utxo)
84
+ this._addToTokenMap(result.tokenUtxosById, utxo.token.tokenId, utxo)
85
+ result.protocolSummary.slp.count++
86
+ result.protocolSummary.slp.tokenTypes.add(utxo.token.tokenId)
87
+ break
88
+
89
+ case 'ALP':
90
+ result.alpUtxos.push(utxo)
91
+ this._addToProtocolMap(result.tokenUtxosByProtocol, 'ALP', utxo)
92
+ this._addToTokenMap(result.tokenUtxosById, utxo.token.tokenId, utxo)
93
+ result.protocolSummary.alp.count++
94
+ result.protocolSummary.alp.tokenTypes.add(utxo.token.tokenId)
95
+ break
96
+ }
97
+ } catch (err) {
98
+ console.warn(`Skipping invalid UTXO: ${err.message}`, utxo)
99
+ }
100
+ }
101
+
102
+ return result
103
+ }
104
+
105
+ /**
106
+ * Filter UTXOs for a specific token ID
107
+ * @param {Array} utxos - Array of UTXO objects
108
+ * @param {string} tokenId - Token ID to filter for
109
+ * @returns {Object} - XEC and token UTXOs for the specific token
110
+ */
111
+ static filterUtxosForToken (utxos, tokenId) {
112
+ const categorized = this.categorizeUtxos(utxos)
113
+ const tokenUtxos = categorized.tokenUtxosById.get(tokenId) || []
114
+
115
+ // Get other UTXOs (not matching the requested tokenId) - includes other tokens AND XEC UTXOs
116
+ const otherUtxos = [...categorized.xecUtxos]
117
+ for (const [id, utxoList] of categorized.tokenUtxosById.entries()) {
118
+ if (id !== tokenId) {
119
+ otherUtxos.push(...utxoList)
120
+ }
121
+ }
122
+
123
+ if (tokenUtxos.length === 0) {
124
+ return {
125
+ xecUtxos: categorized.xecUtxos,
126
+ tokenUtxos: [],
127
+ otherUtxos,
128
+ protocol: null,
129
+ tokenSummary: { totalAtoms: 0n, utxoCount: 0 }
130
+ }
131
+ }
132
+
133
+ // Detect protocol from first token UTXO
134
+ const protocol = this.detectProtocol(tokenUtxos[0])
135
+
136
+ // Calculate token summary
137
+ const tokenSummary = this._calculateTokenSummary(tokenUtxos, protocol)
138
+
139
+ return {
140
+ xecUtxos: categorized.xecUtxos,
141
+ tokenUtxos,
142
+ otherUtxos,
143
+ protocol,
144
+ tokenSummary
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get all unique token IDs in wallet with their protocols
150
+ * @param {Array} utxos - Array of UTXO objects
151
+ * @returns {Array} - Array of {tokenId, protocol} objects
152
+ */
153
+ static getTokenInventory (utxos) {
154
+ const tokenMap = new Map()
155
+
156
+ for (const utxo of utxos) {
157
+ if (utxo && utxo.token && utxo.token.tokenId) {
158
+ const tokenId = utxo.token.tokenId
159
+ if (!tokenMap.has(tokenId)) {
160
+ try {
161
+ const protocol = this.detectProtocol(utxo)
162
+ tokenMap.set(tokenId, {
163
+ tokenId,
164
+ protocol,
165
+ utxoCount: 1,
166
+ totalAtoms: BigInt(utxo.token.atoms || 0),
167
+ firstSeen: utxo.blockHeight || -1
168
+ })
169
+ } catch (err) {
170
+ console.warn(`Invalid token UTXO: ${err.message}`)
171
+ }
172
+ } else {
173
+ const entry = tokenMap.get(tokenId)
174
+ entry.utxoCount++
175
+ entry.totalAtoms += BigInt(utxo.token.atoms || 0)
176
+ }
177
+ }
178
+ }
179
+
180
+ return Array.from(tokenMap.values())
181
+ }
182
+
183
+ /**
184
+ * Validate token protocol compatibility
185
+ * @param {string} expectedProtocol - Expected protocol (SLP or ALP)
186
+ * @param {Object} utxo - UTXO to validate
187
+ * @returns {boolean} - True if compatible
188
+ */
189
+ static validateProtocolCompatibility (expectedProtocol, utxo) {
190
+ try {
191
+ const actualProtocol = this.detectProtocol(utxo)
192
+ return actualProtocol === expectedProtocol
193
+ } catch (err) {
194
+ return false
195
+ }
196
+ }
197
+
198
+ // Private helper methods
199
+
200
+ static _addToProtocolMap (protocolMap, protocol, utxo) {
201
+ if (!protocolMap.has(protocol)) {
202
+ protocolMap.set(protocol, [])
203
+ }
204
+ protocolMap.get(protocol).push(utxo)
205
+ }
206
+
207
+ static _addToTokenMap (tokenMap, tokenId, utxo) {
208
+ if (!tokenMap.has(tokenId)) {
209
+ tokenMap.set(tokenId, [])
210
+ }
211
+ tokenMap.get(tokenId).push(utxo)
212
+ }
213
+
214
+ static _extractSats (utxo) {
215
+ if (!utxo || typeof utxo !== 'object') {
216
+ return 0
217
+ }
218
+
219
+ if (utxo.sats !== undefined) {
220
+ if (typeof utxo.sats === 'bigint') {
221
+ return Number(utxo.sats)
222
+ }
223
+ if (typeof utxo.sats === 'number') {
224
+ return utxo.sats
225
+ }
226
+ const parsed = parseInt(utxo.sats)
227
+ return isNaN(parsed) ? 0 : parsed
228
+ }
229
+
230
+ return 0
231
+ }
232
+
233
+ static _calculateTokenSummary (tokenUtxos, protocol) {
234
+ let totalAtoms = 0n
235
+ const utxoCount = tokenUtxos.length
236
+
237
+ for (const utxo of tokenUtxos) {
238
+ if (utxo.token && utxo.token.atoms) {
239
+ totalAtoms += BigInt(utxo.token.atoms)
240
+ }
241
+ }
242
+
243
+ return {
244
+ totalAtoms,
245
+ utxoCount,
246
+ protocol
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Check if wallet contains any tokens
252
+ * @param {Array} utxos - Array of UTXO objects
253
+ * @returns {boolean} - True if any tokens found
254
+ */
255
+ static hasTokens (utxos) {
256
+ return utxos.some(utxo => utxo && utxo.token && utxo.token.tokenId)
257
+ }
258
+
259
+ /**
260
+ * Check if wallet contains specific protocol tokens
261
+ * @param {Array} utxos - Array of UTXO objects
262
+ * @param {string} protocol - Protocol to check for ('SLP' or 'ALP')
263
+ * @returns {boolean} - True if protocol tokens found
264
+ */
265
+ static hasProtocolTokens (utxos, protocol) {
266
+ return utxos.some(utxo => {
267
+ if (!utxo) return false
268
+ try {
269
+ return this.detectProtocol(utxo) === protocol
270
+ } catch (err) {
271
+ return false
272
+ }
273
+ })
274
+ }
275
+
276
+ /**
277
+ * Get protocol statistics for wallet
278
+ * @param {Array} utxos - Array of UTXO objects
279
+ * @returns {Object} - Protocol statistics
280
+ */
281
+ static getProtocolStats (utxos) {
282
+ const categorized = this.categorizeUtxos(utxos)
283
+
284
+ // Build protocols array from active protocols
285
+ const protocols = []
286
+ if (categorized.protocolSummary.slp.count > 0) protocols.push('SLP')
287
+ if (categorized.protocolSummary.alp.count > 0) protocols.push('ALP')
288
+
289
+ // Calculate total XEC sats from ALL UTXOs (including dust from token UTXOs)
290
+ const totalXecSats = utxos.reduce((sum, utxo) => sum + this._extractSats(utxo), 0)
291
+
292
+ return {
293
+ totalUtxos: utxos.length,
294
+ xecUtxos: categorized.protocolSummary.xec.count,
295
+ slpTokens: categorized.protocolSummary.slp.tokenTypes.size,
296
+ slpUtxos: categorized.protocolSummary.slp.count,
297
+ alpTokens: categorized.protocolSummary.alp.tokenTypes.size,
298
+ alpUtxos: categorized.protocolSummary.alp.count,
299
+ totalXecSats,
300
+ hasTokens: this.hasTokens(utxos),
301
+ protocols,
302
+ hasMultipleProtocols: protocols.length > 1
303
+ }
304
+ }
305
+ }
306
+
307
+ module.exports = TokenProtocolDetector
package/lib/utxos.js ADDED
@@ -0,0 +1,303 @@
1
+ /*
2
+ Simplified UTXO management for minimal XEC wallet
3
+
4
+ Core functionality only:
5
+ - Fetch UTXOs from chronik
6
+ - Basic validation and filtering
7
+ - Simple caching
8
+ - Essential security checks
9
+ */
10
+
11
+ const SecurityValidator = require('./security')
12
+
13
+ class Utxos {
14
+ constructor (localConfig = {}) {
15
+ this.chronik = localConfig.chronik
16
+ this.ar = localConfig.ar
17
+
18
+ if (!this.ar) {
19
+ throw new Error('AdapterRouter instance required for UTXO management')
20
+ }
21
+
22
+ // Simple UTXO store
23
+ this.utxoStore = {
24
+ xecUtxos: [],
25
+ lastUpdated: null,
26
+ cacheKey: null
27
+ }
28
+
29
+ // Security validator
30
+ this.security = new SecurityValidator(localConfig.security)
31
+
32
+ // Simple configuration
33
+ this.maxRetries = localConfig.maxRetries || 3
34
+ this.retryDelay = localConfig.retryDelay || 1000
35
+ this.cacheTimeout = localConfig.cacheTimeout || 30000 // 30 seconds
36
+
37
+ // Performance tracking (basic)
38
+ this.performanceMetrics = {
39
+ totalRequests: 0,
40
+ cacheHits: 0,
41
+ lastRefreshTime: null,
42
+ totalResponseTime: 0,
43
+ averageResponseTime: 0
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Initialize UTXO store for an address
49
+ * @param {string} addr - XEC address
50
+ * @param {boolean} forceRefresh - Force refresh cache
51
+ * @returns {boolean} - Success status
52
+ */
53
+ async initUtxoStore (addr, forceRefresh = false) {
54
+ try {
55
+ this.performanceMetrics.totalRequests++
56
+
57
+ // Check cache validity
58
+ if (!forceRefresh && this._isCacheValid(addr)) {
59
+ this.performanceMetrics.cacheHits++
60
+ return true
61
+ }
62
+
63
+ // Fetch fresh UTXO data
64
+ const utxosResult = await this._fetchUtxosWithRetry(addr)
65
+
66
+ // Process and store UTXOs
67
+ this._processUtxos(utxosResult, addr)
68
+
69
+ return true
70
+ } catch (err) {
71
+ throw new Error(`UTXO initialization failed: ${err.message}`)
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Get spendable XEC UTXOs with basic filtering
77
+ * @param {Object} options - Filtering options
78
+ * @returns {Array} - Filtered UTXOs
79
+ */
80
+ getSpendableXecUtxos (options = {}) {
81
+ const {
82
+ includeUnconfirmed = false,
83
+ excludeDustAttack = true
84
+ } = options
85
+
86
+ // Use security validator for filtering
87
+ return this.security.filterSecureUtxos(this.utxoStore.xecUtxos, {
88
+ includeUnconfirmed,
89
+ excludeDustAttack
90
+ })
91
+ }
92
+
93
+ /**
94
+ * Simple UTXO selection (largest first)
95
+ * @param {number} targetAmount - Target amount in satoshis
96
+ * @param {Object} options - Selection options
97
+ * @returns {Object} - Selection result
98
+ */
99
+ selectOptimalUtxos (targetAmount, options = {}) {
100
+ const spendableUtxos = this.getSpendableXecUtxos(options)
101
+
102
+ if (spendableUtxos.length === 0) {
103
+ throw new Error('No spendable UTXOs available')
104
+ }
105
+
106
+ // Sort by value descending (largest first)
107
+ const sortedUtxos = spendableUtxos.sort((a, b) => {
108
+ const aValue = this._getUtxoValue(a)
109
+ const bValue = this._getUtxoValue(b)
110
+ return bValue - aValue
111
+ })
112
+
113
+ // Simple greedy selection
114
+ const selectedUtxos = []
115
+ let totalAmount = 0
116
+ const inputCost = 148 // P2PKH input size in bytes
117
+
118
+ for (const utxo of sortedUtxos) {
119
+ const utxoValue = this._getUtxoValue(utxo)
120
+ selectedUtxos.push(utxo)
121
+ totalAmount += utxoValue
122
+
123
+ // Estimate fee
124
+ const estimatedFee = selectedUtxos.length * inputCost + 34 + 10 // inputs + output + overhead
125
+
126
+ if (totalAmount >= targetAmount + estimatedFee) {
127
+ break
128
+ }
129
+ }
130
+
131
+ // Check if we have enough
132
+ const finalFee = selectedUtxos.length * inputCost + 34 + 10
133
+ if (totalAmount < targetAmount + finalFee) {
134
+ throw new Error('Insufficient funds')
135
+ }
136
+
137
+ return {
138
+ selectedUtxos,
139
+ totalAmount,
140
+ estimatedFee: finalFee,
141
+ change: totalAmount - targetAmount - finalFee
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Get current balance
147
+ * @returns {Object} - Balance information
148
+ */
149
+ getBalance () {
150
+ const utxos = this.utxoStore.xecUtxos
151
+ let confirmed = 0
152
+ let unconfirmed = 0
153
+
154
+ utxos.forEach(utxo => {
155
+ const value = this._getUtxoValue(utxo)
156
+ if (utxo.blockHeight === -1) {
157
+ unconfirmed += value
158
+ } else {
159
+ confirmed += value
160
+ }
161
+ })
162
+
163
+ return {
164
+ confirmed,
165
+ unconfirmed,
166
+ total: confirmed + unconfirmed
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get performance metrics
172
+ * @returns {Object} - Performance data
173
+ */
174
+ getPerformanceMetrics () {
175
+ const cacheHitRate = this.performanceMetrics.totalRequests > 0
176
+ ? (this.performanceMetrics.cacheHits / this.performanceMetrics.totalRequests) * 100
177
+ : 0
178
+
179
+ return {
180
+ cacheHitRate: Math.round(cacheHitRate * 100) / 100,
181
+ totalRequests: this.performanceMetrics.totalRequests,
182
+ cacheHits: this.performanceMetrics.cacheHits,
183
+ averageResponseTime: this.performanceMetrics.averageResponseTime,
184
+ lastRefreshTime: this.performanceMetrics.lastRefreshTime,
185
+ utxoCount: this.utxoStore.xecUtxos.length
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Clear cache
191
+ */
192
+ clearCache () {
193
+ this.utxoStore = {
194
+ xecUtxos: [],
195
+ lastUpdated: null,
196
+ cacheKey: null
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Get spendable eToken UTXOs (Phase 2 - not implemented)
202
+ * @returns {Array} - Empty array for Phase 1
203
+ */
204
+ getSpendableETokenUtxos () {
205
+ return []
206
+ }
207
+
208
+ /**
209
+ * Refresh cache for address
210
+ * @param {string} addr - Address to refresh
211
+ * @returns {boolean} - Success status
212
+ */
213
+ async refreshCache (addr) {
214
+ return await this.initUtxoStore(addr, true)
215
+ }
216
+
217
+ /**
218
+ * Filter dust UTXOs (legacy method)
219
+ * @param {Array} utxos - UTXOs to filter
220
+ * @returns {Array} - Non-dust UTXOs
221
+ */
222
+ _filterDustUtxos (utxos) {
223
+ return utxos.filter(utxo => this._getUtxoValue(utxo) >= 1000)
224
+ }
225
+
226
+ /**
227
+ * Sort UTXOs by value (legacy method)
228
+ * @param {Array} utxos - UTXOs to sort
229
+ * @param {string} order - 'asc' or 'desc'
230
+ * @returns {Array} - Sorted UTXOs
231
+ */
232
+ _sortUtxosByValue (utxos, order = 'desc') {
233
+ return [...utxos].sort((a, b) => {
234
+ const aValue = this._getUtxoValue(a)
235
+ const bValue = this._getUtxoValue(b)
236
+ return order === 'desc' ? bValue - aValue : aValue - bValue
237
+ })
238
+ }
239
+
240
+ // Private methods
241
+
242
+ async _fetchUtxosWithRetry (addr, maxRetries = null) {
243
+ const retryLimit = maxRetries || this.maxRetries
244
+ let attempt = 1
245
+
246
+ while (attempt <= retryLimit) {
247
+ try {
248
+ const utxosResult = await this.ar.getUtxos(addr)
249
+ return utxosResult
250
+ } catch (err) {
251
+ if (attempt === retryLimit) {
252
+ throw new Error(`Failed to fetch UTXOs after ${retryLimit} attempts: ${err.message}`)
253
+ }
254
+
255
+ await this._delay(this.retryDelay * attempt)
256
+ attempt++
257
+ }
258
+ }
259
+ }
260
+
261
+ _processUtxos (utxosResult, addr) {
262
+ if (!utxosResult || !Array.isArray(utxosResult.utxos)) {
263
+ throw new Error('Invalid UTXO response format')
264
+ }
265
+
266
+ // Filter and validate UTXOs
267
+ const validUtxos = utxosResult.utxos.filter(utxo => this._isValidUtxo(utxo))
268
+
269
+ // Store UTXOs
270
+ this.utxoStore.xecUtxos = validUtxos
271
+ this.utxoStore.lastUpdated = Date.now()
272
+ this.utxoStore.cacheKey = addr
273
+ this.performanceMetrics.lastRefreshTime = Date.now()
274
+ }
275
+
276
+ _isValidUtxo (utxo) {
277
+ return this.security.isValidUtxoStructure(utxo) && this._getUtxoValue(utxo) > 0
278
+ }
279
+
280
+ _getUtxoValue (utxo) {
281
+ if (utxo.sats !== undefined) {
282
+ return typeof utxo.sats === 'bigint' ? Number(utxo.sats) : parseInt(utxo.sats)
283
+ }
284
+ if (utxo.value !== undefined) {
285
+ return typeof utxo.value === 'bigint' ? Number(utxo.value) : parseInt(utxo.value)
286
+ }
287
+ return 0
288
+ }
289
+
290
+ _isCacheValid (addr) {
291
+ return (
292
+ this.utxoStore.cacheKey === addr &&
293
+ this.utxoStore.lastUpdated &&
294
+ (Date.now() - this.utxoStore.lastUpdated) < this.cacheTimeout
295
+ )
296
+ }
297
+
298
+ _delay (ms) {
299
+ return new Promise(resolve => setTimeout(resolve, ms))
300
+ }
301
+ }
302
+
303
+ module.exports = Utxos