react-native-zcash 0.4.2 → 0.6.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 (157) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +6 -3
  3. package/android/build.gradle +5 -4
  4. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2230000.json +8 -0
  5. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2240000.json +8 -0
  6. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2250000.json +8 -0
  7. package/android/src/main/java/app/edge/rnzcash/RNZcashModule.kt +117 -122
  8. package/ios/RNZcash.m +1 -12
  9. package/ios/RNZcash.swift +127 -153
  10. package/ios/ZCashLightClientKit/Block/Actions/Action.swift +98 -0
  11. package/ios/ZCashLightClientKit/Block/Actions/ClearAlreadyScannedBlocksAction.swift +35 -0
  12. package/ios/ZCashLightClientKit/Block/Actions/ClearCacheAction.swift +30 -0
  13. package/ios/ZCashLightClientKit/Block/Actions/DownloadAction.swift +67 -0
  14. package/ios/ZCashLightClientKit/Block/Actions/EnhanceAction.swift +97 -0
  15. package/ios/ZCashLightClientKit/Block/Actions/FetchUTXOsAction.swift +33 -0
  16. package/ios/ZCashLightClientKit/Block/Actions/MigrateLegacyCacheDBAction.swift +70 -0
  17. package/ios/ZCashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +59 -0
  18. package/ios/ZCashLightClientKit/Block/Actions/RewindAction.swift +48 -0
  19. package/ios/ZCashLightClientKit/Block/Actions/SaplingParamsAction.swift +33 -0
  20. package/ios/ZCashLightClientKit/Block/Actions/ScanAction.swift +95 -0
  21. package/ios/ZCashLightClientKit/Block/Actions/UpdateChainTipAction.swift +55 -0
  22. package/ios/ZCashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift +58 -0
  23. package/ios/ZCashLightClientKit/Block/Actions/ValidateServerAction.swift +60 -0
  24. package/ios/ZCashLightClientKit/Block/CompactBlockProcessor.swift +421 -937
  25. package/ios/ZCashLightClientKit/Block/Download/BlockDownloader.swift +31 -17
  26. package/ios/ZCashLightClientKit/Block/Download/BlockDownloaderService.swift +2 -2
  27. package/ios/ZCashLightClientKit/Block/Enhance/BlockEnhancer.swift +46 -15
  28. package/ios/ZCashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift +4 -15
  29. package/ios/ZCashLightClientKit/Block/FilesystemStorage/FSCompactBlockRepository.swift +4 -4
  30. package/ios/ZCashLightClientKit/Block/Scan/BlockScanner.swift +10 -35
  31. package/ios/ZCashLightClientKit/Block/Utils/CompactBlockProgress.swift +24 -0
  32. package/ios/ZCashLightClientKit/Block/Utils/SyncControlData.swift +25 -0
  33. package/ios/ZCashLightClientKit/ClosureSynchronizer.swift +1 -2
  34. package/ios/ZCashLightClientKit/CombineSynchronizer.swift +2 -5
  35. package/ios/ZCashLightClientKit/Constants/ZcashSDK.swift +7 -25
  36. package/ios/ZCashLightClientKit/DAO/TransactionDao.swift +40 -42
  37. package/ios/ZCashLightClientKit/DAO/UnspentTransactionOutputDao.swift +13 -4
  38. package/ios/ZCashLightClientKit/Entity/AccountEntity.swift +9 -0
  39. package/ios/ZCashLightClientKit/Entity/TransactionEntity.swift +7 -10
  40. package/ios/ZCashLightClientKit/Error/Sourcery/generateErrorCode.sh +1 -1
  41. package/ios/ZCashLightClientKit/Error/ZcashError.swift +121 -12
  42. package/ios/ZCashLightClientKit/Error/ZcashErrorCode.swift +43 -5
  43. package/ios/ZCashLightClientKit/Error/ZcashErrorCodeDefinition.swift +72 -6
  44. package/ios/ZCashLightClientKit/Extensions/Bool+ToData.swift +15 -0
  45. package/ios/ZCashLightClientKit/Extensions/Data+ToOtherTypes.swift +18 -0
  46. package/ios/ZCashLightClientKit/Extensions/Int+ToData.swift +15 -0
  47. package/ios/ZCashLightClientKit/Initializer.swift +47 -26
  48. package/ios/ZCashLightClientKit/Metrics/SDKMetrics.swift +0 -12
  49. package/ios/ZCashLightClientKit/Model/Checkpoint.swift +12 -0
  50. package/ios/ZCashLightClientKit/Model/ScanProgress.swift +29 -0
  51. package/ios/ZCashLightClientKit/Model/ScanRange.swift +31 -0
  52. package/ios/ZCashLightClientKit/Modules/Service/GRPC/LightWalletGRPCService.swift +15 -0
  53. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/compact_formats.pb.swift +150 -46
  54. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/compact_formats.proto +30 -16
  55. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto +32 -6
  56. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift +259 -22
  57. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift +193 -7
  58. package/ios/ZCashLightClientKit/Modules/Service/LightWalletService.swift +8 -0
  59. package/ios/ZCashLightClientKit/Providers/LatestBlocksDataProvider.swift +18 -28
  60. package/ios/ZCashLightClientKit/Repository/CompactBlockRepository.swift +1 -1
  61. package/ios/ZCashLightClientKit/Repository/TransactionRepository.swift +2 -6
  62. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2092500.json +8 -0
  63. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2095000.json +8 -0
  64. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2097500.json +8 -0
  65. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2102500.json +8 -0
  66. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2105000.json +8 -0
  67. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2107500.json +8 -0
  68. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2112500.json +8 -0
  69. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2115000.json +8 -0
  70. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2117500.json +8 -0
  71. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2122500.json +8 -0
  72. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2125000.json +8 -0
  73. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2127500.json +8 -0
  74. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2132500.json +8 -0
  75. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2135000.json +8 -0
  76. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2137500.json +8 -0
  77. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2142500.json +8 -0
  78. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2145000.json +8 -0
  79. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2147500.json +8 -0
  80. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2152500.json +8 -0
  81. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2155000.json +8 -0
  82. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2157500.json +8 -0
  83. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2162500.json +8 -0
  84. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2165000.json +8 -0
  85. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2167500.json +8 -0
  86. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2172500.json +8 -0
  87. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2175000.json +8 -0
  88. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2177500.json +8 -0
  89. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2182500.json +8 -0
  90. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2185000.json +8 -0
  91. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2187500.json +8 -0
  92. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2192500.json +8 -0
  93. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2195000.json +8 -0
  94. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2197500.json +8 -0
  95. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2202500.json +8 -0
  96. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2205000.json +8 -0
  97. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2207500.json +8 -0
  98. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2212500.json +8 -0
  99. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2215000.json +8 -0
  100. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2217500.json +8 -0
  101. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2222500.json +8 -0
  102. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2225000.json +8 -0
  103. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2227500.json +8 -0
  104. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2230000.json +8 -0
  105. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2232500.json +8 -0
  106. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2235000.json +8 -0
  107. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2237500.json +8 -0
  108. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2240000.json +8 -0
  109. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2242500.json +8 -0
  110. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2245000.json +8 -0
  111. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2247500.json +8 -0
  112. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2250000.json +8 -0
  113. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2350000.json +8 -0
  114. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2360000.json +8 -0
  115. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2370000.json +8 -0
  116. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2380000.json +8 -0
  117. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2390000.json +8 -0
  118. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2400000.json +8 -0
  119. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2410000.json +8 -0
  120. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2420000.json +8 -0
  121. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2430000.json +8 -0
  122. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2440000.json +8 -0
  123. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2450000.json +8 -0
  124. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2460000.json +8 -0
  125. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2470000.json +8 -0
  126. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2480000.json +8 -0
  127. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2490000.json +8 -0
  128. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2500000.json +8 -0
  129. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2510000.json +8 -0
  130. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2520000.json +8 -0
  131. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2530000.json +8 -0
  132. package/ios/ZCashLightClientKit/Rust/ZcashRustBackend.swift +293 -158
  133. package/ios/ZCashLightClientKit/Rust/ZcashRustBackendWelding.swift +58 -64
  134. package/ios/ZCashLightClientKit/Rust/zcashlc.h +618 -512
  135. package/ios/ZCashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +2 -8
  136. package/ios/ZCashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +3 -15
  137. package/ios/ZCashLightClientKit/Synchronizer/Dependencies.swift +11 -30
  138. package/ios/ZCashLightClientKit/Synchronizer/SDKSynchronizer.swift +41 -50
  139. package/ios/ZCashLightClientKit/Synchronizer.swift +51 -65
  140. package/ios/ZCashLightClientKit/Transaction/TransactionEncoder.swift +2 -2
  141. package/ios/ZCashLightClientKit/Transaction/WalletTransactionEncoder.swift +7 -7
  142. package/ios/ZCashLightClientKit/Utils/OSLogger.swift +3 -3
  143. package/ios/ZCashLightClientKit/Utils/ZcashFileManager.swift +16 -0
  144. package/ios/libzcashlc.xcframework/Info.plist +9 -5
  145. package/ios/libzcashlc.xcframework/ios-arm64/libzcashlc.a +0 -0
  146. package/ios/libzcashlc.xcframework/ios-arm64_x86_64-simulator/libzcashlc.a +0 -0
  147. package/lib/rnzcash.rn.js +8 -30
  148. package/lib/rnzcash.rn.js.map +1 -1
  149. package/lib/src/react-native.d.ts +3 -5
  150. package/lib/src/types.d.ts +19 -14
  151. package/package.json +1 -1
  152. package/src/react-native.ts +13 -21
  153. package/src/types.ts +26 -23
  154. package/ios/ZCashLightClientKit/Block/Utils/InternalSyncProgress.swift +0 -200
  155. package/ios/ZCashLightClientKit/Block/Validate/BlockValidator.swift +0 -51
  156. package/ios/ZCashLightClientKit/DAO/BlockDao.swift +0 -112
  157. package/ios/ZCashLightClientKit/Entity/BlockProgress.swift +0 -24
package/ios/RNZcash.swift CHANGED
@@ -8,17 +8,21 @@ var SynchronizerMap = [String: WalletSynchronizer]()
8
8
  struct ConfirmedTx {
9
9
  var minedHeight: Int
10
10
  var toAddress: String?
11
+ var raw: String?
11
12
  var rawTransactionId: String
12
13
  var blockTimeInSeconds: Int
13
14
  var value: String
15
+ var fee: String?
14
16
  var memos: Array<String>?
15
17
  var dictionary: [String: Any?] {
16
18
  return [
17
19
  "minedHeight": minedHeight,
18
20
  "toAddress": toAddress,
21
+ "raw": raw,
19
22
  "rawTransactionId": rawTransactionId,
20
23
  "blockTimeInSeconds": blockTimeInSeconds,
21
24
  "value": value,
25
+ "fee": fee,
22
26
  "memos": memos ?? [],
23
27
  ]
24
28
  }
@@ -42,16 +46,10 @@ struct TotalBalances {
42
46
  }
43
47
 
44
48
  struct ProcessorState {
45
- var alias: String
46
- var lastDownloadedHeight: Int
47
- var lastScannedHeight: Int
48
49
  var scanProgress: Int
49
50
  var networkBlockHeight: Int
50
51
  var dictionary: [String: Any] {
51
52
  return [
52
- "alias": alias,
53
- "lastDownloadedHeight": lastDownloadedHeight,
54
- "lastScannedHeight": lastScannedHeight,
55
53
  "scanProgress": scanProgress,
56
54
  "networkBlockHeight": networkBlockHeight,
57
55
  ]
@@ -83,7 +81,7 @@ class RNZcash: RCTEventEmitter {
83
81
  // Synchronizer
84
82
  @objc func initialize(
85
83
  _ seed: String, _ birthdayHeight: Int, _ alias: String, _ networkName: String,
86
- _ defaultHost: String, _ defaultPort: Int, resolver resolve: @escaping RCTPromiseResolveBlock,
84
+ _ defaultHost: String, _ defaultPort: Int, _ newWallet: Bool, resolver resolve: @escaping RCTPromiseResolveBlock,
87
85
  rejecter reject: @escaping RCTPromiseRejectBlock
88
86
  ) {
89
87
  Task {
@@ -92,6 +90,7 @@ class RNZcash: RCTEventEmitter {
92
90
  let initializer = Initializer(
93
91
  cacheDbURL: try! cacheDbURLHelper(alias, network),
94
92
  fsBlockDbRoot: try! fsBlockDbRootURLHelper(alias, network),
93
+ generalStorageURL: try! generalStorageURLHelper(alias, network),
95
94
  dataDbURL: try! dataDbURLHelper(alias, network),
96
95
  endpoint: endpoint,
97
96
  network: network,
@@ -105,12 +104,12 @@ class RNZcash: RCTEventEmitter {
105
104
  let wallet = try WalletSynchronizer(
106
105
  alias: alias, initializer: initializer, emitter: sendToJs)
107
106
  let seedBytes = try Mnemonic.deterministicSeedBytes(from: seed)
108
- let viewingKeys = try deriveUnifiedViewingKey(seed, network)
107
+ let initMode = newWallet ? WalletInitMode.newWallet : WalletInitMode.existingWallet
109
108
 
110
109
  _ = try await wallet.synchronizer.prepare(
111
110
  with: seedBytes,
112
- viewingKeys: [viewingKeys],
113
- walletBirthday: birthdayHeight
111
+ walletBirthday: birthdayHeight,
112
+ for: initMode
114
113
  )
115
114
  try await wallet.synchronizer.start()
116
115
  wallet.subscribe()
@@ -221,7 +220,7 @@ class RNZcash: RCTEventEmitter {
221
220
  memo: sdkMemo
222
221
  )
223
222
 
224
- let tx: NSMutableDictionary = ["txId": broadcastTx.rawID.hexEncodedString()]
223
+ let tx: NSMutableDictionary = ["txId": broadcastTx.rawID.toHexStringTxId()]
225
224
  if broadcastTx.raw != nil {
226
225
  tx["raw"] = broadcastTx.raw?.hexEncodedString()
227
226
  }
@@ -235,97 +234,6 @@ class RNZcash: RCTEventEmitter {
235
234
  }
236
235
  }
237
236
 
238
- @objc func getTransactions(
239
- _ alias: String, _ first: Int, _ last: Int, resolver resolve: @escaping RCTPromiseResolveBlock,
240
- rejecter reject: @escaping RCTPromiseRejectBlock
241
- ) {
242
- Task {
243
- if let wallet = SynchronizerMap[alias] {
244
- if !wallet.fullySynced {
245
- reject("GetTransactionsError", "Wallet is not synced", genericError)
246
- return
247
- }
248
-
249
- var out: [NSDictionary] = []
250
- do {
251
- let txs = try await wallet.synchronizer.allTransactions()
252
- let filterTxs = txs.filter {
253
- $0.minedHeight != nil && $0.minedHeight! >= first && $0.minedHeight! <= last
254
- && $0.blockTime != nil
255
- }
256
- for tx in filterTxs {
257
- var confTx = ConfirmedTx(
258
- minedHeight: tx.minedHeight!,
259
- rawTransactionId: (tx.rawID.toHexStringTxId()),
260
- blockTimeInSeconds: Int(tx.blockTime!),
261
- value: String(describing: abs(tx.value.amount))
262
- )
263
- if tx.isSentTransaction {
264
- let recipients = await wallet.synchronizer.getRecipients(for: tx)
265
- if recipients.count > 0 {
266
- let addresses = recipients.compactMap {
267
- if case let .address(address) = $0 {
268
- return address
269
- } else {
270
- return nil
271
- }
272
- }
273
- if addresses.count > 0 {
274
- confTx.toAddress = addresses.first!.stringEncoded
275
- }
276
- }
277
- }
278
- if tx.memoCount > 0 {
279
- let memos = (try? await wallet.synchronizer.getMemos(for: tx)) ?? []
280
- let textMemos = memos.compactMap {
281
- return $0.toString()
282
- }
283
- confTx.memos = textMemos
284
- }
285
- out.append(confTx.nsDictionary)
286
- }
287
- resolve(out)
288
- } catch {
289
- reject("GetTransactionsError", "Failed to get transactions", genericError)
290
- }
291
- } else {
292
- reject("GetTransactionsError", "Wallet does not exist", genericError)
293
- }
294
- }
295
- }
296
-
297
- @objc func getBalance(
298
- _ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock,
299
- rejecter reject: @escaping RCTPromiseRejectBlock
300
- ) {
301
- Task {
302
- if let wallet = SynchronizerMap[alias] {
303
- do {
304
- let totalShieldedBalance = try await wallet.synchronizer.getShieldedBalance()
305
- let availableShieldedBalance = try await wallet.synchronizer.getShieldedVerifiedBalance()
306
-
307
- let transparentWalletBalance = try await wallet.synchronizer.getTransparentBalance(
308
- accountIndex: 0)
309
- let totalTransparentBalance = transparentWalletBalance.total
310
- let availableTransparentBalance = transparentWalletBalance.verified
311
-
312
- let totalAvailable = availableShieldedBalance + availableTransparentBalance
313
- let totalTotal = totalShieldedBalance + totalTransparentBalance
314
-
315
- let balance = TotalBalances(
316
- availableZatoshi: String(totalAvailable.amount), totalZatoshi: String(totalTotal.amount)
317
- )
318
- resolve(balance.nsDictionary)
319
- return
320
- } catch {
321
- reject("GetShieldedBalanceError", "Error", error)
322
- }
323
- } else {
324
- reject("GetShieldedBalanceError", "Wallet does not exist", genericError)
325
- }
326
- }
327
- }
328
-
329
237
  @objc func rescan(
330
238
  _ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock,
331
239
  rejecter reject: @escaping RCTPromiseRejectBlock
@@ -343,6 +251,7 @@ class RNZcash: RCTEventEmitter {
343
251
  wallet.initializeProcessorState()
344
252
  wallet.cancellables.forEach { $0.cancel() }
345
253
  wallet.subscribe()
254
+ try await wallet.synchronizer.start()
346
255
  resolve(nil)
347
256
  case .failure:
348
257
  reject("RescanError", "Failed to rescan wallet", genericError)
@@ -405,7 +314,14 @@ class RNZcash: RCTEventEmitter {
405
314
  if let wallet = SynchronizerMap[alias] {
406
315
  do {
407
316
  let unifiedAddress = try await wallet.synchronizer.getUnifiedAddress(accountIndex: 0)
408
- resolve(unifiedAddress.stringEncoded)
317
+ let saplingAddress = try await wallet.synchronizer.getSaplingAddress(accountIndex: 0)
318
+ let transparentAddress = try await wallet.synchronizer.getTransparentAddress(accountIndex: 0)
319
+ let addresses: NSDictionary = [
320
+ "unifiedAddress": unifiedAddress.stringEncoded,
321
+ "saplingAddress": saplingAddress.stringEncoded,
322
+ "transparentAddress": transparentAddress.stringEncoded
323
+ ]
324
+ resolve(addresses)
409
325
  return
410
326
  } catch {
411
327
  reject("deriveUnifiedAddress", "Failed to derive unified address", error)
@@ -437,7 +353,7 @@ class RNZcash: RCTEventEmitter {
437
353
  }
438
354
 
439
355
  override func supportedEvents() -> [String] {
440
- return ["StatusEvent", "UpdateEvent"]
356
+ return ["BalanceEvent", "StatusEvent", "TransactionEvent", "UpdateEvent"]
441
357
  }
442
358
  }
443
359
 
@@ -450,6 +366,7 @@ class WalletSynchronizer: NSObject {
450
366
  var restart: Bool
451
367
  var processorState: ProcessorState
452
368
  var cancellables: [AnyCancellable] = []
369
+ var balances: TotalBalances
453
370
 
454
371
  init(alias: String, initializer: Initializer, emitter: @escaping (String, Any) -> Void) throws {
455
372
  self.alias = alias
@@ -459,12 +376,10 @@ class WalletSynchronizer: NSObject {
459
376
  self.fullySynced = false
460
377
  self.restart = false
461
378
  self.processorState = ProcessorState(
462
- alias: self.alias,
463
- lastDownloadedHeight: 0,
464
- lastScannedHeight: 0,
465
379
  scanProgress: 0,
466
380
  networkBlockHeight: 0
467
381
  )
382
+ self.balances = TotalBalances(availableZatoshi: "0", totalZatoshi: "0")
468
383
  }
469
384
 
470
385
  public func subscribe() {
@@ -472,10 +387,25 @@ class WalletSynchronizer: NSObject {
472
387
  .throttle(for: .seconds(0.3), scheduler: DispatchQueue.main, latest: true)
473
388
  .sink(receiveValue: { [weak self] state in self?.updateSyncStatus(event: state) })
474
389
  .store(in: &cancellables)
390
+ self.synchronizer.stateStream
391
+ .sink(receiveValue: { [weak self] state in self?.updateProcessorState(event: state) })
392
+ .store(in: &cancellables)
393
+ self.synchronizer.eventStream
394
+ .sink { SynchronizerEvent in
395
+ switch SynchronizerEvent {
396
+ case .minedTransaction(let transaction):
397
+ self.emitTxs(transactions: [transaction])
398
+ case .foundTransactions(let transactions, _):
399
+ self.emitTxs(transactions: transactions)
400
+ default:
401
+ return
402
+ }
403
+ }
404
+ .store(in: &cancellables)
475
405
  }
476
406
 
477
407
  func updateSyncStatus(event: SynchronizerState) {
478
-
408
+
479
409
  if !self.fullySynced {
480
410
  switch event.internalSyncStatus {
481
411
  case .syncing:
@@ -495,68 +425,104 @@ class WalletSynchronizer: NSObject {
495
425
  let data: NSDictionary = ["alias": self.alias, "name": self.status]
496
426
  emit("StatusEvent", data)
497
427
  }
498
-
499
- updateProcessorState(event: event)
500
428
  }
501
429
 
502
430
  func updateProcessorState(event: SynchronizerState) {
503
- let prevLastDownloadedHeight = self.processorState.lastDownloadedHeight
504
- let prevScanProgress = self.processorState.scanProgress
505
- let prevLastScannedHeight = self.synchronizer.latestState.latestScannedHeight
506
- let prevNetworkBlockHeight = self.processorState.lastScannedHeight
507
-
508
- if event.internalSyncStatus != .synced {
509
- switch event.internalSyncStatus {
510
- case .syncing(let status):
511
- // The SDK emits all zero values just before emitting a SYNCED status so we need to ignore these
512
- if status.targetHeight == 0 {
513
- return
514
- }
515
- self.processorState.lastDownloadedHeight = status.progressHeight
516
- self.processorState.scanProgress = Int(floor(status.progress * 100))
517
- self.processorState.lastScannedHeight = status.progressHeight
518
- self.processorState.networkBlockHeight = status.targetHeight
519
- default:
520
- return
521
- }
522
- } else {
523
- self.processorState.lastDownloadedHeight = self.synchronizer.latestState.latestScannedHeight
524
- self.processorState.scanProgress = 100
525
- self.processorState.lastScannedHeight = self.synchronizer.latestState.latestScannedHeight
526
- self.processorState.networkBlockHeight = event.latestBlockHeight
431
+ var scanProgress = 0
432
+
433
+ switch event.internalSyncStatus {
434
+ case .syncing(let progress):
435
+ scanProgress = Int(floor(progress * 100))
436
+ case .synced:
437
+ scanProgress = 100
438
+ case .unprepared, .disconnected, .stopped:
439
+ scanProgress = 0
440
+ default:
441
+ return
527
442
  }
528
443
 
529
- if self.processorState.lastDownloadedHeight != prevLastDownloadedHeight
530
- || self.processorState.scanProgress != prevScanProgress
531
- || self.processorState.lastScannedHeight != prevLastScannedHeight
532
- || self.processorState.networkBlockHeight != prevNetworkBlockHeight
533
- {
534
- emit("UpdateEvent", self.processorState.nsDictionary)
535
- }
444
+ if (scanProgress == self.processorState.scanProgress && event.latestBlockHeight == self.processorState.networkBlockHeight) { return }
445
+
446
+ self.processorState = ProcessorState(scanProgress: scanProgress, networkBlockHeight: event.latestBlockHeight)
447
+ let data: NSDictionary = ["alias": self.alias, "scanProgress": self.processorState.scanProgress, "networkBlockHeight": self.processorState.networkBlockHeight]
448
+ emit("UpdateEvent", data)
449
+ updateBalanceState(event: event)
536
450
  }
537
451
 
538
452
  func initializeProcessorState() {
539
453
  self.processorState = ProcessorState(
540
- alias: self.alias,
541
- lastDownloadedHeight: 0,
542
- lastScannedHeight: 0,
543
454
  scanProgress: 0,
544
455
  networkBlockHeight: 0
545
456
  )
457
+ self.balances = TotalBalances(availableZatoshi: "0", totalZatoshi: "0")
546
458
  }
547
- }
548
459
 
549
- func z_hexEncodedString(data: Data) -> String {
550
- let hexDigits = Array("0123456789abcdef".utf16)
551
- var chars: [unichar] = []
460
+ func updateBalanceState(event: SynchronizerState) {
461
+ let transparentBalance = event.transparentBalance
462
+ let shieldedBalance = event.shieldedBalance
463
+
464
+ let availableTransparentBalance = transparentBalance.verified
465
+ let totalTransparentBalance = transparentBalance.total
466
+
467
+ let availableShieldedBalance = shieldedBalance.verified
468
+ let totalShieldedBalance = shieldedBalance.total
469
+
470
+ let availableZatoshi = String((availableShieldedBalance + availableTransparentBalance).amount)
471
+ let totalZatoshi = String((totalShieldedBalance + totalTransparentBalance).amount)
472
+
473
+ if (availableZatoshi == self.balances.availableZatoshi && totalZatoshi == self.balances.totalZatoshi) { return }
552
474
 
553
- chars.reserveCapacity(2 * data.count)
554
- for byte in data {
555
- chars.append(hexDigits[Int(byte / 16)])
556
- chars.append(hexDigits[Int(byte % 16)])
475
+ self.balances = TotalBalances(availableZatoshi: availableZatoshi, totalZatoshi: totalZatoshi)
476
+ let data: NSDictionary = ["alias": self.alias, "availableZatoshi": self.balances.availableZatoshi, "totalZatoshi": self.balances.totalZatoshi]
477
+ emit("BalanceEvent", data)
557
478
  }
558
479
 
559
- return String(utf16CodeUnits: chars, count: chars.count)
480
+ func emitTxs(transactions: [ZcashTransaction.Overview]) {
481
+ Task {
482
+ var out: [NSDictionary] = []
483
+ for tx in transactions {
484
+ if (tx.isExpiredUmined ?? false) { continue }
485
+ var confTx = ConfirmedTx(
486
+ minedHeight: tx.minedHeight!,
487
+ rawTransactionId: (tx.rawID.toHexStringTxId()),
488
+ blockTimeInSeconds: Int(tx.blockTime!),
489
+ value: String(describing: abs(tx.value.amount))
490
+ )
491
+ if tx.raw != nil {
492
+ confTx.raw = tx.raw!.hexEncodedString()
493
+ }
494
+ if tx.fee != nil {
495
+ confTx.fee = String(describing: abs(tx.value.amount))
496
+ }
497
+ if tx.isSentTransaction {
498
+ let recipients = await self.synchronizer.getRecipients(for: tx)
499
+ if recipients.count > 0 {
500
+ let addresses = recipients.compactMap {
501
+ if case let .address(address) = $0 {
502
+ return address
503
+ } else {
504
+ return nil
505
+ }
506
+ }
507
+ if addresses.count > 0 {
508
+ confTx.toAddress = addresses.first!.stringEncoded
509
+ }
510
+ }
511
+ }
512
+ if tx.memoCount > 0 {
513
+ let memos = (try? await self.synchronizer.getMemos(for: tx)) ?? []
514
+ let textMemos = memos.compactMap {
515
+ return $0.toString()
516
+ }
517
+ confTx.memos = textMemos
518
+ }
519
+ out.append(confTx.nsDictionary)
520
+ }
521
+
522
+ let data: NSDictionary = ["alias": self.alias, "transactions": NSArray(array: out)]
523
+ emit("TransactionEvent", data)
524
+ }
525
+ }
560
526
  }
561
527
 
562
528
  // Local file helper funcs
@@ -596,3 +562,11 @@ func fsBlockDbRootURLHelper(_ alias: String, _ network: ZcashNetwork) throws ->
596
562
  isDirectory: true
597
563
  )
598
564
  }
565
+
566
+ func generalStorageURLHelper(_ alias: String, _ network: ZcashNetwork) throws -> URL {
567
+ try documentsDirectoryHelper()
568
+ .appendingPathComponent(
569
+ network.constants.defaultDbNamePrefix + alias + "general_storage",
570
+ isDirectory: true
571
+ )
572
+ }
@@ -0,0 +1,98 @@
1
+ //
2
+ // Action.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ protocol ActionContext {
11
+ var state: CBPState { get async }
12
+ var prevState: CBPState? { get async }
13
+ var syncControlData: SyncControlData { get async }
14
+ var requestedRewindHeight: BlockHeight? { get async }
15
+ var processedHeight: BlockHeight { get async }
16
+ var lastChainTipUpdateTime: TimeInterval { get async }
17
+ var lastScannedHeight: BlockHeight? { get async }
18
+ var lastEnhancedHeight: BlockHeight? { get async }
19
+
20
+ func update(state: CBPState) async
21
+ func update(syncControlData: SyncControlData) async
22
+ func update(processedHeight: BlockHeight) async
23
+ func update(lastChainTipUpdateTime: TimeInterval) async
24
+ func update(lastScannedHeight: BlockHeight) async
25
+ func update(lastDownloadedHeight: BlockHeight) async
26
+ func update(lastEnhancedHeight: BlockHeight?) async
27
+ func update(requestedRewindHeight: BlockHeight) async
28
+ }
29
+
30
+ actor ActionContextImpl: ActionContext {
31
+ var state: CBPState
32
+ var prevState: CBPState?
33
+ var syncControlData: SyncControlData
34
+ var requestedRewindHeight: BlockHeight?
35
+ /// Amount of blocks that have been processed so far
36
+ var processedHeight: BlockHeight = 0
37
+ /// Update chain tip must be called repeatadly, this value stores the previous update and help to decide when to call it again
38
+ var lastChainTipUpdateTime: TimeInterval = 0.0
39
+ var lastScannedHeight: BlockHeight?
40
+ var lastDownloadedHeight: BlockHeight?
41
+ var lastEnhancedHeight: BlockHeight?
42
+
43
+ init(state: CBPState) {
44
+ self.state = state
45
+ syncControlData = SyncControlData.empty
46
+ }
47
+
48
+ func update(state: CBPState) async {
49
+ prevState = self.state
50
+ self.state = state
51
+ }
52
+ func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
53
+ func update(processedHeight: BlockHeight) async { self.processedHeight = processedHeight }
54
+ func update(lastChainTipUpdateTime: TimeInterval) async { self.lastChainTipUpdateTime = lastChainTipUpdateTime }
55
+ func update(lastScannedHeight: BlockHeight) async { self.lastScannedHeight = lastScannedHeight }
56
+ func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
57
+ func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
58
+ func update(requestedRewindHeight: BlockHeight) async { self.requestedRewindHeight = requestedRewindHeight }
59
+ }
60
+
61
+ enum CBPState: CaseIterable {
62
+ case idle
63
+ case migrateLegacyCacheDB
64
+ case validateServer
65
+ case updateSubtreeRoots
66
+ case updateChainTip
67
+ case processSuggestedScanRanges
68
+ case rewind
69
+ case download
70
+ case scan
71
+ case clearAlreadyScannedBlocks
72
+ case enhance
73
+ case fetchUTXO
74
+ case handleSaplingParams
75
+ case clearCache
76
+ case finished
77
+ case failed
78
+ case stopped
79
+ }
80
+
81
+ protocol Action {
82
+ /// If this is true and action fails with error then blocks cache is cleared.
83
+ var removeBlocksCacheWhenFailed: Bool { get }
84
+
85
+ // When any action is created it can get `DIContainer` and resolve any depedencies it requires.
86
+ // Every action uses `context` to get some informartion like download range.
87
+ //
88
+ // `didUpdate` is closure that action use to tell CBP that some part of the work is done. For example if download action would like to
89
+ // update progress on every block downloaded it can use this closure. Also if action doesn't need to update progress on partial work it doesn't
90
+ // need to use this closure at all.
91
+ //
92
+ // Each action updates context accordingly. It should at least set new state. Reason for this is that action can return different states for
93
+ // different conditions. And action is the thing that knows these conditions.
94
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext
95
+
96
+ // Should be called on each existing action when processor wants to stop. Some actions may do it's own background work.
97
+ func stop() async
98
+ }
@@ -0,0 +1,35 @@
1
+ //
2
+ // ClearCacheForLastScannedBatch.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 08.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class ClearAlreadyScannedBlocksAction {
11
+ let storage: CompactBlockRepository
12
+ let transactionRepository: TransactionRepository
13
+
14
+ init(container: DIContainer) {
15
+ storage = container.resolve(CompactBlockRepository.self)
16
+ transactionRepository = container.resolve(TransactionRepository.self)
17
+ }
18
+ }
19
+
20
+ extension ClearAlreadyScannedBlocksAction: Action {
21
+ var removeBlocksCacheWhenFailed: Bool { false }
22
+
23
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
24
+ guard let lastScannedHeight = await context.lastScannedHeight else {
25
+ throw ZcashError.compactBlockProcessorLastScannedHeight
26
+ }
27
+
28
+ try await storage.clear(upTo: lastScannedHeight)
29
+
30
+ await context.update(state: .enhance)
31
+ return context
32
+ }
33
+
34
+ func stop() async { }
35
+ }
@@ -0,0 +1,30 @@
1
+ //
2
+ // ClearCacheAction.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class ClearCacheAction {
11
+ let storage: CompactBlockRepository
12
+
13
+ init(container: DIContainer) {
14
+ storage = container.resolve(CompactBlockRepository.self)
15
+ }
16
+ }
17
+
18
+ extension ClearCacheAction: Action {
19
+ var removeBlocksCacheWhenFailed: Bool { false }
20
+
21
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
22
+ try await storage.clear()
23
+
24
+ await context.update(state: .processSuggestedScanRanges)
25
+
26
+ return context
27
+ }
28
+
29
+ func stop() async { }
30
+ }
@@ -0,0 +1,67 @@
1
+ //
2
+ // DownloadAction.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class DownloadAction {
11
+ let configProvider: CompactBlockProcessor.ConfigProvider
12
+ let downloader: BlockDownloader
13
+ let transactionRepository: TransactionRepository
14
+ let logger: Logger
15
+
16
+ init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
17
+ self.configProvider = configProvider
18
+ downloader = container.resolve(BlockDownloader.self)
19
+ transactionRepository = container.resolve(TransactionRepository.self)
20
+ logger = container.resolve(Logger.self)
21
+ }
22
+
23
+ private func update(context: ActionContext) async -> ActionContext {
24
+ await context.update(state: .scan)
25
+ return context
26
+ }
27
+ }
28
+
29
+ extension DownloadAction: Action {
30
+ var removeBlocksCacheWhenFailed: Bool { true }
31
+
32
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
33
+ guard let lastScannedHeight = await context.lastScannedHeight else {
34
+ return await update(context: context)
35
+ }
36
+
37
+ let config = await configProvider.config
38
+ let latestBlockHeight = await context.syncControlData.latestBlockHeight
39
+ // This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
40
+ // So the right range for this batch must be computed.
41
+ let batchRangeStart = lastScannedHeight
42
+ let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
43
+
44
+ guard batchRangeStart <= batchRangeEnd else {
45
+ return await update(context: context)
46
+ }
47
+
48
+ let batchRange = batchRangeStart...batchRangeEnd
49
+ let potentialDownloadLimit = batchRange.upperBound + (2 * config.batchSize)
50
+ let downloadLimit = await context.syncControlData.latestBlockHeight >= potentialDownloadLimit ? potentialDownloadLimit : batchRangeEnd
51
+
52
+ logger.debug("Starting download with range: \(batchRangeStart)...\(batchRangeEnd)")
53
+
54
+ await downloader.update(latestDownloadedBlockHeight: batchRangeStart, force: true)
55
+ try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
56
+ await downloader.setDownloadLimit(downloadLimit)
57
+ await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
58
+
59
+ try await downloader.waitUntilRequestedBlocksAreDownloaded(in: batchRange)
60
+
61
+ return await update(context: context)
62
+ }
63
+
64
+ func stop() async {
65
+ await downloader.stopDownload()
66
+ }
67
+ }