react-native-zcash 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # React Native Zcash
2
2
 
3
+ ## 0.2.2 (2022-06-10)
4
+
5
+ - Upgrade SDKs to NU5 compatible versions
6
+ - Android: Upgrade zcash-android-sdk to v1.5.0-beta01
7
+ - iOS: Upgrade ZcashLightClientKit to v0.14.0-beta
8
+ - iOS: Fix memory leak after stopping synchronizer
9
+ - ANdroid: White space and import cleanups
10
+
11
+ ## 0.2.1 (2022-03-16)
12
+
13
+ - Update the ZcashLightClientKit dependency
14
+ - Remove unused build scripts
15
+
16
+ ## 0.2.0 (2022-01-10)
17
+
18
+ - Add iOS support
19
+ - Android: Cleanup unused methods
20
+
3
21
  ## 0.1.0 (2021-11-09)
4
22
 
5
23
  - Initial release
@@ -1,7 +1,7 @@
1
1
  buildscript {
2
2
  ext.versions = [
3
3
  'kotlin': '1.5.0',
4
- 'zcash': '1.3.0-beta18',
4
+ 'zcash': '1.5.0-beta01',
5
5
  'room': '2.3.0'
6
6
  ]
7
7
  repositories {
@@ -33,7 +33,7 @@ def safeExtGet(prop, fallback) {
33
33
 
34
34
  def DEFAULT_COMPILE_SDK_VERSION = 28
35
35
  def DEFAULT_BUILD_TOOLS_VERSION = "28.0.2"
36
- def DEFAULT_MIN_SDK_VERSION = 16
36
+ def DEFAULT_MIN_SDK_VERSION = 19
37
37
  def DEFAULT_TARGET_SDK_VERSION = 27
38
38
 
39
39
  android {
@@ -80,4 +80,10 @@ dependencies {
80
80
  implementation "androidx.paging:paging-runtime-ktx:2.1.2"
81
81
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
82
82
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
83
+
84
+ implementation ("androidx.appcompat:appcompat:1.3.1") {
85
+ version {
86
+ strictly '1.3.1'
87
+ }
88
+ }
83
89
  }
@@ -1,32 +1,29 @@
1
1
  package app.edge.rnzcash;
2
2
 
3
- import androidx.paging.PagedList
4
3
  import cash.z.ecc.android.sdk.Initializer
5
4
  import cash.z.ecc.android.sdk.SdkSynchronizer
6
5
  import cash.z.ecc.android.sdk.Synchronizer
7
- import cash.z.ecc.android.sdk.Synchronizer.Status.SYNCED
8
- import cash.z.ecc.android.sdk.block.CompactBlockProcessor
9
6
  import cash.z.ecc.android.sdk.db.entity.*
10
7
  import cash.z.ecc.android.sdk.ext.*
11
- import cash.z.ecc.android.sdk.transaction.*
8
+ import cash.z.ecc.android.sdk.internal.transaction.PagedTransactionRepository
9
+ import cash.z.ecc.android.sdk.internal.*
12
10
  import cash.z.ecc.android.sdk.type.*
13
11
  import cash.z.ecc.android.sdk.tool.DerivationTool
14
12
  import com.facebook.react.bridge.*
15
13
  import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
16
14
  import kotlinx.coroutines.CoroutineScope
17
- import kotlinx.coroutines.cancel
18
15
  import kotlinx.coroutines.flow.distinctUntilChanged
19
- import kotlinx.coroutines.flow.filter
20
16
  import kotlinx.coroutines.launch
17
+ import kotlinx.coroutines.runBlocking
21
18
  import java.nio.charset.StandardCharsets
22
19
  import kotlin.coroutines.EmptyCoroutineContext
23
20
 
24
21
  class WalletSynchronizer constructor(val initializer: Initializer) {
25
-
26
- val synchronizer: SdkSynchronizer = Synchronizer(
22
+
23
+ val synchronizer: SdkSynchronizer = Synchronizer.newBlocking(
27
24
  initializer
28
25
  ) as SdkSynchronizer
29
- val repository = PagedTransactionRepository(initializer.context, 10, initializer.rustBackend, initializer.birthday, initializer.viewingKeys)
26
+ val repository = runBlocking { PagedTransactionRepository.new(initializer.context, 10, initializer.rustBackend, initializer.birthday, initializer.viewingKeys) }
30
27
  var isStarted = false
31
28
  }
32
29
 
@@ -50,42 +47,44 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
50
47
  @ReactMethod
51
48
  fun initialize(extfvk: String, extpub: String, birthdayHeight: Int, alias: String, networkName: String = "mainnet", defaultHost: String = "mainnet.lightwalletd.com", defaultPort: Int = 9067, promise: Promise) =
52
49
  promise.wrap {
53
- Twig.plant(TroubleshootingTwig())
54
- var vk = UnifiedViewingKey(extfvk, extpub)
55
- if (synchronizerMap[alias] == null) {
56
- val initializer = Initializer(reactApplicationContext) { config ->
57
- config.importedWalletBirthday(birthdayHeight)
58
- config.setViewingKeys(vk)
59
- config.setNetwork(networks[networkName] ?: ZcashNetwork.Mainnet, defaultHost, defaultPort)
60
- config.alias = alias
61
- }
62
- synchronizerMap[alias] = WalletSynchronizer(initializer)
63
- val wallet = getWallet(alias)
50
+ Twig.plant(TroubleshootingTwig())
51
+ var vk = UnifiedViewingKey(extfvk, extpub)
52
+ if (synchronizerMap[alias] == null) {
53
+ runBlocking {
54
+ Initializer.new(reactApplicationContext) {
55
+ it.importedWalletBirthday(birthdayHeight)
56
+ it.setViewingKeys(vk)
57
+ it.setNetwork(networks[networkName]
58
+ ?: ZcashNetwork.Mainnet, defaultHost, defaultPort)
59
+ it.alias = alias
60
+ }
61
+ }.let { initializer ->
62
+ synchronizerMap[alias] = WalletSynchronizer(initializer)
64
63
  }
65
- val wallet = getWallet(alias)
66
- wallet.synchronizer.hashCode().toString()
67
-
64
+ }
65
+ val wallet = getWallet(alias)
66
+ wallet.synchronizer.hashCode().toString()
68
67
  }
69
68
 
70
69
  @ReactMethod
71
70
  fun start(alias: String, promise: Promise) = promise.wrap {
72
71
  val wallet = getWallet(alias)
73
72
  if (!wallet.isStarted) {
74
- wallet.synchronizer.prepare()
73
+ runBlocking {
74
+ wallet.synchronizer.prepare()
75
+ }
75
76
  wallet.synchronizer.start(moduleScope)
76
77
  val scope = wallet.synchronizer.coroutineScope
77
78
  wallet.synchronizer.processorInfo.collectWith(scope, { update ->
78
79
  sendEvent("UpdateEvent") { args ->
79
80
  args.putString("alias", alias)
80
- args.putBoolean("isDownloading", update.isDownloading)
81
- args.putBoolean("isScanning", update.isScanning)
82
81
  args.putInt("lastDownloadedHeight", update.lastDownloadedHeight)
83
82
  args.putInt("lastScannedHeight", update.lastScannedHeight)
84
83
  args.putInt("scanProgress", update.scanProgress)
85
84
  args.putInt("networkBlockHeight", update.networkBlockHeight)
86
85
  }
87
86
  })
88
- wallet.synchronizer.status.collectWith(scope, { status ->
87
+ wallet.synchronizer.status.collectWith(scope, { status ->
89
88
  sendEvent("StatusEvent") { args ->
90
89
  args.putString("alias", alias)
91
90
  args.putString("name", status.toString())
@@ -106,7 +105,6 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
106
105
  args.putInt("transactionCount", txList.count())
107
106
  }
108
107
  })
109
- wallet.repository.prepare()
110
108
  wallet.isStarted = true
111
109
  }
112
110
  "success"
@@ -152,15 +150,9 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
152
150
  }
153
151
  }
154
152
 
155
- @ReactMethod
156
- fun getBlockCount(
157
- promise: Promise
158
- ) = promise.wrap {
159
- }
160
-
161
153
  @ReactMethod
162
154
  fun deriveViewingKey(seedBytesHex: String, network: String = "mainnet", promise: Promise) {
163
- var keys = DerivationTool.deriveUnifiedViewingKeys(seedBytesHex.fromHex(), networks.getOrDefault(network, ZcashNetwork.Mainnet))[0]
155
+ var keys = runBlocking { DerivationTool.deriveUnifiedViewingKeys(seedBytesHex.fromHex(), networks.getOrDefault(network, ZcashNetwork.Mainnet))[0] }
164
156
  val map = Arguments.createMap()
165
157
  map.putString("extfvk", keys.extfvk)
166
158
  map.putString("extpub", keys.extpub)
@@ -169,7 +161,7 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
169
161
 
170
162
  @ReactMethod
171
163
  fun deriveSpendingKey(seedBytesHex: String, network: String = "mainnet", promise: Promise) = promise.wrap {
172
- DerivationTool.deriveSpendingKeys(seedBytesHex.fromHex(), networks.getOrDefault(network, ZcashNetwork.Mainnet))[0]
164
+ runBlocking { DerivationTool.deriveSpendingKeys(seedBytesHex.fromHex(), networks.getOrDefault(network, ZcashNetwork.Mainnet))[0] }
173
165
  }
174
166
 
175
167
  //
@@ -183,24 +175,6 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
183
175
  wallet.synchronizer.latestHeight
184
176
  }
185
177
 
186
- @ReactMethod
187
- fun getLatestScannedHeight(promise: Promise) = promise.wrap {
188
- // TODO: implement this after switching to StateFlow objects
189
- throw NotImplementedError()
190
- }
191
-
192
- @ReactMethod
193
- fun getProgress(promise: Promise) = promise.wrap {
194
- // TODO: implement this after switching to StateFlow objects
195
- throw NotImplementedError()
196
- }
197
-
198
- @ReactMethod
199
- fun getStatus(promise: Promise) = promise.wrap {
200
- // TODO: implement this after switching to StateFlow objects
201
- throw NotImplementedError()
202
- }
203
-
204
178
  @ReactMethod
205
179
  fun getShieldedBalance(alias: String, promise: Promise) = promise.wrap {
206
180
  val wallet = getWallet(alias)
@@ -247,15 +221,12 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
247
221
  if (tx.errorCode != null) map.putString("errorCode", tx.errorCode.toString())
248
222
  promise.resolve(false)
249
223
  }
250
- sendEvent("PendingTransactionUpdated") { args ->
251
- args.putPendingTransaction(tx)
252
- }
253
224
  }
254
225
  } catch (t: Throwable) {
255
226
  promise.reject("Err", t)
256
227
  }
257
228
  }
258
-
229
+
259
230
  }
260
231
 
261
232
  //
@@ -264,82 +235,56 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
264
235
 
265
236
  @ReactMethod
266
237
  fun deriveShieldedAddress(viewingKey: String, network: String = "mainnet", promise: Promise) = promise.wrap {
267
- DerivationTool.deriveShieldedAddress(viewingKey, networks.getOrDefault(network, ZcashNetwork.Mainnet))
238
+ runBlocking { DerivationTool.deriveShieldedAddress(viewingKey, networks.getOrDefault(network, ZcashNetwork.Mainnet)) }
268
239
  }
269
240
 
270
241
  @ReactMethod
271
- fun deriveTransparentAddress(seed: String, network: String = "mainnet", promise: Promise) = promise.wrap {
272
- DerivationTool.deriveTransparentAddress(seed.fromHex(), networks.getOrDefault(network, ZcashNetwork.Mainnet))
273
- }
274
-
275
- @ReactMethod
276
- fun isValidShieldedAddress(address: String, promise: Promise) {
242
+ fun isValidShieldedAddress(address: String, network: String, promise: Promise) {
277
243
  moduleScope.launch {
278
244
  promise.wrap {
279
- val wallet = getAnyWallet()
280
- wallet.synchronizer.isValidShieldedAddr(address)
245
+ var isValid = false
246
+ val wallets = synchronizerMap.asIterable()
247
+ for (wallet in wallets) {
248
+ if (wallet.value.synchronizer.network.networkName == network) {
249
+ isValid = wallet.value.synchronizer.isValidShieldedAddr(address)
250
+ break
251
+ }
252
+ }
253
+ isValid
281
254
  }
282
255
  }
283
256
  }
284
257
 
285
258
  @ReactMethod
286
- fun isValidTransparentAddress(address: String, promise: Promise) {
259
+ fun isValidTransparentAddress(address: String, network: String, promise: Promise) {
287
260
  moduleScope.launch {
288
261
  promise.wrap {
289
- val wallet = getAnyWallet()
290
- wallet.synchronizer.isValidTransparentAddr(address)
291
- }
292
- }
293
- }
294
-
295
- override fun onCatalystInstanceDestroy() {
296
- super.onCatalystInstanceDestroy()
297
- try {
298
- // cancelling the parent scope will also stop the synchronizer, through structured concurrency
299
- // so calling synchronizer.stop() here is possible but redundant
300
- moduleScope.cancel()
301
- } catch (t: Throwable) {
302
- // ignore
303
- }
304
- }
305
-
306
-
307
- //
308
- // Test functions (remove these, later)
309
- //
310
-
311
- @ReactMethod
312
- fun readyToSend(alias: String, promise: Promise) = promise.wrap {
313
- val wallet = getWallet(alias)
314
- // for testing purposes, one is enough--we just want to make sure we're not downloading
315
- wallet.synchronizer.status.filter { it == SYNCED }.onFirstWith(wallet.synchronizer.coroutineScope) {
316
- true
262
+ var isValid = false
263
+ val wallets = synchronizerMap.asIterable()
264
+ for (wallet in wallets) {
265
+ if (wallet.value.synchronizer.network.networkName == network) {
266
+ isValid = wallet.value.synchronizer.isValidTransparentAddr(address)
267
+ break
268
+ }
269
+ }
270
+ isValid
271
+ }
317
272
  }
318
273
  }
319
274
 
320
-
321
275
  //
322
276
  // Utilities
323
277
  //
324
278
 
325
279
  /**
326
280
  * Retrieve wallet object from synchronizer map
327
- */
281
+ */
328
282
  private fun getWallet(alias: String): WalletSynchronizer {
329
283
  val wallet = synchronizerMap.get(alias)
330
284
  if (wallet == null) throw Exception("Wallet not found")
331
285
  return wallet
332
286
  }
333
-
334
-
335
- /**
336
- * Retrieve any wallet object for tasks that need simple synchronizer
337
- * functions like address validation
338
- */
339
- private fun getAnyWallet(): WalletSynchronizer {
340
- val wallet = synchronizerMap.firstNotNullOf { it.takeIf { it != null } }
341
- return wallet.value
342
- }
287
+
343
288
 
344
289
  /**
345
290
  * Wrap the given block of logic in a promise, rejecting for any error.
@@ -352,21 +297,6 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
352
297
  }
353
298
  }
354
299
 
355
- /**
356
- * Serialize a pending tranaction to a map as an event
357
- */
358
- private fun WritableMap.putPendingTransaction(tx: PendingTransaction) {
359
- sendEvent("PendingTransactionUpdated") { args ->
360
- tx.let { info ->
361
- if (tx.accountIndex != null ) putInt("accountIndex", tx.accountIndex)
362
- if (tx.expiryHeight != null ) putInt("expiryHeight", tx.expiryHeight)
363
- if (tx.submitAttempts != null ) putInt("submitAttempts", tx.submitAttempts)
364
- if (tx.errorMessage != null ) putString("errorMessage", tx.errorMessage)
365
- if (tx.createTime != null ) putString("PendingTransactionUpdated", tx.createTime.toString())
366
- }
367
- }
368
- }
369
-
370
300
  private fun sendEvent(eventName: String, putArgs: (WritableMap) -> Unit) {
371
301
  val args = Arguments.createMap()
372
302
  putArgs(args)
@@ -384,4 +314,12 @@ class RNZcashModule(private val reactContext: ReactApplicationContext) :
384
314
  "Unable to parse memo."
385
315
  }
386
316
  }
317
+
318
+ inline fun ByteArray.toHexReversed(): String {
319
+ val sb = StringBuilder(size * 2)
320
+ var i = size - 1
321
+ while (i >= 0)
322
+ sb.append(String.format("%02x", this[i--]))
323
+ return sb.toString()
324
+ }
387
325
  }
@@ -0,0 +1,6 @@
1
+ //
2
+ // Use this file to import your target's public headers that you would like to expose to Swift.
3
+ //
4
+
5
+ #import <React/RCTBridgeModule.h>
6
+ #import <React/RCTEventEmitter.h>
package/ios/RNZcash.m CHANGED
@@ -1,45 +1,91 @@
1
- // Formatted using clang-format with default settings.
2
-
3
- #import "RNZcash.h"
4
-
5
- @implementation RNZcash
6
-
7
- RCT_EXPORT_MODULE()
8
-
9
- // - (id)init {
10
- // self = [super init];
11
- // return self;
12
- // }
13
-
14
- + (BOOL)requiresMainQueueSetup {
15
- return NO;
16
- }
17
-
18
- RCT_REMAP_METHOD(getNumTransactions, getNumTransactions:(float) N
19
- resolver:(RCTPromiseResolveBlock)resolve
20
- rejecter:(RCTPromiseRejectBlock)reject)
21
- {
22
- // NSUInteger const onehundred = 100;
23
- // NSNumber *val = [NSNumber numberWithInteger:(N+42)];
24
- NSNumber *myNum = [NSNumber numberWithFloat:(N+123)];
25
- resolve(myNum);
26
- }
27
-
28
- RCT_REMAP_METHOD(deriveViewKey, deriveViewKey:(NSString *) seedBytesHex
29
- resolver:(RCTPromiseResolveBlock)resolve
30
- rejecter:(RCTPromiseRejectBlock)reject)
31
- {
32
- NSString *out = [seedBytesHex stringByAppendingString:@"-viewKey"];
33
- resolve(out);
34
- }
35
-
36
-
37
- RCT_REMAP_METHOD(getShieldedBalance,
38
- resolver:(RCTPromiseResolveBlock)resolve
39
- rejecter:(RCTPromiseRejectBlock)reject)
40
- {
41
- NSDictionary *dict = @{@"availableBalance":@"123",@"totalBalance": @"1234"};
42
- resolve(dict);
43
- }
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+
5
+ @interface RCT_EXTERN_MODULE(RNZcash, RCTEventEmitter<RCTBridgeModule>)
6
+
7
+ // Synchronizer
8
+ RCT_EXTERN_METHOD(initialize:(NSString *)extfvk
9
+ :(NSString *)extpub
10
+ :(NSInteger *)birthdayHeight
11
+ :(NSString *)alias
12
+ :(NSString *)networkName
13
+ :(NSString *)defaultHost
14
+ :(NSInteger *)defaultPort
15
+ resolver:(RCTPromiseResolveBlock)resolve
16
+ rejecter:(RCTPromiseRejectBlock)reject
17
+ )
18
+
19
+ RCT_EXTERN_METHOD(start:(NSString *)alias
20
+ resolver:(RCTPromiseResolveBlock)resolve
21
+ rejecter:(RCTPromiseRejectBlock)reject
22
+ )
23
+
24
+ RCT_EXTERN_METHOD(stop:(NSString *)alias
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject
27
+ )
28
+
29
+ RCT_EXTERN_METHOD(spendToAddress:(NSString *)alias
30
+ :(NSString *)zatoshi
31
+ :(NSString *)toAddress
32
+ :(NSString *)memo
33
+ :(NSInteger *)fromAccountIndex
34
+ :(NSString *)spendingKey
35
+ resolver:(RCTPromiseResolveBlock)resolve
36
+ rejecter:(RCTPromiseRejectBlock)reject
37
+ )
38
+
39
+ RCT_EXTERN_METHOD(getTransactions:(NSString *)alias
40
+ :(NSInteger *)first
41
+ :(NSInteger *)last
42
+ resolver:(RCTPromiseResolveBlock)resolve
43
+ rejecter:(RCTPromiseRejectBlock)reject
44
+ )
45
+
46
+ RCT_EXTERN_METHOD(getShieldedBalance:(NSString *)alias
47
+ resolver:(RCTPromiseResolveBlock)resolve
48
+ rejecter:(RCTPromiseRejectBlock)reject
49
+ )
50
+
51
+ RCT_EXTERN_METHOD(rescan:(NSString *)alias
52
+ :(NSInteger *)height
53
+ resolver:(RCTPromiseResolveBlock)resolve
54
+ rejecter:(RCTPromiseRejectBlock)reject
55
+ )
56
+
57
+ // Derivation tool
58
+ RCT_EXTERN_METHOD(deriveViewingKey:(NSString *)seed
59
+ :(NSString *)network
60
+ resolver:(RCTPromiseResolveBlock)resolve
61
+ rejecter:(RCTPromiseRejectBlock)reject
62
+ )
63
+
64
+ RCT_EXTERN_METHOD(deriveSpendingKey:(NSString *)seed
65
+ :(NSString *)network
66
+ resolver:(RCTPromiseResolveBlock)resolve
67
+ rejecter:(RCTPromiseRejectBlock)reject
68
+ )
69
+
70
+ RCT_EXTERN_METHOD(deriveShieldedAddress:(NSString *)viewingKey
71
+ :(NSString *)network
72
+ resolver:(RCTPromiseResolveBlock)resolve
73
+ rejecter:(RCTPromiseRejectBlock)reject
74
+ )
75
+
76
+ RCT_EXTERN_METHOD(isValidTransparentAddress:(NSString *)address
77
+ :(NSString *)network
78
+ resolver:(RCTPromiseResolveBlock)resolve
79
+ rejecter:(RCTPromiseRejectBlock)reject
80
+ )
81
+
82
+ RCT_EXTERN_METHOD(isValidShieldedAddress:(NSString *)address
83
+ :(NSString *)network
84
+ resolver:(RCTPromiseResolveBlock)resolve
85
+ rejecter:(RCTPromiseRejectBlock)reject
86
+ )
87
+
88
+ // Events
89
+ RCT_EXTERN_METHOD(supportedEvents)
44
90
 
45
91
  @end