react-native-pirate-wallet 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ package com.pirate.wallet.reactnative
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
6
+ import com.facebook.react.bridge.ReactMethod
7
+ import java.io.File
8
+ import org.json.JSONObject
9
+
10
+ internal object NativeBridge {
11
+ init {
12
+ System.loadLibrary("pirate_ffi_native")
13
+ }
14
+
15
+ external fun invokeJson(requestJson: String, pretty: Boolean = false): String
16
+ }
17
+
18
+ class PirateWalletReactNativeModule(
19
+ reactContext: ReactApplicationContext,
20
+ ) : ReactContextBaseJavaModule(reactContext) {
21
+ override fun getName(): String = "PirateWalletReactNative"
22
+
23
+ @ReactMethod
24
+ fun invoke(requestJson: String, pretty: Boolean, promise: Promise) {
25
+ try {
26
+ promise.resolve(NativeBridge.invokeJson(requestJson, pretty))
27
+ } catch (t: Throwable) {
28
+ promise.reject("PIRATE_WALLET_INVOKE_ERROR", t.message, t)
29
+ }
30
+ }
31
+
32
+ @ReactMethod
33
+ fun configureAccountStorage(
34
+ accountId: String,
35
+ passphrase: String,
36
+ storagePath: String?,
37
+ promise: Promise,
38
+ ) {
39
+ try {
40
+ require(accountId.trim().isNotEmpty()) { "accountId must not be empty" }
41
+ require(passphrase.isNotEmpty()) { "passphrase must not be empty" }
42
+
43
+ val walletDbDir = accountStorageDirectory(reactApplicationContext, accountId, storagePath)
44
+ ensureDirectory(walletDbDir)
45
+
46
+ val requestJson = JSONObject()
47
+ .put("method", "configure_wallet_storage")
48
+ .put("base_dir", walletDbDir.absolutePath)
49
+ .put("passphrase", passphrase)
50
+ .toString()
51
+
52
+ promise.resolve(NativeBridge.invokeJson(requestJson, false))
53
+ } catch (t: Throwable) {
54
+ promise.reject("PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", t.message, t)
55
+ }
56
+ }
57
+
58
+ private fun accountStorageDirectory(
59
+ context: ReactApplicationContext,
60
+ accountId: String,
61
+ storagePath: String?,
62
+ ): File {
63
+ if (!storagePath.isNullOrBlank()) {
64
+ return File(storagePath)
65
+ }
66
+
67
+ val accountsDir = File(File(context.filesDir, "pirate_wallet"), "accounts")
68
+ return File(accountsDir, sanitizeAccountId(accountId))
69
+ }
70
+
71
+ private fun ensureDirectory(walletDbDir: File) {
72
+ if (!walletDbDir.exists() && !walletDbDir.mkdirs()) {
73
+ throw IllegalStateException(
74
+ "Failed to create wallet database directory: ${walletDbDir.absolutePath}"
75
+ )
76
+ }
77
+ }
78
+
79
+ private fun sanitizeAccountId(accountId: String): String {
80
+ val trimmed = accountId.trim()
81
+ require(trimmed.isNotEmpty()) { "accountId must not be empty" }
82
+
83
+ val sanitized = buildString {
84
+ for (char in trimmed) {
85
+ append(
86
+ if (char.isLetterOrDigit() || char == '_' || char == '-' || char == '.') {
87
+ char
88
+ } else {
89
+ '_'
90
+ }
91
+ )
92
+ }
93
+ }
94
+ return sanitized.ifEmpty { "account" }
95
+ }
96
+ }
@@ -0,0 +1,14 @@
1
+ package com.pirate.wallet.reactnative
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class PirateWalletReactNativePackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
10
+ listOf(PirateWalletReactNativeModule(reactContext))
11
+
12
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
13
+ emptyList()
14
+ }
@@ -0,0 +1,172 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridgeModule.h>
3
+ @import PirateWalletNative;
4
+
5
+ @interface PirateWalletReactNative : NSObject <RCTBridgeModule>
6
+ @end
7
+
8
+ @implementation PirateWalletReactNative
9
+
10
+ RCT_EXPORT_MODULE();
11
+
12
+ + (BOOL)requiresMainQueueSetup
13
+ {
14
+ return NO;
15
+ }
16
+
17
+ RCT_REMAP_METHOD(invoke,
18
+ invoke:(NSString *)requestJson
19
+ pretty:(BOOL)pretty
20
+ resolver:(RCTPromiseResolveBlock)resolve
21
+ rejecter:(RCTPromiseRejectBlock)reject)
22
+ {
23
+ const char *requestCString = [requestJson UTF8String];
24
+ if (requestCString == NULL) {
25
+ reject(@"PIRATE_WALLET_INVOKE_ERROR", @"Request string was not valid UTF-8.", nil);
26
+ return;
27
+ }
28
+
29
+ char *responsePtr = pirate_wallet_service_invoke_json(requestCString, pretty);
30
+ if (responsePtr == NULL) {
31
+ reject(@"PIRATE_WALLET_INVOKE_ERROR", @"Wallet service returned a null response.", nil);
32
+ return;
33
+ }
34
+
35
+ NSString *response = [NSString stringWithUTF8String:responsePtr];
36
+ pirate_wallet_service_free_string(responsePtr);
37
+
38
+ if (response == nil) {
39
+ reject(@"PIRATE_WALLET_INVOKE_ERROR", @"Wallet service returned invalid UTF-8.", nil);
40
+ return;
41
+ }
42
+
43
+ resolve(response);
44
+ }
45
+
46
+ RCT_REMAP_METHOD(configureAccountStorage,
47
+ configureAccountStorage:(NSString *)accountId
48
+ passphrase:(NSString *)passphrase
49
+ storagePath:(NSString *)storagePath
50
+ resolver:(RCTPromiseResolveBlock)resolve
51
+ rejecter:(RCTPromiseRejectBlock)reject)
52
+ {
53
+ if ((id)storagePath == [NSNull null]) {
54
+ storagePath = nil;
55
+ }
56
+
57
+ if (accountId == nil || accountId.length == 0) {
58
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", @"accountId must not be empty", nil);
59
+ return;
60
+ }
61
+ if (passphrase == nil || passphrase.length == 0) {
62
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", @"passphrase must not be empty", nil);
63
+ return;
64
+ }
65
+
66
+ NSError *error = nil;
67
+ NSString *baseDir = [self storagePathForAccountId:accountId storagePath:storagePath error:&error];
68
+ if (baseDir == nil) {
69
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", error.localizedDescription, error);
70
+ return;
71
+ }
72
+
73
+ NSDictionary *request = @{
74
+ @"method": @"configure_wallet_storage",
75
+ @"base_dir": baseDir,
76
+ @"passphrase": passphrase
77
+ };
78
+ NSData *requestData = [NSJSONSerialization dataWithJSONObject:request options:0 error:&error];
79
+ if (requestData == nil) {
80
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", error.localizedDescription, error);
81
+ return;
82
+ }
83
+
84
+ NSString *requestJson = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];
85
+ if (requestJson == nil) {
86
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", @"Storage configuration request was not valid UTF-8.", nil);
87
+ return;
88
+ }
89
+
90
+ const char *requestCString = [requestJson UTF8String];
91
+ char *responsePtr = pirate_wallet_service_invoke_json(requestCString, NO);
92
+ if (responsePtr == NULL) {
93
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", @"Wallet service returned a null response.", nil);
94
+ return;
95
+ }
96
+
97
+ NSString *response = [NSString stringWithUTF8String:responsePtr];
98
+ pirate_wallet_service_free_string(responsePtr);
99
+
100
+ if (response == nil) {
101
+ reject(@"PIRATE_WALLET_CONFIGURE_STORAGE_ERROR", @"Wallet service returned invalid UTF-8.", nil);
102
+ return;
103
+ }
104
+
105
+ resolve(response);
106
+ }
107
+
108
+ - (NSString *)storagePathForAccountId:(NSString *)accountId
109
+ storagePath:(NSString *)storagePath
110
+ error:(NSError **)error
111
+ {
112
+ if (storagePath != nil && storagePath.length > 0) {
113
+ return [self ensureStorageDirectory:storagePath error:error] ? storagePath : nil;
114
+ }
115
+
116
+ NSString *sanitized = [self sanitizedAccountId:accountId];
117
+ if (sanitized.length == 0) {
118
+ if (error != nil) {
119
+ *error = [NSError errorWithDomain:@"PirateWalletReactNative"
120
+ code:1
121
+ userInfo:@{NSLocalizedDescriptionKey: @"accountId must not be empty"}];
122
+ }
123
+ return nil;
124
+ }
125
+
126
+ NSArray<NSURL *> *urls = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
127
+ inDomains:NSUserDomainMask];
128
+ NSURL *applicationSupport = urls.firstObject;
129
+ if (applicationSupport == nil) {
130
+ if (error != nil) {
131
+ *error = [NSError errorWithDomain:@"PirateWalletReactNative"
132
+ code:2
133
+ userInfo:@{NSLocalizedDescriptionKey: @"Application Support directory is unavailable"}];
134
+ }
135
+ return nil;
136
+ }
137
+
138
+ NSURL *base = [[applicationSupport URLByAppendingPathComponent:@"PirateWallet" isDirectory:YES]
139
+ URLByAppendingPathComponent:@"accounts" isDirectory:YES];
140
+ NSString *path = [[base URLByAppendingPathComponent:sanitized isDirectory:YES] path];
141
+ return [self ensureStorageDirectory:path error:error] ? path : nil;
142
+ }
143
+
144
+ - (BOOL)ensureStorageDirectory:(NSString *)path error:(NSError **)error
145
+ {
146
+ return [[NSFileManager defaultManager] createDirectoryAtPath:path
147
+ withIntermediateDirectories:YES
148
+ attributes:nil
149
+ error:error];
150
+ }
151
+
152
+ - (NSString *)sanitizedAccountId:(NSString *)accountId
153
+ {
154
+ NSString *trimmed = [accountId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
155
+ if (trimmed.length == 0) {
156
+ return @"";
157
+ }
158
+
159
+ NSMutableString *result = [NSMutableString stringWithCapacity:trimmed.length];
160
+ NSCharacterSet *allowed = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-"];
161
+ for (NSUInteger index = 0; index < trimmed.length; index++) {
162
+ unichar character = [trimmed characterAtIndex:index];
163
+ if ([allowed characterIsMember:character]) {
164
+ [result appendFormat:@"%C", character];
165
+ } else {
166
+ [result appendString:@"_"];
167
+ }
168
+ }
169
+ return result;
170
+ }
171
+
172
+ @end
@@ -0,0 +1,40 @@
1
+ import Foundation
2
+ import PirateWalletNative
3
+ import React
4
+
5
+ @objc(PirateWalletReactNative)
6
+ final class PirateWalletReactNative: NSObject {
7
+ @objc
8
+ static func requiresMainQueueSetup() -> Bool {
9
+ false
10
+ }
11
+
12
+ @objc
13
+ func invoke(
14
+ _ requestJson: String,
15
+ pretty: Bool,
16
+ resolver resolve: @escaping RCTPromiseResolveBlock,
17
+ rejecter reject: @escaping RCTPromiseRejectBlock
18
+ ) {
19
+ guard let request = requestJson.cString(using: .utf8) else {
20
+ reject("PIRATE_WALLET_INVOKE_ERROR", "Request string was not valid UTF-8.", nil)
21
+ return
22
+ }
23
+
24
+ let pointer = request.withUnsafeBufferPointer { buffer in
25
+ pirate_wallet_service_invoke_json(buffer.baseAddress, pretty)
26
+ }
27
+
28
+ guard let pointer else {
29
+ reject("PIRATE_WALLET_INVOKE_ERROR", "Wallet service returned a null response.", nil)
30
+ return
31
+ }
32
+
33
+ defer {
34
+ pirate_wallet_service_free_string(pointer)
35
+ }
36
+
37
+ let response = String(cString: pointer)
38
+ resolve(response)
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "react-native-pirate-wallet",
3
+ "version": "0.2.1",
4
+ "description": "React Native wrapper for the Pirate Unified Wallet native SDK surfaces",
5
+ "keywords": [
6
+ "react-native",
7
+ "pirate-chain",
8
+ "cryptocurrency",
9
+ "wallet",
10
+ "privacy",
11
+ "shielded"
12
+ ],
13
+ "homepage": "https://github.com/PirateNetwork/Pirate-Unified-Light-Wallet/tree/main/bindings/react-native-pirate-wallet#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/PirateNetwork/Pirate-Unified-Light-Wallet/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/PirateNetwork/Pirate-Unified-Light-Wallet.git",
20
+ "directory": "bindings/react-native-pirate-wallet"
21
+ },
22
+ "author": "Pirate Chain Contributors",
23
+ "license": "MIT",
24
+ "main": "src/index.js",
25
+ "types": "src/index.d.ts",
26
+ "files": [
27
+ "android/build.gradle",
28
+ "android/consumer-rules.pro",
29
+ "android/src/main/AndroidManifest.xml",
30
+ "android/src/main/java/",
31
+ "ios/PirateWalletReactNative.m",
32
+ "ios/PirateWalletReactNative.swift",
33
+ "src/",
34
+ "scripts/",
35
+ "test/",
36
+ "LICENSE-MIT",
37
+ "README.md",
38
+ "package.json",
39
+ "react-native.config.js",
40
+ "react-native-pirate-wallet.podspec"
41
+ ],
42
+ "scripts": {
43
+ "test": "node test/smoke.js",
44
+ "verify:package": "node scripts/verify-package.js",
45
+ "prepare:native": "node scripts/assemble-ios-framework.js",
46
+ "postinstall": "npm run prepare:native",
47
+ "prepack": "node scripts/verify-package.js --publish-layout"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "peerDependencies": {
53
+ "react-native": ">=0.71.0 <1.0.0"
54
+ },
55
+ "optionalDependencies": {
56
+ "react-native-pirate-wallet-android": "0.2.1",
57
+ "react-native-pirate-wallet-android-x86_64": "0.2.1",
58
+ "react-native-pirate-wallet-ios-device": "0.2.1",
59
+ "react-native-pirate-wallet-ios-simulator": "0.2.1"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }
@@ -0,0 +1,19 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = package["name"]
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.license = package["license"]
10
+ s.homepage = "https://github.com/piratenetwork/Pirate-Unified-Light-Wallet"
11
+ s.authors = "Pirate Chain Contributors"
12
+
13
+ s.platform = :ios, "15.0"
14
+ s.source = { :path => "." }
15
+ s.source_files = "ios/PirateWalletReactNative.m"
16
+ s.vendored_frameworks = "ios/Frameworks/PirateWalletNative.xcframework"
17
+
18
+ s.dependency "React-Core"
19
+ end
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ sourceDir: './android'
6
+ },
7
+ ios: {}
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const packageRoot = path.resolve(__dirname, '..');
7
+ const frameworkName = 'PirateWalletNative.xcframework';
8
+ const iosPackages = [
9
+ {
10
+ name: 'react-native-pirate-wallet-ios-device',
11
+ slice: 'ios-arm64',
12
+ },
13
+ {
14
+ name: 'react-native-pirate-wallet-ios-simulator',
15
+ slice: 'ios-arm64_x86_64-simulator',
16
+ },
17
+ ];
18
+
19
+ function candidatePackageJsonPaths(packageName) {
20
+ const candidates = [];
21
+ try {
22
+ candidates.push(
23
+ require.resolve(`${packageName}/package.json`, {
24
+ paths: [process.cwd(), packageRoot],
25
+ }),
26
+ );
27
+ } catch (_) {
28
+ // The monorepo package is resolved below before it has been published.
29
+ }
30
+ candidates.push(
31
+ path.resolve(packageRoot, '..', packageName, 'package.json'),
32
+ );
33
+ return [...new Set(candidates)];
34
+ }
35
+
36
+ function resolvePackage(packageName, expectedVersion) {
37
+ for (const packageJsonPath of candidatePackageJsonPaths(packageName)) {
38
+ if (!fs.statSync(packageJsonPath, {throwIfNoEntry: false})?.isFile()) {
39
+ continue;
40
+ }
41
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
42
+ if (packageJson.name !== packageName) {
43
+ continue;
44
+ }
45
+ if (packageJson.version !== expectedVersion) {
46
+ throw new Error(
47
+ `${packageName}@${packageJson.version} does not match ` +
48
+ `react-native-pirate-wallet@${expectedVersion}`,
49
+ );
50
+ }
51
+ return path.dirname(packageJsonPath);
52
+ }
53
+ throw new Error(
54
+ `${packageName}@${expectedVersion} is required to build ` +
55
+ 'react-native-pirate-wallet for iOS',
56
+ );
57
+ }
58
+
59
+ function linkTree(source, destination) {
60
+ fs.mkdirSync(destination, {recursive: true});
61
+ for (const entry of fs.readdirSync(source, {withFileTypes: true})) {
62
+ const sourcePath = path.join(source, entry.name);
63
+ const destinationPath = path.join(destination, entry.name);
64
+ if (entry.isDirectory()) {
65
+ linkTree(sourcePath, destinationPath);
66
+ continue;
67
+ }
68
+ if (!entry.isFile()) {
69
+ throw new Error(`Unsupported iOS package entry: ${sourcePath}`);
70
+ }
71
+ try {
72
+ fs.linkSync(sourcePath, destinationPath);
73
+ } catch (error) {
74
+ if (!['EXDEV', 'EPERM', 'EACCES', 'EMLINK'].includes(error.code)) {
75
+ throw error;
76
+ }
77
+ fs.copyFileSync(sourcePath, destinationPath);
78
+ }
79
+ }
80
+ }
81
+
82
+ function assemble() {
83
+ if (process.platform !== 'darwin' && !process.argv.includes('--force')) {
84
+ return;
85
+ }
86
+
87
+ const wrapperPackage = JSON.parse(
88
+ fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
89
+ );
90
+ const resolved = iosPackages.map(({name, slice}) => {
91
+ const expectedVersion = wrapperPackage.optionalDependencies?.[name];
92
+ if (expectedVersion !== wrapperPackage.version) {
93
+ throw new Error(`${name} must use the exact wrapper version`);
94
+ }
95
+ const root = resolvePackage(name, expectedVersion);
96
+ return {
97
+ name,
98
+ slice,
99
+ framework: path.join(root, 'ios', 'Frameworks', frameworkName),
100
+ };
101
+ });
102
+
103
+ const infoPlists = resolved.map(({framework}) =>
104
+ fs.readFileSync(path.join(framework, 'Info.plist')),
105
+ );
106
+ if (!infoPlists.slice(1).every(data => data.equals(infoPlists[0]))) {
107
+ throw new Error('iOS binary packages contain different XCFramework metadata');
108
+ }
109
+
110
+ const output = path.join(packageRoot, 'ios', 'Frameworks', frameworkName);
111
+ fs.rmSync(output, {recursive: true, force: true});
112
+ fs.mkdirSync(output, {recursive: true});
113
+ fs.writeFileSync(path.join(output, 'Info.plist'), infoPlists[0]);
114
+
115
+ for (const {name, slice, framework} of resolved) {
116
+ const source = path.join(framework, slice);
117
+ if (!fs.statSync(source, {throwIfNoEntry: false})?.isDirectory()) {
118
+ throw new Error(`${name} is missing XCFramework slice ${slice}`);
119
+ }
120
+ linkTree(source, path.join(output, slice));
121
+ }
122
+ }
123
+
124
+ assemble();
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const androidPackageNames = [
7
+ 'react-native-pirate-wallet-android',
8
+ 'react-native-pirate-wallet-android-x86_64',
9
+ ];
10
+ const packageRoot = path.resolve(__dirname, '..');
11
+
12
+ function candidatePackageJsonPaths(packageName) {
13
+ const candidates = [];
14
+ try {
15
+ candidates.push(
16
+ require.resolve(`${packageName}/package.json`, {
17
+ paths: [process.cwd(), packageRoot],
18
+ }),
19
+ );
20
+ } catch (_) {
21
+ // The monorepo package is resolved below before it has been published.
22
+ }
23
+ candidates.push(
24
+ path.resolve(packageRoot, '..', packageName, 'package.json'),
25
+ );
26
+ return [...new Set(candidates)];
27
+ }
28
+
29
+ function resolveAndroidJniLibsPaths() {
30
+ const wrapperPackage = JSON.parse(
31
+ fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
32
+ );
33
+
34
+ return androidPackageNames.map(packageName => {
35
+ const expectedVersion =
36
+ wrapperPackage.optionalDependencies?.[packageName];
37
+
38
+ for (const packageJsonPath of candidatePackageJsonPaths(packageName)) {
39
+ if (!fs.statSync(packageJsonPath, {throwIfNoEntry: false})?.isFile()) {
40
+ continue;
41
+ }
42
+
43
+ const androidPackage = JSON.parse(
44
+ fs.readFileSync(packageJsonPath, 'utf8'),
45
+ );
46
+ if (androidPackage.name !== packageName) {
47
+ continue;
48
+ }
49
+ if (androidPackage.version !== expectedVersion) {
50
+ throw new Error(
51
+ `${packageName}@${androidPackage.version} does not match ` +
52
+ `react-native-pirate-wallet@${wrapperPackage.version}`,
53
+ );
54
+ }
55
+
56
+ const jniLibsPath = path.join(
57
+ path.dirname(packageJsonPath),
58
+ 'android',
59
+ 'src',
60
+ 'main',
61
+ 'jniLibs',
62
+ );
63
+ if (fs.statSync(jniLibsPath, {throwIfNoEntry: false})?.isDirectory()) {
64
+ return jniLibsPath;
65
+ }
66
+ }
67
+
68
+ throw new Error(
69
+ `${packageName}@${expectedVersion} is required to build ` +
70
+ 'react-native-pirate-wallet for Android',
71
+ );
72
+ });
73
+ }
74
+
75
+ module.exports = {resolveAndroidJniLibsPaths};