react-native-rgb 0.2.0

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 (44) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +165 -0
  3. package/Rgb.podspec +23 -0
  4. package/android/build.gradle +80 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/rgb/AppConstants.kt +57 -0
  8. package/android/src/main/java/com/rgb/RgbModule.kt +1959 -0
  9. package/android/src/main/java/com/rgb/RgbPackage.kt +33 -0
  10. package/android/src/main/java/com/rgb/WalletStore.kt +49 -0
  11. package/ios/AppConstants.swift +71 -0
  12. package/ios/Rgb.h +5 -0
  13. package/ios/Rgb.mm +1740 -0
  14. package/ios/Rgb.swift +1916 -0
  15. package/ios/RgbLib.swift +7615 -0
  16. package/ios/WalletStore.swift +61 -0
  17. package/lib/module/Interfaces.js +65 -0
  18. package/lib/module/Interfaces.js.map +1 -0
  19. package/lib/module/NativeRgb.js +5 -0
  20. package/lib/module/NativeRgb.js.map +1 -0
  21. package/lib/module/RgbError.js +65 -0
  22. package/lib/module/RgbError.js.map +1 -0
  23. package/lib/module/Wallet.js +854 -0
  24. package/lib/module/Wallet.js.map +1 -0
  25. package/lib/module/index.js +39 -0
  26. package/lib/module/index.js.map +1 -0
  27. package/lib/module/package.json +1 -0
  28. package/lib/typescript/package.json +1 -0
  29. package/lib/typescript/src/Interfaces.d.ts +390 -0
  30. package/lib/typescript/src/Interfaces.d.ts.map +1 -0
  31. package/lib/typescript/src/NativeRgb.d.ts +417 -0
  32. package/lib/typescript/src/NativeRgb.d.ts.map +1 -0
  33. package/lib/typescript/src/RgbError.d.ts +28 -0
  34. package/lib/typescript/src/RgbError.d.ts.map +1 -0
  35. package/lib/typescript/src/Wallet.d.ts +648 -0
  36. package/lib/typescript/src/Wallet.d.ts.map +1 -0
  37. package/lib/typescript/src/index.d.ts +28 -0
  38. package/lib/typescript/src/index.d.ts.map +1 -0
  39. package/package.json +174 -0
  40. package/src/Interfaces.ts +376 -0
  41. package/src/NativeRgb.ts +630 -0
  42. package/src/RgbError.ts +84 -0
  43. package/src/Wallet.ts +1118 -0
  44. package/src/index.tsx +46 -0
@@ -0,0 +1,1959 @@
1
+ package com.rgb
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReadableArray
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.WritableMap
9
+ import com.facebook.react.module.annotations.ReactModule
10
+ import kotlinx.coroutines.CoroutineScope
11
+ import kotlinx.coroutines.Dispatchers
12
+ import kotlinx.coroutines.SupervisorJob
13
+ import kotlinx.coroutines.launch
14
+ import kotlinx.coroutines.withContext
15
+ import org.rgbtools.AssetSchema
16
+ import org.rgbtools.Assignment
17
+ import org.rgbtools.BitcoinNetwork
18
+ import org.rgbtools.DatabaseType
19
+ import org.rgbtools.RefreshFilter
20
+ import org.rgbtools.Recipient
21
+ import org.rgbtools.RefreshTransferStatus
22
+ import org.rgbtools.Wallet
23
+ import org.rgbtools.WalletData
24
+ import org.rgbtools.WitnessData
25
+ import org.rgbtools.generateKeys
26
+ import org.rgbtools.restoreKeys
27
+ import com.facebook.react.bridge.ReadableMap
28
+ import com.facebook.react.bridge.ReadableType
29
+ import org.rgbtools.Token
30
+ import org.rgbtools.Invoice
31
+
32
+ @ReactModule(name = RgbModule.NAME)
33
+ class RgbModule(reactContext: ReactApplicationContext) :
34
+ NativeRgbSpec(reactContext) {
35
+ private val coroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
36
+ private val TAG = "RNRgb"
37
+
38
+ init {
39
+ AppConstants.ensureInitialized(reactContext)
40
+ }
41
+
42
+ override fun getName(): String {
43
+ return NAME
44
+ }
45
+
46
+ companion object {
47
+ const val NAME = "Rgb"
48
+ }
49
+
50
+ private suspend fun resolvePromise(promise: Promise, result: String) {
51
+ withContext(Dispatchers.Main) {
52
+ promise.resolve(result)
53
+ }
54
+ }
55
+
56
+ private fun getErrorClassName(exception: Exception): String {
57
+ val className = exception.javaClass.name
58
+ // Handle nested classes (separated by $)
59
+ val parts = className.split('$')
60
+ // Take the last part and split by . to get simple name
61
+ val lastPart = parts.last().split('.').last()
62
+ return lastPart
63
+ }
64
+
65
+ private fun parseErrorMessage(message: String?): String {
66
+ if (message == null) return "Unknown error"
67
+ // Remove "details=" prefix if present
68
+ return if (message.startsWith("details=", ignoreCase = true)) {
69
+ message.substring(8).trim()
70
+ } else {
71
+ message
72
+ }
73
+ }
74
+
75
+ override fun generateKeys(bitcoinNetwork: String, promise: Promise) {
76
+ coroutineScope.launch(Dispatchers.IO) {
77
+ try {
78
+ val network = when (bitcoinNetwork) {
79
+ "MAINNET" -> BitcoinNetwork.MAINNET
80
+ "TESTNET" -> BitcoinNetwork.TESTNET
81
+ "TESTNET4" -> BitcoinNetwork.TESTNET4
82
+ "REGTEST" -> BitcoinNetwork.REGTEST
83
+ "SIGNET" -> BitcoinNetwork.SIGNET
84
+ else -> throw IllegalArgumentException("Unknown BitcoinNetwork: $bitcoinNetwork")
85
+ }
86
+
87
+ val keys = generateKeys(bitcoinNetwork = network)
88
+ val result = Arguments.createMap()
89
+ result.putString("mnemonic", keys.mnemonic)
90
+ result.putString("xpub", keys.xpub)
91
+ result.putString("accountXpubVanilla", keys.accountXpubVanilla)
92
+ result.putString("accountXpubColored", keys.accountXpubColored)
93
+ result.putString("masterFingerprint", keys.masterFingerprint)
94
+ promise.resolve(result)
95
+ } catch (e: Exception) {
96
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
97
+ }
98
+ }
99
+ }
100
+
101
+ override fun restoreKeys(bitcoinNetwork: String, mnemonic: String, promise: Promise) {
102
+ coroutineScope.launch(Dispatchers.IO) {
103
+ try {
104
+ val network = when (bitcoinNetwork) {
105
+ "MAINNET" -> BitcoinNetwork.MAINNET
106
+ "TESTNET" -> BitcoinNetwork.TESTNET
107
+ "TESTNET4" -> BitcoinNetwork.TESTNET4
108
+ "REGTEST" -> BitcoinNetwork.REGTEST
109
+ "SIGNET" -> BitcoinNetwork.SIGNET
110
+ else -> throw IllegalArgumentException("Unknown BitcoinNetwork: $bitcoinNetwork")
111
+ }
112
+ val keys = restoreKeys(bitcoinNetwork = network, mnemonic = mnemonic)
113
+ val result = Arguments.createMap()
114
+ result.putString("mnemonic", keys.mnemonic)
115
+ result.putString("xpub", keys.xpub)
116
+ result.putString("accountXpubVanilla", keys.accountXpubVanilla)
117
+ result.putString("accountXpubColored", keys.accountXpubColored)
118
+ result.putString("masterFingerprint", keys.masterFingerprint)
119
+ promise.resolve(result)
120
+ } catch (e: Exception) {
121
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
122
+ }
123
+ }
124
+ }
125
+
126
+ private fun getNetwork(network: String): BitcoinNetwork {
127
+ return when (network) {
128
+ "MAINNET" -> BitcoinNetwork.MAINNET
129
+ "TESTNET" -> BitcoinNetwork.TESTNET
130
+ "TESTNET4" -> BitcoinNetwork.TESTNET4
131
+ "REGTEST" -> BitcoinNetwork.REGTEST
132
+ "SIGNET" -> BitcoinNetwork.SIGNET
133
+ else -> throw IllegalArgumentException("Unknown BitcoinNetwork: $network")
134
+ }
135
+ }
136
+
137
+ private fun getAssetSchema(schema: String): AssetSchema {
138
+ return when (schema) {
139
+ "NIA" -> AssetSchema.NIA
140
+ "UDA" -> AssetSchema.UDA
141
+ "CFA" -> AssetSchema.CFA
142
+ "IFA" -> AssetSchema.IFA
143
+ else -> throw IllegalArgumentException("Unknown AssetSchema: $schema")
144
+ }
145
+ }
146
+
147
+ override fun initializeWallet(
148
+ network: String,
149
+ accountXpubVanilla: String,
150
+ accountXpubColored: String,
151
+ mnemonic: String,
152
+ masterFingerprint: String,
153
+ supportedSchemas: ReadableArray,
154
+ maxAllocationsPerUtxo: Double,
155
+ vanillaKeychain: Double,
156
+ promise: Promise
157
+ ) {
158
+ coroutineScope.launch(Dispatchers.IO) {
159
+ try {
160
+ val rgbDir = AppConstants.rgbDir
161
+ ?: throw IllegalStateException("RGB directory not initialized. Call AppConstants.initContext() first.")
162
+
163
+ val rgbNetwork = getNetwork(network)
164
+ val schemaList = mutableListOf<AssetSchema>()
165
+ for (i in 0 until supportedSchemas.size()) {
166
+ val schemaStr = supportedSchemas.getString(i)
167
+ schemaList.add(getAssetSchema(schemaStr ?: throw IllegalArgumentException("Invalid schema at index $i")))
168
+ }
169
+
170
+ val walletData = WalletData(
171
+ dataDir = rgbDir.absolutePath,
172
+ bitcoinNetwork = rgbNetwork,
173
+ databaseType = DatabaseType.SQLITE,
174
+ maxAllocationsPerUtxo = maxAllocationsPerUtxo.toInt().toUInt(),
175
+ accountXpubVanilla = accountXpubVanilla,
176
+ accountXpubColored = accountXpubColored,
177
+ mnemonic = mnemonic,
178
+ masterFingerprint = masterFingerprint,
179
+ vanillaKeychain = vanillaKeychain.toInt().toUByte(),
180
+ supportedSchemas = schemaList
181
+ )
182
+ val wallet = Wallet(walletData)
183
+ val walletId = WalletStore.create(wallet)
184
+ withContext(Dispatchers.Main) {
185
+ promise.resolve(walletId)
186
+ }
187
+ } catch (e: Exception) {
188
+ Log.e(TAG, "initializeWallet error: ${e.message}", e)
189
+ withContext(Dispatchers.Main) {
190
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ override fun goOnline(
197
+ walletId: Double,
198
+ skipConsistencyCheck: Boolean,
199
+ indexerUrl: String,
200
+ promise: Promise
201
+ ) {
202
+ coroutineScope.launch(Dispatchers.IO) {
203
+ try {
204
+ val session = WalletStore.get(walletId.toInt())
205
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
206
+
207
+ val online = session.wallet.goOnline(
208
+ skipConsistencyCheck = skipConsistencyCheck,
209
+ indexerUrl = indexerUrl
210
+ )
211
+ WalletStore.setOnline(walletId.toInt(), online)
212
+
213
+ withContext(Dispatchers.Main) {
214
+ promise.resolve(null)
215
+ }
216
+ } catch (e: Exception) {
217
+ Log.e(TAG, "goOnline error: ${e.message}", e)
218
+ withContext(Dispatchers.Main) {
219
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
220
+ }
221
+ }
222
+ }
223
+ }
224
+
225
+ override fun getBtcBalance(
226
+ walletId: Double,
227
+ skipSync: Boolean,
228
+ promise: Promise
229
+ ) {
230
+ coroutineScope.launch(Dispatchers.IO) {
231
+ try {
232
+ val session = WalletStore.get(walletId.toInt())
233
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
234
+
235
+ val btcBalance = session.wallet.getBtcBalance(
236
+ online = session.online,
237
+ skipSync = skipSync
238
+ )
239
+
240
+ val result = Arguments.createMap()
241
+
242
+ val vanilla = Arguments.createMap()
243
+ vanilla.putDouble("settled", btcBalance.vanilla.settled.toDouble())
244
+ vanilla.putDouble("future", btcBalance.vanilla.future.toDouble())
245
+ vanilla.putDouble("spendable", btcBalance.vanilla.spendable.toDouble())
246
+
247
+ val colored = Arguments.createMap()
248
+ colored.putDouble("settled", btcBalance.colored.settled.toDouble())
249
+ colored.putDouble("future", btcBalance.colored.future.toDouble())
250
+ colored.putDouble("spendable", btcBalance.colored.spendable.toDouble())
251
+
252
+ result.putMap("vanilla", vanilla)
253
+ result.putMap("colored", colored)
254
+
255
+ withContext(Dispatchers.Main) {
256
+ promise.resolve(result)
257
+ }
258
+ } catch (e: Exception) {
259
+ Log.e(TAG, "getBtcBalance error: ${e.message}", e)
260
+ withContext(Dispatchers.Main) {
261
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ override fun walletClose(walletId: Double, promise: Promise) {
268
+ coroutineScope.launch(Dispatchers.IO) {
269
+ try {
270
+ WalletStore.remove(walletId.toInt())
271
+ withContext(Dispatchers.Main) {
272
+ promise.resolve(null)
273
+ }
274
+ } catch (e: Exception) {
275
+ Log.e(TAG, "walletClose error: ${e.message}", e)
276
+ withContext(Dispatchers.Main) {
277
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // Helper functions for type conversions
284
+ private fun getAssignment(assignmentMap: ReadableMap): Assignment {
285
+ val type = assignmentMap.getString("type") ?: throw IllegalArgumentException("Assignment type is required")
286
+ return when (type) {
287
+ "FUNGIBLE" -> {
288
+ val amount = assignmentMap.getDouble("amount").toULong()
289
+ Assignment.Fungible(amount)
290
+ }
291
+ "NON_FUNGIBLE" -> Assignment.NonFungible
292
+ "INFLATION_RIGHT" -> {
293
+ val amount = assignmentMap.getDouble("amount").toULong()
294
+ Assignment.InflationRight(amount)
295
+ }
296
+ "REPLACE_RIGHT" -> Assignment.ReplaceRight
297
+ "ANY" -> Assignment.Any
298
+ else -> throw IllegalArgumentException("Unknown Assignment type: $type")
299
+ }
300
+ }
301
+
302
+ private fun getRefreshFilter(filterMap: ReadableMap): RefreshFilter {
303
+ val statusStr = filterMap.getString("status") ?: throw IllegalArgumentException("RefreshFilter status is required")
304
+ val status = when (statusStr.uppercase()) {
305
+ "WAITING_COUNTERPARTY" -> RefreshTransferStatus.WAITING_COUNTERPARTY
306
+ "WAITING_CONFIRMATIONS" -> RefreshTransferStatus.WAITING_CONFIRMATIONS
307
+ else -> throw IllegalArgumentException("Unknown RefreshTransferStatus: $statusStr")
308
+ }
309
+ val incoming = filterMap.getBoolean("incoming")
310
+ return RefreshFilter(status, incoming)
311
+ }
312
+
313
+ private fun getRecipient(recipientMap: ReadableMap): Recipient {
314
+ val recipientId = recipientMap.getString("recipientId") ?: throw IllegalArgumentException("Recipient recipientId is required")
315
+ val assignmentMap = recipientMap.getMap("assignment") ?: throw IllegalArgumentException("Recipient assignment is required")
316
+ val assignment = getAssignment(assignmentMap)
317
+
318
+ val transportEndpointsArray = recipientMap.getArray("transportEndpoints") ?: throw IllegalArgumentException("Recipient transportEndpoints is required")
319
+ val transportEndpoints = mutableListOf<String>()
320
+ for (i in 0 until transportEndpointsArray.size()) {
321
+ transportEndpoints.add(transportEndpointsArray.getString(i) ?: "")
322
+ }
323
+
324
+ val witnessData = if (recipientMap.hasKey("witnessData") && recipientMap.getType("witnessData") == ReadableType.Map) {
325
+ val witnessDataMap = recipientMap.getMap("witnessData")
326
+ ?: throw IllegalArgumentException("WitnessData map is null")
327
+
328
+ val amountSat = witnessDataMap.getDouble("amountSat").toULong()
329
+ val blinding = if (witnessDataMap.hasKey("blinding") && !witnessDataMap.isNull("blinding")) {
330
+ witnessDataMap.getDouble("blinding").toULong()
331
+ } else {
332
+ null
333
+ }
334
+
335
+ WitnessData(amountSat, blinding)
336
+ } else {
337
+ null
338
+ }
339
+
340
+ return Recipient(recipientId, witnessData, assignment, transportEndpoints)
341
+ }
342
+
343
+ private fun balanceToMap(balance: org.rgbtools.Balance): WritableMap {
344
+ val map = Arguments.createMap()
345
+ map.putDouble("settled", balance.settled.toDouble())
346
+ map.putDouble("future", balance.future.toDouble())
347
+ map.putDouble("spendable", balance.spendable.toDouble())
348
+ return map
349
+ }
350
+
351
+ private fun assetCfaToMap(asset: org.rgbtools.AssetCfa): WritableMap {
352
+ val map = Arguments.createMap()
353
+ map.putString("assetId", asset.assetId)
354
+ map.putString("name", asset.name)
355
+ asset.details?.let { map.putString("details", it) }
356
+ map.putInt("precision", asset.precision.toInt())
357
+ map.putDouble("issuedSupply", asset.issuedSupply.toDouble())
358
+ map.putDouble("timestamp", asset.timestamp.toDouble())
359
+ map.putDouble("addedAt", asset.addedAt.toDouble())
360
+ map.putMap("balance", balanceToMap(asset.balance))
361
+ asset.media?.let { media ->
362
+ val mediaMap = Arguments.createMap()
363
+ mediaMap.putString("filePath", media.filePath)
364
+ mediaMap.putString("mime", media.mime)
365
+ mediaMap.putString("digest", media.digest)
366
+ map.putMap("media", mediaMap)
367
+ }
368
+ return map
369
+ }
370
+
371
+ private fun assetIfaToMap(asset: org.rgbtools.AssetIfa): WritableMap {
372
+ val map = Arguments.createMap()
373
+ map.putString("assetId", asset.assetId)
374
+ map.putString("ticker", asset.ticker)
375
+ map.putString("name", asset.name)
376
+ asset.details?.let { map.putString("details", it) }
377
+ map.putInt("precision", asset.precision.toInt())
378
+ map.putDouble("initialSupply", asset.initialSupply.toDouble())
379
+ map.putDouble("maxSupply", asset.maxSupply.toDouble())
380
+ map.putDouble("knownCirculatingSupply", asset.knownCirculatingSupply.toDouble())
381
+ map.putDouble("timestamp", asset.timestamp.toDouble())
382
+ map.putDouble("addedAt", asset.addedAt.toDouble())
383
+ map.putMap("balance", balanceToMap(asset.balance))
384
+ asset.media?.let { media ->
385
+ val mediaMap = Arguments.createMap()
386
+ mediaMap.putString("filePath", media.filePath)
387
+ mediaMap.putString("mime", media.mime)
388
+ mediaMap.putString("digest", media.digest)
389
+ map.putMap("media", mediaMap)
390
+ }
391
+ asset.rejectListUrl?.let { map.putString("rejectListUrl", it) }
392
+ return map
393
+ }
394
+
395
+ private fun assetNiaToMap(asset: org.rgbtools.AssetNia): WritableMap {
396
+ val map = Arguments.createMap()
397
+ map.putString("assetId", asset.assetId)
398
+ map.putString("ticker", asset.ticker)
399
+ map.putString("name", asset.name)
400
+ map.putInt("precision", asset.precision.toInt())
401
+ map.putDouble("issuedSupply", asset.issuedSupply.toDouble())
402
+ map.putDouble("timestamp", asset.timestamp.toDouble())
403
+ map.putDouble("addedAt", asset.addedAt.toDouble())
404
+ map.putMap("balance", balanceToMap(asset.balance))
405
+ asset.media?.let { media ->
406
+ val mediaMap = Arguments.createMap()
407
+ mediaMap.putString("filePath", media.filePath)
408
+ mediaMap.putString("mime", media.mime)
409
+ mediaMap.putString("digest", media.digest)
410
+ map.putMap("media", mediaMap)
411
+ }
412
+ return map
413
+ }
414
+
415
+ private fun assetUdaToMap(asset: org.rgbtools.AssetUda): WritableMap {
416
+ val map = Arguments.createMap()
417
+ map.putString("assetId", asset.assetId)
418
+ map.putString("ticker", asset.ticker)
419
+ map.putString("name", asset.name)
420
+ asset.details?.let { map.putString("details", it) }
421
+ map.putInt("precision", asset.precision.toInt())
422
+ map.putDouble("timestamp", asset.timestamp.toDouble())
423
+ map.putDouble("addedAt", asset.addedAt.toDouble())
424
+ map.putMap("balance", balanceToMap(asset.balance))
425
+ asset.token?.let { tokenLight ->
426
+ val token = Arguments.createMap()
427
+ token.putInt("index", tokenLight.index.toInt())
428
+ tokenLight.ticker?.let { token.putString("ticker", it) }
429
+ tokenLight.name?.let { token.putString("name", it) }
430
+ tokenLight.details?.let { token.putString("details", it) }
431
+ token.putBoolean("embeddedMedia", tokenLight.embeddedMedia)
432
+
433
+ tokenLight.media?.let { media ->
434
+ val mediaMap = Arguments.createMap()
435
+ mediaMap.putString("filePath", media.filePath)
436
+ mediaMap.putString("mime", media.mime)
437
+ mediaMap.putString("digest", media.digest)
438
+ token.putMap("media", mediaMap)
439
+ }
440
+
441
+ val attachmentsArray = Arguments.createArray()
442
+ tokenLight.attachments.forEach { (key, media) ->
443
+ val attachmentMap = Arguments.createMap()
444
+ attachmentMap.putInt("key", key.toInt())
445
+ attachmentMap.putString("filePath", media.filePath)
446
+ attachmentMap.putString("mime", media.mime)
447
+ attachmentMap.putString("digest", media.digest)
448
+ attachmentsArray.pushMap(attachmentMap)
449
+ }
450
+ token.putArray("attachments", attachmentsArray)
451
+ token.putBoolean("reserves", tokenLight.reserves)
452
+ map.putMap("token", token)
453
+ }
454
+ return map
455
+ }
456
+
457
+ private fun operationResultToMap(result: org.rgbtools.OperationResult): WritableMap {
458
+ val map = Arguments.createMap()
459
+ map.putString("txid", result.txid)
460
+ map.putInt("batchTransferIdx", result.batchTransferIdx)
461
+ return map
462
+ }
463
+
464
+ private fun receiveDataToMap(data: org.rgbtools.ReceiveData): WritableMap {
465
+ val map = Arguments.createMap()
466
+ map.putString("invoice", data.invoice)
467
+ map.putString("recipientId", data.recipientId)
468
+ data.expirationTimestamp?.let { map.putDouble("expirationTimestamp", it.toDouble()) }
469
+ map.putInt("batchTransferIdx", data.batchTransferIdx)
470
+ return map
471
+ }
472
+
473
+ // Wallet methods
474
+ override fun backup(walletId: Double, backupPath: String, password: String, promise: Promise) {
475
+ coroutineScope.launch(Dispatchers.IO) {
476
+ try {
477
+ val session = WalletStore.get(walletId.toInt())
478
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
479
+
480
+ session.wallet.backup(backupPath, password)
481
+
482
+ withContext(Dispatchers.Main) {
483
+ promise.resolve(null)
484
+ }
485
+ } catch (e: Exception) {
486
+ Log.e(TAG, "backup error: ${e.message}", e)
487
+ withContext(Dispatchers.Main) {
488
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ override fun backupInfo(walletId: Double, promise: Promise) {
495
+ coroutineScope.launch(Dispatchers.IO) {
496
+ try {
497
+ val session = WalletStore.get(walletId.toInt())
498
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
499
+
500
+ val hasBackup = session.wallet.backupInfo()
501
+
502
+ withContext(Dispatchers.Main) {
503
+ promise.resolve(hasBackup)
504
+ }
505
+ } catch (e: Exception) {
506
+ Log.e(TAG, "backupInfo error: ${e.message}", e)
507
+ withContext(Dispatchers.Main) {
508
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
509
+ }
510
+ }
511
+ }
512
+ }
513
+
514
+ override fun blindReceive(
515
+ walletId: Double,
516
+ assetId: String?,
517
+ assignment: ReadableMap,
518
+ durationSeconds: Double?,
519
+ transportEndpoints: ReadableArray,
520
+ minConfirmations: Double,
521
+ promise: Promise
522
+ ) {
523
+ coroutineScope.launch(Dispatchers.IO) {
524
+ try {
525
+ val session = WalletStore.get(walletId.toInt())
526
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
527
+
528
+ val assignmentObj = getAssignment(assignment)
529
+ val endpoints = mutableListOf<String>()
530
+ for (i in 0 until transportEndpoints.size()) {
531
+ endpoints.add(transportEndpoints.getString(i) ?: "")
532
+ }
533
+
534
+ val receiveData = session.wallet.blindReceive(
535
+ assetId,
536
+ assignmentObj,
537
+ durationSeconds?.toInt()?.toUInt(),
538
+ endpoints,
539
+ minConfirmations.toInt().toUByte()
540
+ )
541
+
542
+ withContext(Dispatchers.Main) {
543
+ promise.resolve(receiveDataToMap(receiveData))
544
+ }
545
+ } catch (e: Exception) {
546
+ Log.e(TAG, "blindReceive error: ${e.message}", e)
547
+ withContext(Dispatchers.Main) {
548
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
549
+ }
550
+ }
551
+ }
552
+ }
553
+
554
+ override fun createUtxos(
555
+ walletId: Double,
556
+ upTo: Boolean,
557
+ num: Double?,
558
+ size: Double?,
559
+ feeRate: Double,
560
+ skipSync: Boolean,
561
+ promise: Promise
562
+ ) {
563
+ coroutineScope.launch(Dispatchers.IO) {
564
+ try {
565
+ val session = WalletStore.get(walletId.toInt())
566
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
567
+
568
+ val online = session.online
569
+ ?: throw IllegalStateException("Wallet is not online")
570
+
571
+ val count = session.wallet.createUtxos(
572
+ online,
573
+ upTo,
574
+ num?.toInt()?.toUByte(),
575
+ size?.toInt()?.toUInt(),
576
+ feeRate.toULong(),
577
+ skipSync
578
+ )
579
+
580
+ withContext(Dispatchers.Main) {
581
+ promise.resolve(count.toInt())
582
+ }
583
+ } catch (e: Exception) {
584
+ Log.e(TAG, "createUtxos error: ${e.message}", e)
585
+ withContext(Dispatchers.Main) {
586
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
587
+ }
588
+ }
589
+ }
590
+ }
591
+
592
+ override fun createUtxosBegin(
593
+ walletId: Double,
594
+ upTo: Boolean,
595
+ num: Double?,
596
+ size: Double?,
597
+ feeRate: Double,
598
+ skipSync: Boolean,
599
+ promise: Promise
600
+ ) {
601
+ coroutineScope.launch(Dispatchers.IO) {
602
+ try {
603
+ val session = WalletStore.get(walletId.toInt())
604
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
605
+
606
+ val online = session.online
607
+ ?: throw IllegalStateException("Wallet is not online")
608
+
609
+ val psbt = session.wallet.createUtxosBegin(
610
+ online,
611
+ upTo,
612
+ num?.toInt()?.toUByte(),
613
+ size?.toInt()?.toUInt(),
614
+ feeRate.toULong(),
615
+ skipSync
616
+ )
617
+
618
+ withContext(Dispatchers.Main) {
619
+ promise.resolve(psbt)
620
+ }
621
+ } catch (e: Exception) {
622
+ Log.e(TAG, "createUtxosBegin error: ${e.message}", e)
623
+ withContext(Dispatchers.Main) {
624
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ override fun createUtxosEnd(
631
+ walletId: Double,
632
+ signedPsbt: String,
633
+ skipSync: Boolean,
634
+ promise: Promise
635
+ ) {
636
+ coroutineScope.launch(Dispatchers.IO) {
637
+ try {
638
+ val session = WalletStore.get(walletId.toInt())
639
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
640
+
641
+ val online = session.online
642
+ ?: throw IllegalStateException("Wallet is not online")
643
+
644
+ val count = session.wallet.createUtxosEnd(online, signedPsbt, skipSync)
645
+
646
+ withContext(Dispatchers.Main) {
647
+ promise.resolve(count.toInt())
648
+ }
649
+ } catch (e: Exception) {
650
+ Log.e(TAG, "createUtxosEnd error: ${e.message}", e)
651
+ withContext(Dispatchers.Main) {
652
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ override fun deleteTransfers(
659
+ walletId: Double,
660
+ batchTransferIdx: Double?,
661
+ noAssetOnly: Boolean,
662
+ promise: Promise
663
+ ) {
664
+ coroutineScope.launch(Dispatchers.IO) {
665
+ try {
666
+ val session = WalletStore.get(walletId.toInt())
667
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
668
+
669
+ val deleted = session.wallet.deleteTransfers(
670
+ batchTransferIdx?.toInt(),
671
+ noAssetOnly
672
+ )
673
+
674
+ withContext(Dispatchers.Main) {
675
+ promise.resolve(deleted)
676
+ }
677
+ } catch (e: Exception) {
678
+ Log.e(TAG, "deleteTransfers error: ${e.message}", e)
679
+ withContext(Dispatchers.Main) {
680
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
681
+ }
682
+ }
683
+ }
684
+ }
685
+
686
+ override fun drainTo(
687
+ walletId: Double,
688
+ address: String,
689
+ destroyAssets: Boolean,
690
+ feeRate: Double,
691
+ promise: Promise
692
+ ) {
693
+ coroutineScope.launch(Dispatchers.IO) {
694
+ try {
695
+ val session = WalletStore.get(walletId.toInt())
696
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
697
+
698
+ val online = session.online
699
+ ?: throw IllegalStateException("Wallet is not online")
700
+
701
+ val txid = session.wallet.drainTo(online, address, destroyAssets, feeRate.toULong())
702
+
703
+ withContext(Dispatchers.Main) {
704
+ promise.resolve(txid)
705
+ }
706
+ } catch (e: Exception) {
707
+ Log.e(TAG, "drainTo error: ${e.message}", e)
708
+ withContext(Dispatchers.Main) {
709
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
710
+ }
711
+ }
712
+ }
713
+ }
714
+
715
+ override fun drainToBegin(
716
+ walletId: Double,
717
+ address: String,
718
+ destroyAssets: Boolean,
719
+ feeRate: Double,
720
+ promise: Promise
721
+ ) {
722
+ coroutineScope.launch(Dispatchers.IO) {
723
+ try {
724
+ val session = WalletStore.get(walletId.toInt())
725
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
726
+
727
+ val online = session.online
728
+ ?: throw IllegalStateException("Wallet is not online")
729
+
730
+ val psbt = session.wallet.drainToBegin(online, address, destroyAssets, feeRate.toULong())
731
+
732
+ withContext(Dispatchers.Main) {
733
+ promise.resolve(psbt)
734
+ }
735
+ } catch (e: Exception) {
736
+ Log.e(TAG, "drainToBegin error: ${e.message}", e)
737
+ withContext(Dispatchers.Main) {
738
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
739
+ }
740
+ }
741
+ }
742
+ }
743
+
744
+ override fun drainToEnd(
745
+ walletId: Double,
746
+ signedPsbt: String,
747
+ promise: Promise
748
+ ) {
749
+ coroutineScope.launch(Dispatchers.IO) {
750
+ try {
751
+ val session = WalletStore.get(walletId.toInt())
752
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
753
+
754
+ val online = session.online
755
+ ?: throw IllegalStateException("Wallet is not online")
756
+
757
+ val txid = session.wallet.drainToEnd(online, signedPsbt)
758
+
759
+ withContext(Dispatchers.Main) {
760
+ promise.resolve(txid)
761
+ }
762
+ } catch (e: Exception) {
763
+ Log.e(TAG, "drainToEnd error: ${e.message}", e)
764
+ withContext(Dispatchers.Main) {
765
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
766
+ }
767
+ }
768
+ }
769
+ }
770
+
771
+ override fun failTransfers(
772
+ walletId: Double,
773
+ batchTransferIdx: Double?,
774
+ noAssetOnly: Boolean,
775
+ skipSync: Boolean,
776
+ promise: Promise
777
+ ) {
778
+ coroutineScope.launch(Dispatchers.IO) {
779
+ try {
780
+ val session = WalletStore.get(walletId.toInt())
781
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
782
+
783
+ val online = session.online
784
+ ?: throw IllegalStateException("Wallet is not online")
785
+
786
+ val failed = session.wallet.failTransfers(
787
+ online,
788
+ batchTransferIdx?.toInt(),
789
+ noAssetOnly,
790
+ skipSync
791
+ )
792
+
793
+ withContext(Dispatchers.Main) {
794
+ promise.resolve(failed)
795
+ }
796
+ } catch (e: Exception) {
797
+ Log.e(TAG, "failTransfers error: ${e.message}", e)
798
+ withContext(Dispatchers.Main) {
799
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
800
+ }
801
+ }
802
+ }
803
+ }
804
+
805
+ override fun finalizePsbt(walletId: Double, signedPsbt: String, promise: Promise) {
806
+ coroutineScope.launch(Dispatchers.IO) {
807
+ try {
808
+ val session = WalletStore.get(walletId.toInt())
809
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
810
+
811
+ val finalizedPsbt = session.wallet.finalizePsbt(signedPsbt)
812
+
813
+ withContext(Dispatchers.Main) {
814
+ promise.resolve(finalizedPsbt)
815
+ }
816
+ } catch (e: Exception) {
817
+ Log.e(TAG, "finalizePsbt error: ${e.message}", e)
818
+ withContext(Dispatchers.Main) {
819
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
820
+ }
821
+ }
822
+ }
823
+ }
824
+
825
+ override fun getAddress(walletId: Double, promise: Promise) {
826
+ coroutineScope.launch(Dispatchers.IO) {
827
+ try {
828
+ val session = WalletStore.get(walletId.toInt())
829
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
830
+
831
+ val address = session.wallet.getAddress()
832
+
833
+ withContext(Dispatchers.Main) {
834
+ promise.resolve(address)
835
+ }
836
+ } catch (e: Exception) {
837
+ Log.e(TAG, "getAddress error: ${e.message}", e)
838
+ withContext(Dispatchers.Main) {
839
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
840
+ }
841
+ }
842
+ }
843
+ }
844
+
845
+ override fun getAssetBalance(walletId: Double, assetId: String, promise: Promise) {
846
+ coroutineScope.launch(Dispatchers.IO) {
847
+ try {
848
+ val session = WalletStore.get(walletId.toInt())
849
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
850
+
851
+ val balance = session.wallet.getAssetBalance(assetId)
852
+
853
+ withContext(Dispatchers.Main) {
854
+ promise.resolve(balanceToMap(balance))
855
+ }
856
+ } catch (e: Exception) {
857
+ Log.e(TAG, "getAssetBalance error: ${e.message}", e)
858
+ withContext(Dispatchers.Main) {
859
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
860
+ }
861
+ }
862
+ }
863
+ }
864
+
865
+ override fun getAssetMetadata(walletId: Double, assetId: String, promise: Promise) {
866
+ coroutineScope.launch(Dispatchers.IO) {
867
+ try {
868
+ val session = WalletStore.get(walletId.toInt())
869
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
870
+
871
+ val metadata = session.wallet.getAssetMetadata(assetId)
872
+ val map = Arguments.createMap()
873
+ map.putString("assetId", assetId)
874
+ val assetSchemaString = when (metadata.assetSchema) {
875
+ AssetSchema.NIA -> "NIA"
876
+ AssetSchema.UDA -> "UDA"
877
+ AssetSchema.CFA -> "CFA"
878
+ AssetSchema.IFA -> "IFA"
879
+ }
880
+ map.putString("assetSchema", assetSchemaString)
881
+ map.putString("name", metadata.name)
882
+ map.putInt("precision", metadata.precision.toInt())
883
+ map.putDouble("initialSupply", metadata.initialSupply.toDouble())
884
+ map.putDouble("maxSupply", metadata.maxSupply.toDouble())
885
+ map.putDouble("knownCirculatingSupply", metadata.knownCirculatingSupply.toDouble())
886
+ map.putDouble("timestamp", metadata.timestamp.toDouble())
887
+ metadata.ticker?.let { map.putString("ticker", it) }
888
+ metadata.details?.let { map.putString("details", it) }
889
+ metadata.rejectListUrl?.let { map.putString("rejectListUrl", it) }
890
+ metadata.token?.let { token ->
891
+ val tokenMap = Arguments.createMap()
892
+ tokenMap.putInt("index", token.index.toInt())
893
+ token.ticker?.let { tokenMap.putString("ticker", it) }
894
+ token.name?.let { tokenMap.putString("name", it) }
895
+ token.details?.let { tokenMap.putString("details", it) }
896
+ token.embeddedMedia?.let { embeddedMedia ->
897
+ val embeddedMediaMap = Arguments.createMap()
898
+ embeddedMediaMap.putString("mime", embeddedMedia.mime)
899
+ val dataArray = Arguments.createArray()
900
+ embeddedMedia.data.forEach { dataArray.pushInt(it.toInt()) }
901
+ embeddedMediaMap.putArray("data", dataArray)
902
+ tokenMap.putMap("embeddedMedia", embeddedMediaMap)
903
+ }
904
+ token.media?.let { media ->
905
+ val mediaMap = Arguments.createMap()
906
+ mediaMap.putString("filePath", media.filePath)
907
+ mediaMap.putString("mime", media.mime)
908
+ mediaMap.putString("digest", media.digest)
909
+ tokenMap.putMap("media", mediaMap)
910
+ }
911
+ val attachmentsArray = Arguments.createArray()
912
+ token.attachments.forEach { (key, media) ->
913
+ val attachmentMap = Arguments.createMap()
914
+ attachmentMap.putInt("key", key.toInt())
915
+ attachmentMap.putString("filePath", media.filePath)
916
+ attachmentMap.putString("mime", media.mime)
917
+ attachmentMap.putString("digest", media.digest)
918
+ attachmentsArray.pushMap(attachmentMap)
919
+ }
920
+ tokenMap.putArray("attachments", attachmentsArray)
921
+ token.reserves?.let { reserves ->
922
+ val reservesMap = Arguments.createMap()
923
+ val utxoMap = Arguments.createMap()
924
+ utxoMap.putString("txid", reserves.utxo.txid)
925
+ utxoMap.putDouble("vout", reserves.utxo.vout.toDouble())
926
+ reservesMap.putMap("utxo", utxoMap)
927
+ val proofArray = Arguments.createArray()
928
+ reserves.proof.forEach { proofArray.pushInt(it.toInt()) }
929
+ reservesMap.putArray("proof", proofArray)
930
+ tokenMap.putMap("reserves", reservesMap)
931
+ }
932
+ map.putMap("token", tokenMap)
933
+ }
934
+
935
+ withContext(Dispatchers.Main) {
936
+ promise.resolve(map)
937
+ }
938
+ } catch (e: Exception) {
939
+ Log.e(TAG, "getAssetMetadata error: ${e.message}", e)
940
+ withContext(Dispatchers.Main) {
941
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
942
+ }
943
+ }
944
+ }
945
+ }
946
+
947
+ override fun getFeeEstimation(
948
+ walletId: Double,
949
+ blocks: Double,
950
+ promise: Promise
951
+ ) {
952
+ coroutineScope.launch(Dispatchers.IO) {
953
+ try {
954
+ val session = WalletStore.get(walletId.toInt())
955
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
956
+
957
+ val online = session.online
958
+ ?: throw IllegalStateException("Wallet is not online")
959
+
960
+ val feeRate = session.wallet.getFeeEstimation(online, blocks.toInt().toUShort())
961
+
962
+ withContext(Dispatchers.Main) {
963
+ promise.resolve(feeRate)
964
+ }
965
+ } catch (e: Exception) {
966
+ Log.e(TAG, "getFeeEstimation error: ${e.message}", e)
967
+ withContext(Dispatchers.Main) {
968
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
969
+ }
970
+ }
971
+ }
972
+ }
973
+
974
+ override fun getMediaDir(walletId: Double, promise: Promise) {
975
+ coroutineScope.launch(Dispatchers.IO) {
976
+ try {
977
+ val session = WalletStore.get(walletId.toInt())
978
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
979
+
980
+ val mediaDir = session.wallet.getMediaDir()
981
+
982
+ withContext(Dispatchers.Main) {
983
+ promise.resolve(mediaDir)
984
+ }
985
+ } catch (e: Exception) {
986
+ Log.e(TAG, "getMediaDir error: ${e.message}", e)
987
+ withContext(Dispatchers.Main) {
988
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
989
+ }
990
+ }
991
+ }
992
+ }
993
+
994
+ override fun getWalletData(walletId: Double, promise: Promise) {
995
+ coroutineScope.launch(Dispatchers.IO) {
996
+ try {
997
+ val session = WalletStore.get(walletId.toInt())
998
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
999
+
1000
+ val walletData = session.wallet.getWalletData()
1001
+ val map = Arguments.createMap()
1002
+ map.putString("dataDir", walletData.dataDir)
1003
+
1004
+ val networkString = when (walletData.bitcoinNetwork) {
1005
+ BitcoinNetwork.MAINNET -> "MAINNET"
1006
+ BitcoinNetwork.TESTNET -> "TESTNET"
1007
+ BitcoinNetwork.TESTNET4 -> "TESTNET4"
1008
+ BitcoinNetwork.REGTEST -> "REGTEST"
1009
+ BitcoinNetwork.SIGNET -> "SIGNET"
1010
+ else -> {
1011
+ throw Exception("Unknown bitcoin network")
1012
+ }
1013
+ }
1014
+ map.putString("bitcoinNetwork", networkString)
1015
+
1016
+ val dbTypeString = when (walletData.databaseType) {
1017
+ DatabaseType.SQLITE -> "SQLITE"
1018
+ }
1019
+ map.putString("databaseType", dbTypeString)
1020
+
1021
+ map.putDouble("maxAllocationsPerUtxo", walletData.maxAllocationsPerUtxo.toDouble())
1022
+ map.putString("accountXpubVanilla", walletData.accountXpubVanilla)
1023
+ map.putString("accountXpubColored", walletData.accountXpubColored)
1024
+ walletData.mnemonic?.let { map.putString("mnemonic", it) }
1025
+ map.putString("masterFingerprint", walletData.masterFingerprint)
1026
+ walletData.vanillaKeychain?.let { map.putInt("vanillaKeychain", it.toInt()) }
1027
+ val schemasArray = Arguments.createArray()
1028
+ walletData.supportedSchemas.forEach { schema ->
1029
+ val schemaString = when (schema) {
1030
+ AssetSchema.NIA -> "NIA"
1031
+ AssetSchema.UDA -> "UDA"
1032
+ AssetSchema.CFA -> "CFA"
1033
+ AssetSchema.IFA -> "IFA"
1034
+ }
1035
+ schemasArray.pushString(schemaString)
1036
+ }
1037
+ map.putArray("supportedSchemas", schemasArray)
1038
+
1039
+ withContext(Dispatchers.Main) {
1040
+ promise.resolve(map)
1041
+ }
1042
+ } catch (e: Exception) {
1043
+ Log.e(TAG, "getWalletData error: ${e.message}", e)
1044
+ withContext(Dispatchers.Main) {
1045
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1046
+ }
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ override fun getWalletDir(walletId: Double, promise: Promise) {
1052
+ coroutineScope.launch(Dispatchers.IO) {
1053
+ try {
1054
+ val session = WalletStore.get(walletId.toInt())
1055
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1056
+
1057
+ val walletDir = session.wallet.getWalletDir()
1058
+
1059
+ withContext(Dispatchers.Main) {
1060
+ promise.resolve(walletDir)
1061
+ }
1062
+ } catch (e: Exception) {
1063
+ Log.e(TAG, "getWalletDir error: ${e.message}", e)
1064
+ withContext(Dispatchers.Main) {
1065
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1066
+ }
1067
+ }
1068
+ }
1069
+ }
1070
+
1071
+ override fun inflate(
1072
+ walletId: Double,
1073
+ assetId: String,
1074
+ inflationAmounts: ReadableArray,
1075
+ feeRate: Double,
1076
+ minConfirmations: Double,
1077
+ promise: Promise
1078
+ ) {
1079
+ coroutineScope.launch(Dispatchers.IO) {
1080
+ try {
1081
+ val session = WalletStore.get(walletId.toInt())
1082
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1083
+
1084
+ val online = session.online
1085
+ ?: throw IllegalStateException("Wallet is not online")
1086
+
1087
+ val amounts = mutableListOf<ULong>()
1088
+ for (i in 0 until inflationAmounts.size()) {
1089
+ amounts.add(inflationAmounts.getDouble(i).toULong())
1090
+ }
1091
+
1092
+ val result = session.wallet.inflate(
1093
+ online,
1094
+ assetId,
1095
+ amounts,
1096
+ feeRate.toULong(),
1097
+ minConfirmations.toInt().toUByte()
1098
+ )
1099
+
1100
+ withContext(Dispatchers.Main) {
1101
+ promise.resolve(operationResultToMap(result))
1102
+ }
1103
+ } catch (e: Exception) {
1104
+ Log.e(TAG, "inflate error: ${e.message}", e)
1105
+ withContext(Dispatchers.Main) {
1106
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1107
+ }
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ override fun inflateBegin(
1113
+ walletId: Double,
1114
+ assetId: String,
1115
+ inflationAmounts: ReadableArray,
1116
+ feeRate: Double,
1117
+ minConfirmations: Double,
1118
+ promise: Promise
1119
+ ) {
1120
+ coroutineScope.launch(Dispatchers.IO) {
1121
+ try {
1122
+ val session = WalletStore.get(walletId.toInt())
1123
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1124
+
1125
+ val online = session.online
1126
+ ?: throw IllegalStateException("Wallet is not online")
1127
+
1128
+ val amounts = mutableListOf<ULong>()
1129
+ for (i in 0 until inflationAmounts.size()) {
1130
+ amounts.add(inflationAmounts.getDouble(i).toULong())
1131
+ }
1132
+
1133
+ val psbt = session.wallet.inflateBegin(
1134
+ online,
1135
+ assetId,
1136
+ amounts,
1137
+ feeRate.toULong(),
1138
+ minConfirmations.toInt().toUByte()
1139
+ )
1140
+
1141
+ withContext(Dispatchers.Main) {
1142
+ promise.resolve(psbt)
1143
+ }
1144
+ } catch (e: Exception) {
1145
+ Log.e(TAG, "inflateBegin error: ${e.message}", e)
1146
+ withContext(Dispatchers.Main) {
1147
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ override fun inflateEnd(
1154
+ walletId: Double,
1155
+ signedPsbt: String,
1156
+ promise: Promise
1157
+ ) {
1158
+ coroutineScope.launch(Dispatchers.IO) {
1159
+ try {
1160
+ val session = WalletStore.get(walletId.toInt())
1161
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1162
+
1163
+ val online = session.online
1164
+ ?: throw IllegalStateException("Wallet is not online")
1165
+
1166
+ val result = session.wallet.inflateEnd(online, signedPsbt)
1167
+
1168
+ withContext(Dispatchers.Main) {
1169
+ promise.resolve(operationResultToMap(result))
1170
+ }
1171
+ } catch (e: Exception) {
1172
+ Log.e(TAG, "inflateEnd error: ${e.message}", e)
1173
+ withContext(Dispatchers.Main) {
1174
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ override fun issueAssetCfa(
1181
+ walletId: Double,
1182
+ name: String,
1183
+ details: String?,
1184
+ precision: Double,
1185
+ amounts: ReadableArray,
1186
+ filePath: String?,
1187
+ promise: Promise
1188
+ ) {
1189
+ coroutineScope.launch(Dispatchers.IO) {
1190
+ try {
1191
+ val session = WalletStore.get(walletId.toInt())
1192
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1193
+
1194
+ val amountsList = mutableListOf<ULong>()
1195
+ for (i in 0 until amounts.size()) {
1196
+ amountsList.add(amounts.getDouble(i).toULong())
1197
+ }
1198
+
1199
+ val asset = session.wallet.issueAssetCfa(
1200
+ name,
1201
+ details,
1202
+ precision.toInt().toUByte(),
1203
+ amountsList,
1204
+ filePath
1205
+ )
1206
+
1207
+ withContext(Dispatchers.Main) {
1208
+ promise.resolve(assetCfaToMap(asset))
1209
+ }
1210
+ } catch (e: Exception) {
1211
+ Log.e(TAG, "issueAssetCfa error: ${e.message}", e)
1212
+ withContext(Dispatchers.Main) {
1213
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1214
+ }
1215
+ }
1216
+ }
1217
+ }
1218
+
1219
+ override fun issueAssetIfa(
1220
+ walletId: Double,
1221
+ ticker: String,
1222
+ name: String,
1223
+ precision: Double,
1224
+ amounts: ReadableArray,
1225
+ inflationAmounts: ReadableArray,
1226
+ replaceRightsNum: Double,
1227
+ rejectListUrl: String?,
1228
+ promise: Promise
1229
+ ) {
1230
+ coroutineScope.launch(Dispatchers.IO) {
1231
+ try {
1232
+ val session = WalletStore.get(walletId.toInt())
1233
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1234
+
1235
+ val amountsList = mutableListOf<ULong>()
1236
+ for (i in 0 until amounts.size()) {
1237
+ amountsList.add(amounts.getDouble(i).toULong())
1238
+ }
1239
+
1240
+ val inflationAmountsList = mutableListOf<ULong>()
1241
+ for (i in 0 until inflationAmounts.size()) {
1242
+ inflationAmountsList.add(inflationAmounts.getDouble(i).toULong())
1243
+ }
1244
+
1245
+ val asset = session.wallet.issueAssetIfa(
1246
+ ticker,
1247
+ name,
1248
+ precision.toInt().toUByte(),
1249
+ amountsList,
1250
+ inflationAmountsList,
1251
+ replaceRightsNum.toInt().toUByte(),
1252
+ rejectListUrl
1253
+ )
1254
+
1255
+ withContext(Dispatchers.Main) {
1256
+ promise.resolve(assetIfaToMap(asset))
1257
+ }
1258
+ } catch (e: Exception) {
1259
+ Log.e(TAG, "issueAssetIfa error: ${e.message}", e)
1260
+ withContext(Dispatchers.Main) {
1261
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ override fun issueAssetNia(
1268
+ walletId: Double,
1269
+ ticker: String,
1270
+ name: String,
1271
+ precision: Double,
1272
+ amounts: ReadableArray,
1273
+ promise: Promise
1274
+ ) {
1275
+ coroutineScope.launch(Dispatchers.IO) {
1276
+ try {
1277
+ val session = WalletStore.get(walletId.toInt())
1278
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1279
+
1280
+ val amountsList = mutableListOf<ULong>()
1281
+ for (i in 0 until amounts.size()) {
1282
+ amountsList.add(amounts.getDouble(i).toULong())
1283
+ }
1284
+
1285
+ val asset = session.wallet.issueAssetNia(
1286
+ ticker,
1287
+ name,
1288
+ precision.toInt().toUByte(),
1289
+ amountsList
1290
+ )
1291
+
1292
+ withContext(Dispatchers.Main) {
1293
+ promise.resolve(assetNiaToMap(asset))
1294
+ }
1295
+ } catch (e: Exception) {
1296
+ Log.e(TAG, "issueAssetNia error: ${e.message}", e)
1297
+ withContext(Dispatchers.Main) {
1298
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1303
+
1304
+ override fun issueAssetUda(
1305
+ walletId: Double,
1306
+ ticker: String,
1307
+ name: String,
1308
+ details: String?,
1309
+ precision: Double,
1310
+ mediaFilePath: String?,
1311
+ attachmentsFilePaths: ReadableArray,
1312
+ promise: Promise
1313
+ ) {
1314
+ coroutineScope.launch(Dispatchers.IO) {
1315
+ try {
1316
+ val session = WalletStore.get(walletId.toInt())
1317
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1318
+
1319
+ val attachmentsList = mutableListOf<String>()
1320
+ for (i in 0 until attachmentsFilePaths.size()) {
1321
+ attachmentsList.add(attachmentsFilePaths.getString(i) ?: "")
1322
+ }
1323
+
1324
+ val asset = session.wallet.issueAssetUda(
1325
+ ticker,
1326
+ name,
1327
+ details,
1328
+ precision.toInt().toUByte(),
1329
+ mediaFilePath,
1330
+ attachmentsList
1331
+ )
1332
+
1333
+ withContext(Dispatchers.Main) {
1334
+ promise.resolve(assetUdaToMap(asset))
1335
+ }
1336
+ } catch (e: Exception) {
1337
+ Log.e(TAG, "issueAssetUda error: ${e.message}", e)
1338
+ withContext(Dispatchers.Main) {
1339
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ override fun listAssets(
1346
+ walletId: Double,
1347
+ filterAssetSchemas: ReadableArray,
1348
+ promise: Promise
1349
+ ) {
1350
+ coroutineScope.launch(Dispatchers.IO) {
1351
+ try {
1352
+ val session = WalletStore.get(walletId.toInt())
1353
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1354
+
1355
+ val schemaList = mutableListOf<AssetSchema>()
1356
+ for (i in 0 until filterAssetSchemas.size()) {
1357
+ val schemaStr = filterAssetSchemas.getString(i)
1358
+ schemaList.add(getAssetSchema(schemaStr ?: ""))
1359
+ }
1360
+
1361
+ val assets = session.wallet.listAssets(schemaList)
1362
+ val result = Arguments.createMap()
1363
+
1364
+ val niaArray = Arguments.createArray()
1365
+ assets.nia?.forEach { asset ->
1366
+ niaArray.pushMap(assetNiaToMap(asset))
1367
+ }
1368
+ result.putArray("nia", niaArray)
1369
+
1370
+ val udaArray = Arguments.createArray()
1371
+ assets.uda?.forEach { asset ->
1372
+ udaArray.pushMap(assetUdaToMap(asset))
1373
+ }
1374
+ result.putArray("uda", udaArray)
1375
+
1376
+ val cfaArray = Arguments.createArray()
1377
+ assets.cfa?.forEach { asset ->
1378
+ cfaArray.pushMap(assetCfaToMap(asset))
1379
+ }
1380
+ result.putArray("cfa", cfaArray)
1381
+
1382
+ val ifaArray = Arguments.createArray()
1383
+ assets.ifa?.forEach { asset ->
1384
+ ifaArray.pushMap(assetIfaToMap(asset))
1385
+ }
1386
+ result.putArray("ifa", ifaArray)
1387
+
1388
+ withContext(Dispatchers.Main) {
1389
+ promise.resolve(result)
1390
+ }
1391
+ } catch (e: Exception) {
1392
+ Log.e(TAG, "listAssets error: ${e.message}", e)
1393
+ withContext(Dispatchers.Main) {
1394
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1395
+ }
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ override fun listTransactions(
1401
+ walletId: Double,
1402
+ skipSync: Boolean,
1403
+ promise: Promise
1404
+ ) {
1405
+ coroutineScope.launch(Dispatchers.IO) {
1406
+ try {
1407
+ val session = WalletStore.get(walletId.toInt())
1408
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1409
+
1410
+ val online = session.online
1411
+
1412
+ val transactions = session.wallet.listTransactions(online, skipSync)
1413
+ val transactionsArray = Arguments.createArray()
1414
+
1415
+ transactions.forEach { tx ->
1416
+ val txMap = Arguments.createMap()
1417
+ val txTypeString = when (tx.transactionType) {
1418
+ org.rgbtools.TransactionType.RGB_SEND -> "RGB_SEND"
1419
+ org.rgbtools.TransactionType.DRAIN -> "DRAIN"
1420
+ org.rgbtools.TransactionType.CREATE_UTXOS -> "CREATE_UTXOS"
1421
+ org.rgbtools.TransactionType.USER -> "USER"
1422
+ }
1423
+ txMap.putString("transactionType", txTypeString)
1424
+ txMap.putString("txid", tx.txid)
1425
+ txMap.putDouble("received", tx.received.toDouble())
1426
+ txMap.putDouble("sent", tx.sent.toDouble())
1427
+ txMap.putDouble("fee", tx.fee.toDouble())
1428
+ tx.confirmationTime?.let { blockTime ->
1429
+ txMap.putDouble("confirmationTime", blockTime.timestamp.toDouble())
1430
+ }
1431
+ transactionsArray.pushMap(txMap)
1432
+ }
1433
+
1434
+ withContext(Dispatchers.Main) {
1435
+ promise.resolve(transactionsArray)
1436
+ }
1437
+ } catch (e: Exception) {
1438
+ Log.e(TAG, "listTransactions error: ${e.message}", e)
1439
+ withContext(Dispatchers.Main) {
1440
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+
1446
+ override fun listTransfers(
1447
+ walletId: Double,
1448
+ assetId: String?,
1449
+ promise: Promise
1450
+ ) {
1451
+ coroutineScope.launch(Dispatchers.IO) {
1452
+ try {
1453
+ val session = WalletStore.get(walletId.toInt())
1454
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1455
+
1456
+ val transfers = session.wallet.listTransfers(assetId)
1457
+ val transfersArray = Arguments.createArray()
1458
+
1459
+ transfers.forEach { transfer ->
1460
+ val transferMap = Arguments.createMap()
1461
+ transferMap.putInt("transferIdx", transfer.idx)
1462
+ transferMap.putInt("batchTransferIdx", transfer.batchTransferIdx)
1463
+ transferMap.putDouble("createdAt", transfer.createdAt.toDouble())
1464
+ transferMap.putDouble("updatedAt", transfer.updatedAt.toDouble())
1465
+
1466
+ val kindString = when (transfer.kind) {
1467
+ org.rgbtools.TransferKind.ISSUANCE -> "ISSUANCE"
1468
+ org.rgbtools.TransferKind.RECEIVE_BLIND -> "RECEIVE_BLIND"
1469
+ org.rgbtools.TransferKind.RECEIVE_WITNESS -> "RECEIVE_WITNESS"
1470
+ org.rgbtools.TransferKind.SEND -> "SEND"
1471
+ org.rgbtools.TransferKind.INFLATION -> "INFLATION"
1472
+ }
1473
+ transferMap.putString("kind", kindString)
1474
+
1475
+ val statusString = when (transfer.status) {
1476
+ org.rgbtools.TransferStatus.WAITING_COUNTERPARTY -> "WAITING_COUNTERPARTY"
1477
+ org.rgbtools.TransferStatus.WAITING_CONFIRMATIONS -> "WAITING_CONFIRMATIONS"
1478
+ org.rgbtools.TransferStatus.SETTLED -> "SETTLED"
1479
+ org.rgbtools.TransferStatus.FAILED -> "FAILED"
1480
+ }
1481
+ transferMap.putString("status", statusString)
1482
+
1483
+ transfer.txid?.let { transferMap.putString("txid", it) }
1484
+ transfer.recipientId?.let { transferMap.putString("recipientId", it) }
1485
+ transfer.receiveUtxo?.let {
1486
+ transferMap.putString("receiveUtxoTxid", it.txid)
1487
+ transferMap.putDouble("receiveUtxoVout", it.vout.toDouble())
1488
+ }
1489
+ transfer.changeUtxo?.let {
1490
+ transferMap.putString("changeUtxoTxid", it.txid)
1491
+ transferMap.putDouble("changeUtxoVout", it.vout.toDouble())
1492
+ }
1493
+ transfer.expiration?.let { transferMap.putDouble("expiration", it.toDouble()) }
1494
+
1495
+ val transportEndpointsStrings = transfer.transportEndpoints.map { it.endpoint }
1496
+ transferMap.putString("transportEndpoints", transportEndpointsStrings.joinToString(","))
1497
+
1498
+ transfer.invoiceString?.let { transferMap.putString("invoiceString", it) }
1499
+ transfer.consignmentPath?.let { transferMap.putString("consignmentPath", it) }
1500
+
1501
+ transferMap.putInt("assignmentsCount", transfer.assignments.size)
1502
+
1503
+ transfersArray.pushMap(transferMap)
1504
+ }
1505
+
1506
+ withContext(Dispatchers.Main) {
1507
+ promise.resolve(transfersArray)
1508
+ }
1509
+ } catch (e: Exception) {
1510
+ Log.e(TAG, "listTransfers error: ${e.message}", e)
1511
+ withContext(Dispatchers.Main) {
1512
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+
1518
+ override fun listUnspents(
1519
+ walletId: Double,
1520
+ settledOnly: Boolean,
1521
+ skipSync: Boolean,
1522
+ promise: Promise
1523
+ ) {
1524
+ coroutineScope.launch(Dispatchers.IO) {
1525
+ try {
1526
+ val session = WalletStore.get(walletId.toInt())
1527
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1528
+
1529
+ val online = session.online
1530
+
1531
+ val unspents = session.wallet.listUnspents(online, settledOnly, skipSync)
1532
+ val unspentsArray = Arguments.createArray()
1533
+
1534
+ unspents.forEach { unspent ->
1535
+ val unspentMap = Arguments.createMap()
1536
+ val utxoMap = Arguments.createMap()
1537
+ utxoMap.putString("txid", unspent.utxo.outpoint.txid)
1538
+ utxoMap.putDouble("vout", unspent.utxo.outpoint.vout.toDouble())
1539
+ utxoMap.putDouble("btcAmount", unspent.utxo.btcAmount.toDouble())
1540
+ utxoMap.putBoolean("colorable", unspent.utxo.colorable)
1541
+ utxoMap.putBoolean("exists", unspent.utxo.exists)
1542
+ unspentMap.putMap("utxo", utxoMap)
1543
+ unspentMap.putDouble("pendingBlinded", unspent.pendingBlinded.toDouble())
1544
+ unspentMap.putDouble("rgbAllocationsCount", unspent.rgbAllocations.size.toDouble())
1545
+ unspentsArray.pushMap(unspentMap)
1546
+ }
1547
+
1548
+ withContext(Dispatchers.Main) {
1549
+ promise.resolve(unspentsArray)
1550
+ }
1551
+ } catch (e: Exception) {
1552
+ Log.e(TAG, "listUnspents error: ${e.message}", e)
1553
+ withContext(Dispatchers.Main) {
1554
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1555
+ }
1556
+ }
1557
+ }
1558
+ }
1559
+
1560
+ override fun refresh(
1561
+ walletId: Double,
1562
+ assetId: String?,
1563
+ filter: ReadableArray,
1564
+ skipSync: Boolean,
1565
+ promise: Promise
1566
+ ) {
1567
+ coroutineScope.launch(Dispatchers.IO) {
1568
+ try {
1569
+ val session = WalletStore.get(walletId.toInt())
1570
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1571
+
1572
+ val online = session.online
1573
+ ?: throw IllegalStateException("Wallet is not online")
1574
+
1575
+ val filterList = mutableListOf<RefreshFilter>()
1576
+ for (i in 0 until filter.size()) {
1577
+ val filterMap = filter.getMap(i) ?: continue
1578
+ filterList.add(getRefreshFilter(filterMap))
1579
+ }
1580
+
1581
+ val refreshed = session.wallet.refresh(online, assetId, filterList, skipSync)
1582
+ val result = Arguments.createMap()
1583
+
1584
+ refreshed.forEach { (idx, refreshedTransfer) ->
1585
+ val refreshedMap = Arguments.createMap()
1586
+ refreshedTransfer.updatedStatus?.let { status ->
1587
+ val statusString = when (status) {
1588
+ org.rgbtools.TransferStatus.WAITING_COUNTERPARTY -> "WAITING_COUNTERPARTY"
1589
+ org.rgbtools.TransferStatus.WAITING_CONFIRMATIONS -> "WAITING_CONFIRMATIONS"
1590
+ org.rgbtools.TransferStatus.SETTLED -> "SETTLED"
1591
+ org.rgbtools.TransferStatus.FAILED -> "FAILED"
1592
+ }
1593
+ refreshedMap.putString("updatedStatus", statusString)
1594
+ }
1595
+ refreshedTransfer.failure?.let {
1596
+ refreshedMap.putString("failure", it.toString())
1597
+ }
1598
+ result.putMap(idx.toString(), refreshedMap)
1599
+ }
1600
+
1601
+ withContext(Dispatchers.Main) {
1602
+ promise.resolve(result)
1603
+ }
1604
+ } catch (e: Exception) {
1605
+ Log.e(TAG, "refresh error: ${e.message}", e)
1606
+ withContext(Dispatchers.Main) {
1607
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1608
+ }
1609
+ }
1610
+ }
1611
+ }
1612
+
1613
+ override fun send(
1614
+ walletId: Double,
1615
+ recipientMap: ReadableMap,
1616
+ donation: Boolean,
1617
+ feeRate: Double,
1618
+ minConfirmations: Double,
1619
+ skipSync: Boolean,
1620
+ promise: Promise
1621
+ ) {
1622
+ coroutineScope.launch(Dispatchers.IO) {
1623
+ try {
1624
+ val session = WalletStore.get(walletId.toInt())
1625
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1626
+
1627
+ val online = session.online
1628
+ ?: throw IllegalStateException("Wallet is not online")
1629
+
1630
+ // Convert ReadableMap to Map<String, List<Recipient>>
1631
+ val recipientMapNative = mutableMapOf<String, List<Recipient>>()
1632
+ val keys = recipientMap.keySetIterator()
1633
+ while (keys.hasNextKey()) {
1634
+ val key = keys.nextKey()
1635
+ val recipientsArray = recipientMap.getArray(key) ?: continue
1636
+ val recipientsList = mutableListOf<Recipient>()
1637
+ for (i in 0 until recipientsArray.size()) {
1638
+ val recipientMapItem = recipientsArray.getMap(i) ?: continue
1639
+ recipientsList.add(getRecipient(recipientMapItem))
1640
+ }
1641
+ recipientMapNative[key] = recipientsList
1642
+ }
1643
+
1644
+ val result = session.wallet.send(
1645
+ online,
1646
+ recipientMapNative,
1647
+ donation,
1648
+ feeRate.toULong(),
1649
+ minConfirmations.toInt().toUByte(),
1650
+ skipSync
1651
+ )
1652
+
1653
+ withContext(Dispatchers.Main) {
1654
+ promise.resolve(operationResultToMap(result))
1655
+ }
1656
+ } catch (e: Exception) {
1657
+ Log.e(TAG, "send error: ${e.message}", e)
1658
+ withContext(Dispatchers.Main) {
1659
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1660
+ }
1661
+ }
1662
+ }
1663
+ }
1664
+
1665
+ override fun sendBegin(
1666
+ walletId: Double,
1667
+ recipientMap: ReadableMap,
1668
+ donation: Boolean,
1669
+ feeRate: Double,
1670
+ minConfirmations: Double,
1671
+ promise: Promise
1672
+ ) {
1673
+ coroutineScope.launch(Dispatchers.IO) {
1674
+ try {
1675
+ val session = WalletStore.get(walletId.toInt())
1676
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1677
+
1678
+ val online = session.online
1679
+ ?: throw IllegalStateException("Wallet is not online")
1680
+
1681
+ // Convert ReadableMap to Map<String, List<Recipient>>
1682
+ val recipientMapNative = mutableMapOf<String, List<Recipient>>()
1683
+ val keys = recipientMap.keySetIterator()
1684
+ while (keys.hasNextKey()) {
1685
+ val key = keys.nextKey()
1686
+ val recipientsArray = recipientMap.getArray(key) ?: continue
1687
+ val recipientsList = mutableListOf<Recipient>()
1688
+ for (i in 0 until recipientsArray.size()) {
1689
+ val recipientMapItem = recipientsArray.getMap(i) ?: continue
1690
+ recipientsList.add(getRecipient(recipientMapItem))
1691
+ }
1692
+ recipientMapNative[key] = recipientsList
1693
+ }
1694
+
1695
+ val psbt = session.wallet.sendBegin(
1696
+ online,
1697
+ recipientMapNative,
1698
+ donation,
1699
+ feeRate.toULong(),
1700
+ minConfirmations.toInt().toUByte()
1701
+ )
1702
+
1703
+ withContext(Dispatchers.Main) {
1704
+ promise.resolve(psbt)
1705
+ }
1706
+ } catch (e: Exception) {
1707
+ Log.e(TAG, "sendBegin error: ${e.message}", e)
1708
+ withContext(Dispatchers.Main) {
1709
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1710
+ }
1711
+ }
1712
+ }
1713
+ }
1714
+
1715
+ override fun sendBtc(
1716
+ walletId: Double,
1717
+ address: String,
1718
+ amount: Double,
1719
+ feeRate: Double,
1720
+ skipSync: Boolean,
1721
+ promise: Promise
1722
+ ) {
1723
+ coroutineScope.launch(Dispatchers.IO) {
1724
+ try {
1725
+ val session = WalletStore.get(walletId.toInt())
1726
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1727
+
1728
+ val online = session.online
1729
+ ?: throw IllegalStateException("Wallet is not online")
1730
+
1731
+ val txid = session.wallet.sendBtc(
1732
+ online,
1733
+ address,
1734
+ amount.toULong(),
1735
+ feeRate.toULong(),
1736
+ skipSync
1737
+ )
1738
+
1739
+ withContext(Dispatchers.Main) {
1740
+ promise.resolve(txid)
1741
+ }
1742
+ } catch (e: Exception) {
1743
+ Log.e(TAG, "sendBtc error: ${e.message}", e)
1744
+ withContext(Dispatchers.Main) {
1745
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1746
+ }
1747
+ }
1748
+ }
1749
+ }
1750
+
1751
+ override fun sendBtcBegin(
1752
+ walletId: Double,
1753
+ address: String,
1754
+ amount: Double,
1755
+ feeRate: Double,
1756
+ skipSync: Boolean,
1757
+ promise: Promise
1758
+ ) {
1759
+ coroutineScope.launch(Dispatchers.IO) {
1760
+ try {
1761
+ val session = WalletStore.get(walletId.toInt())
1762
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1763
+
1764
+ val online = session.online
1765
+ ?: throw IllegalStateException("Wallet is not online")
1766
+
1767
+ val psbt = session.wallet.sendBtcBegin(
1768
+ online,
1769
+ address,
1770
+ amount.toULong(),
1771
+ feeRate.toULong(),
1772
+ skipSync
1773
+ )
1774
+
1775
+ withContext(Dispatchers.Main) {
1776
+ promise.resolve(psbt)
1777
+ }
1778
+ } catch (e: Exception) {
1779
+ Log.e(TAG, "sendBtcBegin error: ${e.message}", e)
1780
+ withContext(Dispatchers.Main) {
1781
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1782
+ }
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ override fun sendBtcEnd(
1788
+ walletId: Double,
1789
+ signedPsbt: String,
1790
+ skipSync: Boolean,
1791
+ promise: Promise
1792
+ ) {
1793
+ coroutineScope.launch(Dispatchers.IO) {
1794
+ try {
1795
+ val session = WalletStore.get(walletId.toInt())
1796
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1797
+
1798
+ val online = session.online
1799
+ ?: throw IllegalStateException("Wallet is not online")
1800
+
1801
+ val txid = session.wallet.sendBtcEnd(online, signedPsbt, skipSync)
1802
+
1803
+ withContext(Dispatchers.Main) {
1804
+ promise.resolve(txid)
1805
+ }
1806
+ } catch (e: Exception) {
1807
+ Log.e(TAG, "sendBtcEnd error: ${e.message}", e)
1808
+ withContext(Dispatchers.Main) {
1809
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1810
+ }
1811
+ }
1812
+ }
1813
+ }
1814
+
1815
+ override fun sendEnd(
1816
+ walletId: Double,
1817
+ signedPsbt: String,
1818
+ skipSync: Boolean,
1819
+ promise: Promise
1820
+ ) {
1821
+ coroutineScope.launch(Dispatchers.IO) {
1822
+ try {
1823
+ val session = WalletStore.get(walletId.toInt())
1824
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1825
+
1826
+ val online = session.online
1827
+ ?: throw IllegalStateException("Wallet is not online")
1828
+
1829
+ val result = session.wallet.sendEnd(online, signedPsbt, skipSync)
1830
+
1831
+ withContext(Dispatchers.Main) {
1832
+ promise.resolve(operationResultToMap(result))
1833
+ }
1834
+ } catch (e: Exception) {
1835
+ Log.e(TAG, "sendEnd error: ${e.message}", e)
1836
+ withContext(Dispatchers.Main) {
1837
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+
1843
+ override fun signPsbt(walletId: Double, unsignedPsbt: String, promise: Promise) {
1844
+ coroutineScope.launch(Dispatchers.IO) {
1845
+ try {
1846
+ val session = WalletStore.get(walletId.toInt())
1847
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1848
+
1849
+ val signedPsbt = session.wallet.signPsbt(unsignedPsbt)
1850
+
1851
+ withContext(Dispatchers.Main) {
1852
+ promise.resolve(signedPsbt)
1853
+ }
1854
+ } catch (e: Exception) {
1855
+ Log.e(TAG, "signPsbt error: ${e.message}", e)
1856
+ withContext(Dispatchers.Main) {
1857
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1858
+ }
1859
+ }
1860
+ }
1861
+ }
1862
+
1863
+ override fun sync(walletId: Double, promise: Promise) {
1864
+ coroutineScope.launch(Dispatchers.IO) {
1865
+ try {
1866
+ val session = WalletStore.get(walletId.toInt())
1867
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1868
+
1869
+ val online = session.online
1870
+ ?: throw IllegalStateException("Wallet is not online")
1871
+
1872
+ session.wallet.sync(online)
1873
+
1874
+ withContext(Dispatchers.Main) {
1875
+ promise.resolve(null)
1876
+ }
1877
+ } catch (e: Exception) {
1878
+ Log.e(TAG, "sync error: ${e.message}", e)
1879
+ withContext(Dispatchers.Main) {
1880
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1881
+ }
1882
+ }
1883
+ }
1884
+ }
1885
+
1886
+ override fun witnessReceive(
1887
+ walletId: Double,
1888
+ assetId: String?,
1889
+ assignment: ReadableMap,
1890
+ durationSeconds: Double?,
1891
+ transportEndpoints: ReadableArray,
1892
+ minConfirmations: Double,
1893
+ promise: Promise
1894
+ ) {
1895
+ coroutineScope.launch(Dispatchers.IO) {
1896
+ try {
1897
+ val session = WalletStore.get(walletId.toInt())
1898
+ ?: throw IllegalStateException("Wallet with id $walletId not found")
1899
+
1900
+ val assignmentObj = getAssignment(assignment)
1901
+ val endpoints = mutableListOf<String>()
1902
+ for (i in 0 until transportEndpoints.size()) {
1903
+ endpoints.add(transportEndpoints.getString(i) ?: "")
1904
+ }
1905
+
1906
+ val receiveData = session.wallet.witnessReceive(
1907
+ assetId,
1908
+ assignmentObj,
1909
+ durationSeconds?.toInt()?.toUInt(),
1910
+ endpoints,
1911
+ minConfirmations.toInt().toUByte()
1912
+ )
1913
+
1914
+ withContext(Dispatchers.Main) {
1915
+ promise.resolve(receiveDataToMap(receiveData))
1916
+ }
1917
+ } catch (e: Exception) {
1918
+ Log.e(TAG, "witnessReceive error: ${e.message}", e)
1919
+ withContext(Dispatchers.Main) {
1920
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1921
+ }
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ override fun decodeInvoice(invoice: String, promise: Promise) {
1927
+ coroutineScope.launch(Dispatchers.IO) {
1928
+ try {
1929
+ val invoiceData = Invoice(invoiceString = invoice).invoiceData()
1930
+ val map = Arguments.createMap()
1931
+ map.putString("invoice", invoice)
1932
+ map.putString("recipientId", invoiceData.recipientId)
1933
+ map.putString("assetSchema", invoiceData.assetSchema?.toString())
1934
+ map.putString("assetId", invoiceData.assetId)
1935
+ map.putString("assignment", invoiceData.assignment.toString())
1936
+ map.putString("assignmentName", invoiceData.assignmentName)
1937
+ map.putString("network", invoiceData.network.toString())
1938
+ val transportEndpointsStrings = invoiceData.transportEndpoints.map { it }
1939
+ map.putString("transportEndpoints", transportEndpointsStrings.joinToString(","))
1940
+
1941
+ invoiceData.expirationTimestamp?.let {
1942
+ map.putDouble("expirationTimestamp", it.toDouble())
1943
+ } ?: run {
1944
+ map.putNull("expirationTimestamp")
1945
+ }
1946
+
1947
+ withContext(Dispatchers.Main) {
1948
+ promise.resolve(map)
1949
+ }
1950
+ } catch (e: Exception) {
1951
+ Log.e(TAG, "decodeInvoice error: ${e.message}", e)
1952
+ withContext(Dispatchers.Main) {
1953
+ promise.reject(getErrorClassName(e), parseErrorMessage(e.message), e)
1954
+ }
1955
+ }
1956
+ }
1957
+ }
1958
+
1959
+ }