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.
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/minimal-xec-wallet.js +66268 -0
- package/dist/minimal-xec-wallet.min.js +55 -0
- package/examples/README.md +380 -0
- package/examples/advanced/browser-compatibility-test.js +263 -0
- package/examples/advanced/get-xec-price.js +149 -0
- package/examples/advanced/optimize-utxos.js +255 -0
- package/examples/advanced/send-op-return.js +216 -0
- package/examples/browser-test.html +350 -0
- package/examples/key-management/derive-addresses.js +191 -0
- package/examples/key-management/export-to-wif.js +114 -0
- package/examples/key-management/validate-address.js +214 -0
- package/examples/optimization/simple-consolidation-test.js +79 -0
- package/examples/optimization/test-utxo-consolidation.js +179 -0
- package/examples/test-examples.js +1204 -0
- package/examples/tokens/burn-tokens.js +293 -0
- package/examples/tokens/get-token-balance.js +169 -0
- package/examples/tokens/get-token-info.js +269 -0
- package/examples/tokens/list-all-tokens.js +162 -0
- package/examples/tokens/send-any-token.js +260 -0
- package/examples/tokens/test-main-wallet-integration.js +193 -0
- package/examples/transactions/send-all-xec.js +205 -0
- package/examples/transactions/send-to-multiple.js +217 -0
- package/examples/transactions/send-xec.js +191 -0
- package/examples/utils/show-qr.js +119 -0
- package/examples/utils/wallet-helper.js +176 -0
- package/examples/validation/comprehensive-infrastructure-test.js +210 -0
- package/examples/wallet-creation/create-new-wallet.js +67 -0
- package/examples/wallet-creation/import-from-wif.js +135 -0
- package/examples/wallet-creation/restore-from-mnemonic.js +100 -0
- package/examples/wallet-info/get-balance.js +99 -0
- package/examples/wallet-info/get-transactions.js +157 -0
- package/examples/wallet-info/get-utxos.js +145 -0
- package/examples/wallet.json +11 -0
- package/lib/adapters/robust-chronik-router.js +507 -0
- package/lib/adapters/router.js +651 -0
- package/lib/alp-token-handler.js +581 -0
- package/lib/browser-wasm-loader.js +271 -0
- package/lib/consolidate-utxos.js +338 -0
- package/lib/hybrid-token-manager.js +322 -0
- package/lib/key-derivation.js +466 -0
- package/lib/op-return.js +314 -0
- package/lib/security.js +270 -0
- package/lib/send-xec.js +396 -0
- package/lib/slp-token-handler.js +572 -0
- package/lib/token-protocol-detector.js +307 -0
- package/lib/utxos.js +303 -0
- package/package.json +125 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Hybrid Token Manager - Coordinates SLP and ALP token operations
|
|
3
|
+
Provides unified interface for both protocols
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const TokenProtocolDetector = require('./token-protocol-detector')
|
|
7
|
+
const SLPTokenHandler = require('./slp-token-handler')
|
|
8
|
+
const ALPTokenHandler = require('./alp-token-handler')
|
|
9
|
+
|
|
10
|
+
class HybridTokenManager {
|
|
11
|
+
constructor (localConfig = {}) {
|
|
12
|
+
this.chronik = localConfig.chronik
|
|
13
|
+
this.ar = localConfig.ar
|
|
14
|
+
|
|
15
|
+
if (!this.chronik) {
|
|
16
|
+
throw new Error('Chronik client required for token operations')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!this.ar) {
|
|
20
|
+
throw new Error('AdapterRouter required for token operations')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Initialize protocol handlers
|
|
24
|
+
this.slpHandler = new SLPTokenHandler(localConfig)
|
|
25
|
+
this.alpHandler = new ALPTokenHandler(localConfig)
|
|
26
|
+
|
|
27
|
+
// Cache for token metadata
|
|
28
|
+
this.tokenMetadataCache = new Map()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async sendTokens (tokenId, outputs, walletInfo, utxos, satsPerByte = 1.2) {
|
|
32
|
+
try {
|
|
33
|
+
// Detect protocol for this token
|
|
34
|
+
const protocol = await this._detectTokenProtocol(tokenId, utxos)
|
|
35
|
+
|
|
36
|
+
// Route to appropriate handler
|
|
37
|
+
switch (protocol) {
|
|
38
|
+
case 'SLP':
|
|
39
|
+
return await this.slpHandler.sendTokens(tokenId, outputs, walletInfo, utxos, satsPerByte)
|
|
40
|
+
case 'ALP':
|
|
41
|
+
return await this.alpHandler.sendTokens(tokenId, outputs, walletInfo, utxos, satsPerByte)
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Unsupported token protocol: ${protocol}`)
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
throw new Error(`Token send failed: ${err.message}`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async burnTokens (tokenId, amount, walletInfo, utxos, satsPerByte = 1.2) {
|
|
51
|
+
try {
|
|
52
|
+
// Detect protocol for this token
|
|
53
|
+
const protocol = await this._detectTokenProtocol(tokenId, utxos)
|
|
54
|
+
|
|
55
|
+
// Route to appropriate handler
|
|
56
|
+
switch (protocol) {
|
|
57
|
+
case 'SLP':
|
|
58
|
+
return await this.slpHandler.burnTokens(tokenId, amount, walletInfo, utxos, satsPerByte)
|
|
59
|
+
case 'ALP':
|
|
60
|
+
return await this.alpHandler.burnTokens(tokenId, amount, walletInfo, utxos, satsPerByte)
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`Unsupported token protocol: ${protocol}`)
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
throw new Error(`Token burn failed: ${err.message}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async burnAllTokens (tokenId, walletInfo, utxos) {
|
|
70
|
+
try {
|
|
71
|
+
// Get token metadata and calculate total balance
|
|
72
|
+
const tokenInfo = await this._getTokenInfo(tokenId)
|
|
73
|
+
const filtered = TokenProtocolDetector.filterUtxosForToken(utxos, tokenId)
|
|
74
|
+
|
|
75
|
+
if (filtered.tokenUtxos.length === 0) {
|
|
76
|
+
throw new Error(`No ${tokenInfo.genesisInfo.tokenTicker} tokens found to burn`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Calculate total balance in display units
|
|
80
|
+
const totalAtoms = filtered.tokenSummary.totalAtoms
|
|
81
|
+
const displayAmount = this._atomsToDisplay(totalAtoms, tokenInfo.genesisInfo.decimals)
|
|
82
|
+
|
|
83
|
+
// Burn all tokens
|
|
84
|
+
return await this.burnTokens(tokenId, displayAmount, walletInfo, utxos)
|
|
85
|
+
} catch (err) {
|
|
86
|
+
throw new Error(`Burn all tokens failed: ${err.message}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async listTokensFromAddress (address) {
|
|
91
|
+
try {
|
|
92
|
+
// Get UTXOs for the address using the adapter router
|
|
93
|
+
const utxoData = await this.ar.getUtxos(address)
|
|
94
|
+
const utxos = utxoData.utxos || []
|
|
95
|
+
|
|
96
|
+
// Use the existing listTokensFromUtxos method
|
|
97
|
+
return await this.listTokensFromUtxos(utxos)
|
|
98
|
+
} catch (err) {
|
|
99
|
+
throw new Error(`List tokens failed: ${err.message}`)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async listTokensFromUtxos (utxos) {
|
|
104
|
+
try {
|
|
105
|
+
const inventory = TokenProtocolDetector.getTokenInventory(utxos)
|
|
106
|
+
const tokens = []
|
|
107
|
+
|
|
108
|
+
for (const tokenEntry of inventory) {
|
|
109
|
+
try {
|
|
110
|
+
const tokenInfo = await this._getTokenInfo(tokenEntry.tokenId)
|
|
111
|
+
const filtered = TokenProtocolDetector.filterUtxosForToken(utxos, tokenEntry.tokenId)
|
|
112
|
+
|
|
113
|
+
// Calculate balance based on protocol
|
|
114
|
+
const displayBalance = this._atomsToDisplay(
|
|
115
|
+
filtered.tokenSummary.totalAtoms,
|
|
116
|
+
tokenInfo.genesisInfo.decimals
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
tokens.push({
|
|
120
|
+
tokenId: tokenEntry.tokenId,
|
|
121
|
+
protocol: tokenEntry.protocol,
|
|
122
|
+
ticker: tokenInfo.genesisInfo.tokenTicker,
|
|
123
|
+
name: tokenInfo.genesisInfo.tokenName,
|
|
124
|
+
decimals: tokenInfo.genesisInfo.decimals,
|
|
125
|
+
url: tokenInfo.genesisInfo.url,
|
|
126
|
+
balance: {
|
|
127
|
+
display: displayBalance,
|
|
128
|
+
atoms: filtered.tokenSummary.totalAtoms
|
|
129
|
+
},
|
|
130
|
+
utxoCount: filtered.tokenSummary.utxoCount,
|
|
131
|
+
utxos: filtered.tokenUtxos
|
|
132
|
+
})
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn(`Failed to process token ${tokenEntry.tokenId}: ${err.message}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return tokens
|
|
139
|
+
} catch (err) {
|
|
140
|
+
throw new Error(`List tokens from UTXOs failed: ${err.message}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getTokenBalance (tokenId, utxos) {
|
|
145
|
+
try {
|
|
146
|
+
const filtered = TokenProtocolDetector.filterUtxosForToken(utxos, tokenId)
|
|
147
|
+
|
|
148
|
+
if (filtered.tokenUtxos.length === 0) {
|
|
149
|
+
// Try to get token info, but handle case where token doesn't exist
|
|
150
|
+
try {
|
|
151
|
+
const tokenInfo = await this._getTokenInfo(tokenId)
|
|
152
|
+
return {
|
|
153
|
+
tokenId,
|
|
154
|
+
protocol: tokenInfo.tokenType.protocol,
|
|
155
|
+
ticker: tokenInfo.genesisInfo.tokenTicker,
|
|
156
|
+
name: tokenInfo.genesisInfo.tokenName,
|
|
157
|
+
decimals: tokenInfo.genesisInfo.decimals,
|
|
158
|
+
balance: {
|
|
159
|
+
display: 0,
|
|
160
|
+
atoms: 0n
|
|
161
|
+
},
|
|
162
|
+
utxoCount: 0
|
|
163
|
+
}
|
|
164
|
+
} catch (err) {
|
|
165
|
+
// Token doesn't exist, return minimal info
|
|
166
|
+
return {
|
|
167
|
+
tokenId,
|
|
168
|
+
protocol: 'UNKNOWN',
|
|
169
|
+
ticker: 'UNKNOWN',
|
|
170
|
+
name: 'Unknown Token',
|
|
171
|
+
decimals: 0,
|
|
172
|
+
balance: {
|
|
173
|
+
display: 0,
|
|
174
|
+
atoms: 0n
|
|
175
|
+
},
|
|
176
|
+
utxoCount: 0
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const tokenInfo = await this._getTokenInfo(tokenId)
|
|
182
|
+
|
|
183
|
+
const displayBalance = this._atomsToDisplay(
|
|
184
|
+
filtered.tokenSummary.totalAtoms,
|
|
185
|
+
tokenInfo.genesisInfo.decimals
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
tokenId,
|
|
190
|
+
protocol: filtered.protocol,
|
|
191
|
+
ticker: tokenInfo.genesisInfo.tokenTicker,
|
|
192
|
+
name: tokenInfo.genesisInfo.tokenName,
|
|
193
|
+
decimals: tokenInfo.genesisInfo.decimals,
|
|
194
|
+
balance: {
|
|
195
|
+
display: displayBalance,
|
|
196
|
+
atoms: filtered.tokenSummary.totalAtoms
|
|
197
|
+
},
|
|
198
|
+
utxoCount: filtered.tokenSummary.utxoCount
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
throw new Error(`Get token balance failed: ${err.message}`)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async getTokenData (tokenId, withTxHistory = false, sortOrder = 'DESCENDING') {
|
|
206
|
+
try {
|
|
207
|
+
// Get basic token metadata
|
|
208
|
+
const tokenInfo = await this._getTokenInfo(tokenId)
|
|
209
|
+
|
|
210
|
+
const result = {
|
|
211
|
+
tokenId,
|
|
212
|
+
protocol: tokenInfo.tokenType.protocol,
|
|
213
|
+
type: tokenInfo.tokenType.type,
|
|
214
|
+
ticker: tokenInfo.genesisInfo.tokenTicker,
|
|
215
|
+
name: tokenInfo.genesisInfo.tokenName,
|
|
216
|
+
decimals: tokenInfo.genesisInfo.decimals,
|
|
217
|
+
url: tokenInfo.genesisInfo.url,
|
|
218
|
+
data: tokenInfo.genesisInfo.data,
|
|
219
|
+
authPubkey: tokenInfo.genesisInfo.authPubkey,
|
|
220
|
+
timeFirstSeen: tokenInfo.timeFirstSeen
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Add transaction history if requested
|
|
224
|
+
if (withTxHistory) {
|
|
225
|
+
// This would require additional chronik calls
|
|
226
|
+
// For now, just note that it's not implemented
|
|
227
|
+
result.txHistory = 'Transaction history not implemented yet'
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
} catch (err) {
|
|
232
|
+
throw new Error(`Get token data failed: ${err.message}`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Utility methods
|
|
237
|
+
|
|
238
|
+
getProtocolStats (utxos) {
|
|
239
|
+
return TokenProtocolDetector.getProtocolStats(utxos)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
hasTokens (utxos) {
|
|
243
|
+
return TokenProtocolDetector.hasTokens(utxos)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
hasProtocolTokens (utxos, protocol) {
|
|
247
|
+
return TokenProtocolDetector.hasProtocolTokens(utxos, protocol)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
categorizeUtxos (utxos) {
|
|
251
|
+
return TokenProtocolDetector.categorizeUtxos(utxos)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Private helper methods
|
|
255
|
+
|
|
256
|
+
async _detectTokenProtocol (tokenId, utxos) {
|
|
257
|
+
// First try to detect from UTXOs
|
|
258
|
+
const tokenUtxo = utxos.find(utxo =>
|
|
259
|
+
utxo.token && utxo.token.tokenId === tokenId
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (tokenUtxo) {
|
|
263
|
+
return TokenProtocolDetector.detectProtocol(tokenUtxo)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Fallback to metadata lookup
|
|
267
|
+
try {
|
|
268
|
+
const tokenInfo = await this._getTokenInfo(tokenId)
|
|
269
|
+
return TokenProtocolDetector.detectProtocolFromMetadata(tokenInfo)
|
|
270
|
+
} catch (err) {
|
|
271
|
+
throw new Error(`Cannot determine protocol for token ${tokenId}: ${err.message}`)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async _getTokenInfo (tokenId) {
|
|
276
|
+
// Check cache first
|
|
277
|
+
if (this.tokenMetadataCache.has(tokenId)) {
|
|
278
|
+
return this.tokenMetadataCache.get(tokenId)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Fetch from chronik
|
|
282
|
+
try {
|
|
283
|
+
const tokenInfo = await this.chronik.token(tokenId)
|
|
284
|
+
this.tokenMetadataCache.set(tokenId, tokenInfo)
|
|
285
|
+
return tokenInfo
|
|
286
|
+
} catch (err) {
|
|
287
|
+
throw new Error(`Failed to fetch token metadata: ${err.message}`)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_atomsToDisplay (atoms, decimals) {
|
|
292
|
+
if (decimals === 0) {
|
|
293
|
+
return Number(atoms)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return Number(atoms) / Math.pow(10, decimals)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_displayToAtoms (displayAmount, decimals) {
|
|
300
|
+
if (decimals === 0) {
|
|
301
|
+
return BigInt(Math.floor(displayAmount))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const atoms = Math.floor(displayAmount * Math.pow(10, decimals))
|
|
305
|
+
return BigInt(atoms)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Clear metadata cache
|
|
309
|
+
clearCache () {
|
|
310
|
+
this.tokenMetadataCache.clear()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Get cache statistics
|
|
314
|
+
getCacheStats () {
|
|
315
|
+
return {
|
|
316
|
+
cachedTokens: this.tokenMetadataCache.size,
|
|
317
|
+
tokenIds: Array.from(this.tokenMetadataCache.keys())
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = HybridTokenManager
|