react-native-zcash 0.8.1 → 0.9.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 (118) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2650000.json +8 -0
  4. package/android/src/main/java/app/edge/rnzcash/RNZcashModule.kt +15 -25
  5. package/ios/RNZcash.m +3 -5
  6. package/ios/RNZcash.swift +42 -23
  7. package/ios/ZCashLightClientKit/Block/Actions/Action.swift +1 -0
  8. package/ios/ZCashLightClientKit/Block/Actions/EnhanceAction.swift +1 -1
  9. package/ios/ZCashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +1 -1
  10. package/ios/ZCashLightClientKit/Block/Actions/ScanAction.swift +1 -1
  11. package/ios/ZCashLightClientKit/Block/Actions/TxResubmissionAction.swift +75 -0
  12. package/ios/ZCashLightClientKit/Block/Actions/UpdateChainTipAction.swift +1 -1
  13. package/ios/ZCashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift +3 -1
  14. package/ios/ZCashLightClientKit/Block/CompactBlockProcessor.swift +14 -0
  15. package/ios/ZCashLightClientKit/Block/Download/BlockDownloaderService.swift +2 -2
  16. package/ios/ZCashLightClientKit/Block/Enhance/BlockEnhancer.swift +54 -49
  17. package/ios/ZCashLightClientKit/Checkpoint/BundleCheckpointSource.swift +1 -6
  18. package/ios/ZCashLightClientKit/Checkpoint/CheckpointSourceFactory.swift +1 -1
  19. package/ios/ZCashLightClientKit/ClosureSynchronizer.swift +3 -1
  20. package/ios/ZCashLightClientKit/CombineSynchronizer.swift +6 -2
  21. package/ios/ZCashLightClientKit/Constants/ZcashSDK.swift +15 -0
  22. package/ios/ZCashLightClientKit/DAO/TransactionDao.swift +20 -4
  23. package/ios/ZCashLightClientKit/Entity/TransactionEntity.swift +27 -24
  24. package/ios/ZCashLightClientKit/Error/Sourcery/generateErrorCode.sh +2 -2
  25. package/ios/ZCashLightClientKit/Error/ZcashError.swift +32 -1
  26. package/ios/ZCashLightClientKit/Error/ZcashErrorCode.swift +11 -1
  27. package/ios/ZCashLightClientKit/Error/ZcashErrorCodeDefinition.swift +18 -0
  28. package/ios/ZCashLightClientKit/Initializer.swift +22 -14
  29. package/ios/ZCashLightClientKit/Metrics/SDKMetrics.swift +0 -1
  30. package/ios/ZCashLightClientKit/Model/FiatCurrencyResult.swift +25 -0
  31. package/ios/ZCashLightClientKit/Model/Proposal.swift +1 -1
  32. package/ios/ZCashLightClientKit/Model/TransactionDataRequest.swift +26 -0
  33. package/ios/ZCashLightClientKit/Model/WalletTypes.swift +39 -1
  34. package/ios/ZCashLightClientKit/Model/Zatoshi.swift +1 -1
  35. package/ios/ZCashLightClientKit/Modules/Service/GRPC/LightWalletGRPCService.swift +39 -2
  36. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto +5 -4
  37. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift +819 -3
  38. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift +2 -2
  39. package/ios/ZCashLightClientKit/Modules/Service/LightWalletService.swift +3 -1
  40. package/ios/ZCashLightClientKit/Providers/ResourceProvider.swift +10 -0
  41. package/ios/ZCashLightClientKit/Repository/TransactionRepository.swift +4 -0
  42. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2475000.json +8 -0
  43. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2477500.json +8 -0
  44. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2482500.json +8 -0
  45. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2485000.json +8 -0
  46. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2487500.json +8 -0
  47. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2492500.json +8 -0
  48. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2495000.json +8 -0
  49. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2497500.json +8 -0
  50. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2502500.json +8 -0
  51. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2505000.json +8 -0
  52. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2507500.json +8 -0
  53. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2512500.json +8 -0
  54. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2515000.json +8 -0
  55. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2517500.json +8 -0
  56. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2522500.json +8 -0
  57. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2525000.json +8 -0
  58. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2527500.json +8 -0
  59. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2532500.json +8 -0
  60. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2535000.json +8 -0
  61. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2537500.json +8 -0
  62. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2542500.json +8 -0
  63. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2545000.json +8 -0
  64. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2547500.json +8 -0
  65. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2552500.json +8 -0
  66. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2555000.json +8 -0
  67. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2557500.json +8 -0
  68. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2562500.json +8 -0
  69. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2565000.json +8 -0
  70. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2567500.json +8 -0
  71. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2572500.json +8 -0
  72. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2575000.json +8 -0
  73. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2577500.json +8 -0
  74. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2582500.json +8 -0
  75. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2585000.json +8 -0
  76. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2587500.json +8 -0
  77. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2592500.json +8 -0
  78. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2595000.json +8 -0
  79. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2597500.json +8 -0
  80. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2602500.json +8 -0
  81. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2605000.json +8 -0
  82. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2607500.json +8 -0
  83. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2612500.json +8 -0
  84. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2615000.json +8 -0
  85. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2617500.json +8 -0
  86. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2622500.json +8 -0
  87. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2625000.json +8 -0
  88. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2627500.json +8 -0
  89. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2632500.json +8 -0
  90. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2635000.json +8 -0
  91. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2637500.json +8 -0
  92. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2642500.json +8 -0
  93. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2645000.json +8 -0
  94. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2647500.json +8 -0
  95. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2650000.json +8 -0
  96. package/ios/ZCashLightClientKit/Rust/ZcashKeyDerivationBackend.swift +4 -24
  97. package/ios/ZCashLightClientKit/Rust/ZcashKeyDerivationBackendWelding.swift +0 -15
  98. package/ios/ZCashLightClientKit/Rust/ZcashRustBackend.swift +133 -32
  99. package/ios/ZCashLightClientKit/Rust/ZcashRustBackendWelding.swift +18 -1
  100. package/ios/ZCashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +4 -0
  101. package/ios/ZCashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +5 -1
  102. package/ios/ZCashLightClientKit/Synchronizer/Dependencies.swift +53 -3
  103. package/ios/ZCashLightClientKit/Synchronizer/SDKSynchronizer.swift +236 -7
  104. package/ios/ZCashLightClientKit/Synchronizer.swift +28 -0
  105. package/ios/ZCashLightClientKit/Tool/DerivationTool.swift +29 -5
  106. package/ios/ZCashLightClientKit/Tor/TorClient.swift +57 -0
  107. package/ios/ZCashLightClientKit/Utils/LoggingProxy.swift +4 -0
  108. package/ios/ZCashLightClientKit/Utils/OSLogger.swift +4 -0
  109. package/ios/libzcashlc.xcframework/ios-arm64/libzcashlc.a +0 -0
  110. package/ios/libzcashlc.xcframework/ios-arm64_x86_64-simulator/libzcashlc.a +0 -0
  111. package/ios/zcashlc.h +287 -42
  112. package/lib/rnzcash.rn.js +8 -8
  113. package/lib/rnzcash.rn.js.map +1 -1
  114. package/lib/src/react-native.d.ts +3 -3
  115. package/lib/src/types.d.ts +4 -2
  116. package/package.json +1 -1
  117. package/src/react-native.ts +13 -18
  118. package/src/types.ts +5 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.9.0 (2024-09-25)
6
+
7
+ - added: Support sending to ZIP-320 TEX addresses
8
+ - changed: Replace `sendToAddress` with `createTransfer`
9
+ - changed: Updated checkpoints
10
+
5
11
  ## 0.8.1 (2024-09-16)
6
12
 
7
13
  - changed: Updated checkpoints
@@ -1,7 +1,7 @@
1
1
  buildscript {
2
2
  def kotlinVersion = rootProject.ext.has('kotlinVersion')
3
3
  ? rootProject.ext.get('kotlinVersion')
4
- : '1.8.22'
4
+ : '1.9.23'
5
5
 
6
6
  repositories {
7
7
  mavenCentral()
@@ -49,8 +49,8 @@ dependencies {
49
49
 
50
50
  implementation 'androidx.appcompat:appcompat:1.6.1'
51
51
  implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
52
- implementation 'cash.z.ecc.android:zcash-android-sdk:2.1.0'
53
- implementation 'cash.z.ecc.android:zcash-android-sdk-incubator:2.1.0'
52
+ implementation 'cash.z.ecc.android:zcash-android-sdk:2.2.4'
53
+ implementation 'cash.z.ecc.android:zcash-android-sdk-incubator:2.2.4'
54
54
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
55
55
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
56
56
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "network": "main",
3
+ "height": "2650000",
4
+ "hash": "0000000000b32debcdb364e5fc7988044a4eb29e4f86ed6c97a95f0836e3c37f",
5
+ "time": 1726602492,
6
+ "saplingTree": "01e09fe8115a458f1802d1d95ad1cbc4d6737d31ebf4a63f936b62011034dabd31010855462f487eaa137ee9950887b40953765357aff4a9eda4d85d4eccf274c42a1f000001488261fba3b34b7f78d50bc11ce1830c2339813606bb17570d7293128769577101216344eb0f3b3b8bddd4d2b51a36f6bb4fa368125d29dc02cf776797152bd95a00011f360200bebb9acb23f27fd0cb18a95b746d3a07c4df8cf7a843147bd0f9042d0000000001b5c2bb61f0ea5b18cfdeddac25f36c98988ce0705eae6824a3121158a6f6f824010d143acd831f6da07b124b16f4c4e58102caa7517722bb550946c5fa2a245371000000019a730d9d524de40f3fa2a76a8072ff66ef8ce0af24a68a080ef683a9d45aab7301b254e3cfba937bcd40621e1d623a943440b083892546f081aab37058fd1b763f000000013e2598f743726006b8de42476ed56a55a75629a7b82e430c4e7c101a69e9b02a011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e0000000000",
7
+ "orchardTree": "016c37f7adb67b940b91f05b0d60e1d98c2d4f67c0a766ae367d42a56302ea0416001f015126bbb64a18d6e518922654683434e6aa3c488e1a4e2b35d5ff4bb4d32c310501e8131c28e898b1247a2e28d1691b0854445aecf30e7bd601117fdd36967c893e01c454a4fac32e3c68f16a55476d7e992d7cec2e4af24bc34d7d9c459f58fbe22100000102b7afec099b149198431e309cec4b3f95531b53fa350ff453c0a648276bc1140001cc846293d335bbb212a6c0f54fd45d92f995724d58b9b5fed397dbc55e3bb8030001f783a7a186e453c6d11e88542ed866790b00d4102899da05c57eb0d71fffbc2500000000010c104faa2b1148b1b627090aa939e321b82edcb0ce70aa4eee298997556b4f35000112278dfeae9949f887b70ae81e084f8897a5054627acef3efd01c8b29793d522000160040850b766b126a2b4843fcdfdffa5d5cab3f53bc860a3bef68958b5f066170001cc2dcaa338b312112db04b435a706d63244dd435238f0aa1e9e1598d35470810012dcc4273c8a0ed2337ecf7879380a07e7d427c7f9d82e538002bd1442978402c01daf63debf5b40df902dae98dadc029f281474d190cddecef1b10653248a234150001e2bca6a8d987d668defba89dc082196a922634ed88e065c669e526bb8815ee1b000000000000"
8
+ }
@@ -20,6 +20,7 @@ import kotlinx.coroutines.Dispatchers
20
20
  import kotlinx.coroutines.async
21
21
  import kotlinx.coroutines.flow.*
22
22
  import kotlinx.coroutines.launch
23
+ import java.util.Base64
23
24
 
24
25
  class RNZcashModule(private val reactContext: ReactApplicationContext) :
25
26
  ReactContextBaseJavaModule(reactContext) {
@@ -203,7 +204,7 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
203
204
  }
204
205
  map.putInt("minedHeight", tx.minedHeight?.value?.toInt() ?: 0)
205
206
  map.putInt("blockTimeInSeconds", tx.blockTimeEpochSeconds?.toInt() ?: 0)
206
- map.putString("rawTransactionId", tx.rawId.byteArray.toHexReversed())
207
+ map.putString("rawTransactionId", tx.txIdString())
207
208
  if (tx.raw != null) {
208
209
  map.putString("raw", tx.raw!!.byteArray.toHex())
209
210
  }
@@ -323,6 +324,7 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
323
324
  val map = Arguments.createMap()
324
325
  map.putInt("transactionCount", proposal.transactionCount())
325
326
  map.putString("totalFee", proposal.totalFeeRequired().value.toString())
327
+ map.putString("proposalBase64", Base64.getEncoder().encodeToString(proposal.toByteArray()))
326
328
  promise.resolve(map)
327
329
  } catch (t: Throwable) {
328
330
  promise.reject("Err", t)
@@ -330,12 +332,11 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
330
332
  }
331
333
  }
332
334
 
335
+ @kotlin.ExperimentalStdlibApi
333
336
  @ReactMethod
334
- fun sendToAddress(
337
+ fun createTransfer(
335
338
  alias: String,
336
- zatoshi: String,
337
- toAddress: String,
338
- memo: String = "",
339
+ proposalBase64: String,
339
340
  seed: String,
340
341
  promise: Promise,
341
342
  ) {
@@ -344,18 +345,15 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
344
345
  try {
345
346
  val seedPhrase = SeedPhrase.new(seed)
346
347
  val usk = DerivationTool.getInstance().deriveUnifiedSpendingKey(seedPhrase.toByteArray(), wallet.network, Account.DEFAULT)
347
- val internalId =
348
- wallet.sendToAddress(
349
- usk,
350
- Zatoshi(zatoshi.toLong()),
351
- toAddress,
352
- memo,
353
- )
354
- val tx = wallet.coroutineScope.async { wallet.transactions.first().first() }.await()
355
- val map = Arguments.createMap()
356
- map.putString("txId", tx.rawId.byteArray.toHexReversed())
357
- if (tx.raw != null) map.putString("raw", tx.raw?.byteArray?.toHex())
358
- promise.resolve(map)
348
+ val proposalByteArray = Base64.getDecoder().decode(proposalBase64)
349
+ val proposal = Proposal.fromByteArray(proposalByteArray)
350
+
351
+ val txs =
352
+ wallet.coroutineScope.async {
353
+ wallet.createProposedTransactions(proposal, usk).take(proposal.transactionCount()).toList()
354
+ }.await()
355
+ val txid = txs[txs.lastIndex].txIdString() // The last transfer is the most relevant to the user
356
+ promise.resolve(txid)
359
357
  } catch (t: Throwable) {
360
358
  promise.reject("Err", t)
361
359
  }
@@ -473,14 +471,6 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
473
471
  .emit(eventName, args)
474
472
  }
475
473
 
476
- private fun ByteArray.toHexReversed(): String {
477
- val sb = StringBuilder(size * 2)
478
- var i = size - 1
479
- while (i >= 0)
480
- sb.append(String.format("%02x", this[i--]))
481
- return sb.toString()
482
- }
483
-
484
474
  data class Balances(
485
475
  val transparentBalance: Zatoshi?,
486
476
  val saplingBalances: WalletBalance?,
package/ios/RNZcash.m CHANGED
@@ -44,11 +44,9 @@ resolver:(RCTPromiseResolveBlock)resolve
44
44
  rejecter:(RCTPromiseRejectBlock)reject
45
45
  )
46
46
 
47
- RCT_EXTERN_METHOD(sendToAddress:(NSString *)alias
48
- :(NSString *)zatoshi
49
- :(NSString *)toAddress
50
- :(NSString *)memo
51
- :(NSString *)spendingKey
47
+ RCT_EXTERN_METHOD(createTransfer:(NSString *)alias
48
+ :(NSString *)proposalBase64
49
+ :(NSString *)seed
52
50
  resolver:(RCTPromiseResolveBlock)resolve
53
51
  rejecter:(RCTPromiseRejectBlock)reject
54
52
  )
package/ios/RNZcash.swift CHANGED
@@ -1,6 +1,7 @@
1
1
  import Combine
2
2
  import Foundation
3
3
  import MnemonicSwift
4
+ import SwiftProtobuf
4
5
  import os
5
6
 
6
7
  var SynchronizerMap = [String: WalletSynchronizer]()
@@ -102,6 +103,7 @@ class RNZcash: RCTEventEmitter {
102
103
  fsBlockDbRoot: try! fsBlockDbRootURLHelper(alias, network),
103
104
  generalStorageURL: try! generalStorageURLHelper(alias, network),
104
105
  dataDbURL: try! dataDbURLHelper(alias, network),
106
+ torDirURL: try! torDirURLHelper(alias, network),
105
107
  endpoint: endpoint,
106
108
  network: network,
107
109
  spendParamsURL: try! spendParamsURLHelper(alias),
@@ -229,7 +231,10 @@ class RNZcash: RCTEventEmitter {
229
231
  memo: sdkMemo
230
232
  )
231
233
 
234
+ let proposalBase64 = try proposal.inner.serializedData().base64EncodedString()
235
+
232
236
  let out: NSMutableDictionary = [
237
+ "proposalBase64": proposalBase64,
233
238
  "transactionCount": proposal.transactionCount(),
234
239
  "totalFee": String(proposal.totalFeeRequired().amount),
235
240
  ]
@@ -248,42 +253,47 @@ class RNZcash: RCTEventEmitter {
248
253
  }
249
254
  }
250
255
 
251
- @objc func sendToAddress(
252
- _ alias: String, _ zatoshi: String, _ toAddress: String, _ memo: String, _ seed: String,
256
+ @objc func createTransfer(
257
+ _ alias: String, _ proposalBase64: String, _ seed: String,
253
258
  resolver resolve: @escaping RCTPromiseResolveBlock,
254
259
  rejecter reject: @escaping RCTPromiseRejectBlock
255
260
  ) {
256
261
  Task {
257
262
  if let wallet = SynchronizerMap[alias] {
258
- let amount = Int64(zatoshi)
259
- if amount == nil {
260
- reject("SpendToAddressError", "Amount is invalid", genericError)
261
- return
262
- }
263
-
264
263
  do {
265
264
  let spendingKey = try deriveUnifiedSpendingKey(seed, wallet.synchronizer.network)
266
- var sdkMemo: Memo? = nil
267
- if memo != "" {
268
- sdkMemo = try Memo(string: memo)
269
- }
270
- let broadcastTx = try await wallet.synchronizer.sendToAddress(
271
- spendingKey: spendingKey,
272
- zatoshi: Zatoshi(amount!),
273
- toAddress: Recipient(toAddress, network: wallet.synchronizer.network.networkType),
274
- memo: sdkMemo
265
+ let data = Data.init(base64Encoded: proposalBase64)!
266
+ let ffiProposal = try FfiProposal(serializedData: data)
267
+ let proposal = Proposal(inner: ffiProposal)
268
+
269
+ let transactions = try await wallet.synchronizer.createProposedTransactions(
270
+ proposal: proposal, spendingKey: spendingKey
275
271
  )
276
272
 
277
- let tx: NSMutableDictionary = ["txId": broadcastTx.rawID.toHexStringTxId()]
278
- if broadcastTx.raw != nil {
279
- tx["raw"] = broadcastTx.raw?.hexEncodedString()
273
+ var lastTxid = "" // The last transfer is the most relevant to the user
274
+ for try await tx in transactions {
275
+ switch tx {
276
+ case .grpcFailure(_, let error):
277
+ throw error
278
+ case .success(let txId):
279
+ lastTxid = txId.toHexStringTxId()
280
+ continue
281
+ case let .submitFailure(txId: _, code: code, description: description):
282
+ throw NSError(
283
+ domain:
284
+ "transaction failed to submit with code: \(code) - description: \(description)",
285
+ code: 0)
286
+ case .notAttempted(_):
287
+ throw NSError(domain: "transaction not attempted", code: 0)
288
+ }
280
289
  }
281
- resolve(tx)
290
+
291
+ resolve(lastTxid)
282
292
  } catch {
283
- reject("SpendToAddressError", "Failed to spend", error)
293
+ reject("createTransfer", "Failed to spend", error)
284
294
  }
285
295
  } else {
286
- reject("SpendToAddressError", "Wallet does not exist", genericError)
296
+ reject("createTransfer", "Wallet does not exist", genericError)
287
297
  }
288
298
  }
289
299
  }
@@ -432,6 +442,7 @@ class RNZcash: RCTEventEmitter {
432
442
  if derivationTool.isValidUnifiedAddress(address)
433
443
  || derivationTool.isValidSaplingAddress(address)
434
444
  || derivationTool.isValidTransparentAddress(address)
445
+ || derivationTool.isValidTexAddress(address)
435
446
  {
436
447
  resolve(true)
437
448
  } else {
@@ -698,6 +709,14 @@ func dataDbURLHelper(_ alias: String, _ network: ZcashNetwork) throws -> URL {
698
709
  )
699
710
  }
700
711
 
712
+ func torDirURLHelper(_ alias: String, _ network: ZcashNetwork) throws -> URL {
713
+ try documentsDirectoryHelper()
714
+ .appendingPathComponent(
715
+ network.constants.defaultDbNamePrefix + alias + ZcashSDK.defaultTorDirName,
716
+ isDirectory: true
717
+ )
718
+ }
719
+
701
720
  func spendParamsURLHelper(_ alias: String) throws -> URL {
702
721
  try documentsDirectoryHelper().appendingPathComponent(alias + "sapling-spend.params")
703
722
  }
@@ -73,6 +73,7 @@ enum CBPState: CaseIterable {
73
73
  case fetchUTXO
74
74
  case handleSaplingParams
75
75
  case clearCache
76
+ case txResubmission
76
77
  case finished
77
78
  case failed
78
79
  case stopped
@@ -28,7 +28,7 @@ final class EnhanceAction {
28
28
  if lastScannedHeight >= latestBlockHeight {
29
29
  await context.update(state: .clearCache)
30
30
  } else {
31
- await context.update(state: .updateChainTip)
31
+ await context.update(state: .txResubmission)
32
32
  }
33
33
 
34
34
  return context
@@ -59,7 +59,7 @@ extension ProcessSuggestedScanRangesAction: Action {
59
59
 
60
60
  await context.update(state: .download)
61
61
  } else {
62
- await context.update(state: .finished)
62
+ await context.update(state: .txResubmission)
63
63
  }
64
64
 
65
65
  return context
@@ -98,7 +98,7 @@ extension ScanAction: Action {
98
98
  return await update(context: context)
99
99
  }
100
100
 
101
- func stop() async {
101
+ func stop() async {
102
102
  progressReportReducer = 0
103
103
  }
104
104
  }
@@ -0,0 +1,75 @@
1
+ //
2
+ // TxResubmissionAction.swift
3
+ //
4
+ //
5
+ // Created by Lukas Korba on 06-17-2024.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class TxResubmissionAction {
11
+ private enum Constants {
12
+ static let thresholdToTrigger = TimeInterval(300.0)
13
+ }
14
+
15
+ var latestResolvedTime: TimeInterval = 0
16
+ let transactionRepository: TransactionRepository
17
+ let transactionEncoder: TransactionEncoder
18
+ let logger: Logger
19
+
20
+ init(container: DIContainer) {
21
+ transactionRepository = container.resolve(TransactionRepository.self)
22
+ transactionEncoder = container.resolve(TransactionEncoder.self)
23
+ logger = container.resolve(Logger.self)
24
+ }
25
+ }
26
+
27
+ extension TxResubmissionAction: Action {
28
+ var removeBlocksCacheWhenFailed: Bool { true }
29
+
30
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
31
+ let latestBlockHeight = await context.syncControlData.latestBlockHeight
32
+
33
+ // find all candidates for the resubmission
34
+ do {
35
+ logger.info("TxResubmissionAction check started at \(latestBlockHeight) height.")
36
+ let transactions = try await transactionRepository.findForResubmission(upTo: latestBlockHeight)
37
+
38
+ // no candidates, update the time and continue with the next action
39
+ if transactions.isEmpty {
40
+ latestResolvedTime = Date().timeIntervalSince1970
41
+ } else {
42
+ let now = Date().timeIntervalSince1970
43
+ let diff = now - latestResolvedTime
44
+
45
+ // the last time resubmission was triggered is more than 5 minutes ago so try again
46
+ if diff > Constants.thresholdToTrigger {
47
+ // resubmission
48
+ do {
49
+ for transaction in transactions {
50
+ logger.info("TxResubmissionAction trying to resubmit transaction \(transaction.rawID.toHexStringTxId()).")
51
+ let encodedTransaction = try transaction.encodedTransaction()
52
+
53
+ try await transactionEncoder.submit(transaction: encodedTransaction)
54
+ }
55
+ } catch {
56
+ logger.error("TxResubmissionAction failed to resubmit candidates.")
57
+ }
58
+
59
+ latestResolvedTime = Date().timeIntervalSince1970
60
+ }
61
+ }
62
+ } catch {
63
+ logger.error("TxResubmissionAction failed to find candidates.")
64
+ }
65
+
66
+ if await context.prevState == .enhance {
67
+ await context.update(state: .updateChainTip)
68
+ } else {
69
+ await context.update(state: .finished)
70
+ }
71
+ return context
72
+ }
73
+
74
+ func stop() async { }
75
+ }
@@ -44,7 +44,7 @@ extension UpdateChainTipAction: Action {
44
44
  await downloader.stopDownload()
45
45
  try await updateChainTip(context, time: now)
46
46
  await context.update(state: .clearCache)
47
- } else if await context.prevState == .enhance {
47
+ } else if await context.prevState == .txResubmission {
48
48
  await context.update(state: .download)
49
49
  }
50
50
 
@@ -30,7 +30,7 @@ extension UpdateSubtreeRootsAction: Action {
30
30
 
31
31
  logger.debug("Attempt to get subtree roots, this may fail because lightwalletd may not support Spend before Sync.")
32
32
  let stream = service.getSubtreeRoots(request)
33
-
33
+
34
34
  var saplingRoots: [SubtreeRoot] = []
35
35
 
36
36
  do {
@@ -39,6 +39,8 @@ extension UpdateSubtreeRootsAction: Action {
39
39
  }
40
40
  } catch ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut) {
41
41
  throw ZcashError.serviceSubtreeRootsStreamFailed(LightWalletServiceError.timeOut)
42
+ } catch {
43
+ await context.update(state: .updateChainTip)
42
44
  }
43
45
 
44
46
  logger.debug("Sapling tree has \(saplingRoots.count) subtrees")
@@ -56,6 +56,7 @@ actor CompactBlockProcessor {
56
56
  let saplingParamsSourceURL: SaplingParamsSourceURL
57
57
  let fsBlockCacheRoot: URL
58
58
  let dataDb: URL
59
+ let torDir: URL
59
60
  let spendParamsURL: URL
60
61
  let outputParamsURL: URL
61
62
  let enhanceBatchSize: Int
@@ -79,6 +80,7 @@ actor CompactBlockProcessor {
79
80
  cacheDbURL: URL? = nil,
80
81
  fsBlockCacheRoot: URL,
81
82
  dataDb: URL,
83
+ torDir: URL,
82
84
  spendParamsURL: URL,
83
85
  outputParamsURL: URL,
84
86
  saplingParamsSourceURL: SaplingParamsSourceURL,
@@ -94,6 +96,7 @@ actor CompactBlockProcessor {
94
96
  self.alias = alias
95
97
  self.fsBlockCacheRoot = fsBlockCacheRoot
96
98
  self.dataDb = dataDb
99
+ self.torDir = torDir
97
100
  self.spendParamsURL = spendParamsURL
98
101
  self.outputParamsURL = outputParamsURL
99
102
  self.saplingParamsSourceURL = saplingParamsSourceURL
@@ -112,6 +115,7 @@ actor CompactBlockProcessor {
112
115
  alias: ZcashSynchronizerAlias,
113
116
  fsBlockCacheRoot: URL,
114
117
  dataDb: URL,
118
+ torDir: URL,
115
119
  spendParamsURL: URL,
116
120
  outputParamsURL: URL,
117
121
  saplingParamsSourceURL: SaplingParamsSourceURL,
@@ -126,6 +130,7 @@ actor CompactBlockProcessor {
126
130
  self.alias = alias
127
131
  self.fsBlockCacheRoot = fsBlockCacheRoot
128
132
  self.dataDb = dataDb
133
+ self.torDir = torDir
129
134
  self.spendParamsURL = spendParamsURL
130
135
  self.outputParamsURL = outputParamsURL
131
136
  self.saplingParamsSourceURL = saplingParamsSourceURL
@@ -151,6 +156,7 @@ actor CompactBlockProcessor {
151
156
  alias: initializer.alias,
152
157
  fsBlockCacheRoot: initializer.fsBlockDbRoot,
153
158
  dataDb: initializer.dataDbURL,
159
+ torDir: initializer.torDirURL,
154
160
  spendParamsURL: initializer.spendParamsURL,
155
161
  outputParamsURL: initializer.outputParamsURL,
156
162
  saplingParamsSourceURL: initializer.saplingParamsSourceURL,
@@ -228,6 +234,8 @@ actor CompactBlockProcessor {
228
234
  action = SaplingParamsAction(container: container)
229
235
  case .clearCache:
230
236
  action = ClearCacheAction(container: container)
237
+ case .txResubmission:
238
+ action = TxResubmissionAction(container: container)
231
239
  case .finished, .failed, .stopped, .idle:
232
240
  return nil
233
241
  }
@@ -284,6 +292,10 @@ extension CompactBlockProcessor {
284
292
  func latestHeight() async throws -> BlockHeight {
285
293
  try await blockDownloaderService.latestBlockHeight()
286
294
  }
295
+
296
+ func consensusBranchIdFor(_ height: Int32) -> Int32? {
297
+ try? rustBackend.consensusBranchIdFor(height: height)
298
+ }
287
299
  }
288
300
 
289
301
  // MARK: - Rewind
@@ -667,6 +679,8 @@ extension CompactBlockProcessor {
667
679
  break
668
680
  case .stopped:
669
681
  break
682
+ case .txResubmission:
683
+ break
670
684
  }
671
685
  }
672
686
 
@@ -43,7 +43,7 @@ protocol BlockDownloaderService {
43
43
  Gets the transaction for the Id given
44
44
  - Parameter txId: Data representing the transaction Id
45
45
  */
46
- func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched
46
+ func fetchTransaction(txId: Data) async throws -> (tx: ZcashTransaction.Fetched?, status: TransactionStatus)
47
47
 
48
48
  func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
49
49
 
@@ -111,7 +111,7 @@ extension BlockDownloaderServiceImpl: BlockDownloaderService {
111
111
  try await self.storage.latestHeight()
112
112
  }
113
113
 
114
- func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched {
114
+ func fetchTransaction(txId: Data) async throws -> (tx: ZcashTransaction.Fetched?, status: TransactionStatus) {
115
115
  try await lightwalletService.fetchTransaction(txId: txId)
116
116
  }
117
117
  }
@@ -60,24 +60,8 @@ struct BlockEnhancerImpl {
60
60
  let rustBackend: ZcashRustBackendWelding
61
61
  let transactionRepository: TransactionRepository
62
62
  let metrics: SDKMetrics
63
+ let service: LightWalletService
63
64
  let logger: Logger
64
-
65
- private func enhance(transaction: ZcashTransaction.Overview) async throws -> ZcashTransaction.Overview {
66
- logger.debug("Zoom.... Enhance... Tx: \(transaction.rawID.toHexStringTxId())")
67
-
68
- let fetchedTransaction = try await blockDownloaderService.fetchTransaction(txId: transaction.rawID)
69
-
70
- let transactionID = fetchedTransaction.rawID.toHexStringTxId()
71
- let block = String(describing: transaction.minedHeight)
72
- logger.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
73
-
74
- try await rustBackend.decryptAndStoreTransaction(
75
- txBytes: fetchedTransaction.raw.bytes,
76
- minedHeight: Int32(fetchedTransaction.minedHeight)
77
- )
78
-
79
- return try await transactionRepository.find(rawID: fetchedTransaction.rawID)
80
- }
81
65
  }
82
66
 
83
67
  extension BlockEnhancerImpl: BlockEnhancer {
@@ -91,54 +75,75 @@ extension BlockEnhancerImpl: BlockEnhancer {
91
75
 
92
76
  // fetch transactions
93
77
  do {
94
- let startTime = Date()
95
- let transactions = try await transactionRepository.find(in: range, limit: Int.max, kind: .all)
78
+ let transactionDataRequests = try await rustBackend.transactionDataRequests()
96
79
 
97
- guard !transactions.isEmpty else {
98
- logger.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
99
- logger.sync("No transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
80
+ guard !transactionDataRequests.isEmpty else {
81
+ logger.debug("No transaction data requests detected.")
82
+ logger.sync("No transaction data requests detected.")
100
83
  return nil
101
84
  }
102
85
 
103
- let chainTipHeight = try await blockDownloaderService.latestBlockHeight()
104
-
105
- let newlyMinedLowerBound = chainTipHeight - ZcashSDK.expiryOffset
106
-
107
- let newlyMinedRange = newlyMinedLowerBound...chainTipHeight
108
-
109
- for index in 0 ..< transactions.count {
110
- let transaction = transactions[index]
86
+ for index in 0 ..< transactionDataRequests.count {
87
+ let transactionDataRequest = transactionDataRequests[index]
111
88
  var retry = true
112
-
89
+
113
90
  while retry && retries < maxRetries {
114
91
  try Task.checkCancellation()
115
92
  do {
116
- let confirmedTx = try await enhance(transaction: transaction)
117
- retry = false
118
-
119
- let progress = EnhancementProgress(
120
- totalTransactions: transactions.count,
121
- enhancedTransactions: index + 1,
122
- lastFoundTransaction: confirmedTx,
123
- range: range,
124
- newlyMined: confirmedTx.isSentTransaction && newlyMinedRange.contains(confirmedTx.minedHeight ?? 0)
125
- )
126
-
127
- await didEnhance(progress)
93
+ switch transactionDataRequest {
94
+ case .getStatus(let txId):
95
+ let response = try await blockDownloaderService.fetchTransaction(txId: txId.data)
96
+ retry = false
97
+
98
+ if let fetchedTransaction = response.tx {
99
+ try await rustBackend.setTransactionStatus(txId: fetchedTransaction.rawID, status: response.status)
100
+ }
101
+
102
+ case .enhancement(let txId):
103
+ let response = try await blockDownloaderService.fetchTransaction(txId: txId.data)
104
+ retry = false
105
+
106
+ if response.status == .txidNotRecognized {
107
+ try await rustBackend.setTransactionStatus(txId: txId.data, status: .txidNotRecognized)
108
+ } else if let fetchedTransaction = response.tx {
109
+ try await rustBackend.decryptAndStoreTransaction(
110
+ txBytes: fetchedTransaction.raw.bytes,
111
+ minedHeight: fetchedTransaction.minedHeight
112
+ )
113
+ }
114
+
115
+ case .spendsFromAddress(let sfa):
116
+ guard let blockRangeEnd = sfa.blockRangeEnd else {
117
+ logger.error("spendsFromAddress \(sfa) is missing blockRangeEnd, ignoring the request.")
118
+ continue
119
+ }
120
+
121
+ var filter = TransparentAddressBlockFilter()
122
+ filter.address = sfa.address
123
+ filter.range = BlockRange(startHeight: Int(sfa.blockRangeStart), endHeight: Int(blockRangeEnd - 1))
124
+
125
+ let stream = service.getTaddressTxids(filter)
126
+
127
+ for try await rawTransaction in stream {
128
+ let minedHeight = (rawTransaction.height == 0 || rawTransaction.height > UInt32.max)
129
+ ? nil : UInt32(rawTransaction.height)
130
+
131
+ try await rustBackend.decryptAndStoreTransaction(
132
+ txBytes: rawTransaction.data.bytes,
133
+ minedHeight: minedHeight
134
+ )
135
+ }
136
+ retry = false
137
+ }
128
138
  } catch {
129
139
  retries += 1
130
- logger.error("could not enhance txId \(transaction.rawID.toHexStringTxId()) - Error: \(error)")
140
+ logger.error("could not enhance transactionDataRequest \(transactionDataRequest) - Error: \(error)")
131
141
  if retries > maxRetries {
132
142
  throw error
133
143
  }
134
144
  }
135
145
  }
136
146
  }
137
-
138
- let endTime = Date()
139
- let logMsg = "Enhanced \(transactions.count) transaction(s) in \(endTime.timeIntervalSince1970 - startTime.timeIntervalSince1970) for range \(range.lowerBound)...\(range.upperBound)"
140
- logger.sync(logMsg)
141
- metrics.actionDetail(logMsg, for: .enhance)
142
147
  } catch {
143
148
  logger.error("error enhancing transactions! \(error)")
144
149
  throw error
@@ -14,12 +14,7 @@ struct BundleCheckpointSource: CheckpointSource {
14
14
 
15
15
  init(network: NetworkType) {
16
16
  self.network = network
17
- self.saplingActivation = switch network {
18
- case .mainnet:
19
- Checkpoint.mainnetMin
20
- case .testnet:
21
- Checkpoint.testnetMin
22
- }
17
+ self.saplingActivation = if network == .mainnet { Checkpoint.mainnetMin } else { Checkpoint.testnetMin }
23
18
  }
24
19
 
25
20
  func latestKnownCheckpoint() -> Checkpoint {
@@ -7,7 +7,7 @@
7
7
 
8
8
  import Foundation
9
9
 
10
- struct CheckpointSourceFactory {
10
+ enum CheckpointSourceFactory {
11
11
  static func fromBundle(for network: NetworkType) -> CheckpointSource {
12
12
  BundleCheckpointSource(network: network)
13
13
  }