@solana-mobile/seed-vault-lib 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore +2 -0
- package/README.md +94 -0
- package/android/.gitignore +14 -0
- package/android/src/main/java/com/solanamobile/seedvault/model/SigningRequest.kt +5 -3
- package/android/src/main/java/com/solanamobile/seedvault/reactnative/Extensions.kt +85 -4
- package/android/src/main/java/com/solanamobile/seedvault/reactnative/SerializationUtils.kt +81 -0
- package/android/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt +247 -94
- package/lib/esm/index.js +111 -0
- package/lib/esm/index.native.js +6 -3
- package/lib/types/index.d.ts +148 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.native.d.ts +68 -7
- package/lib/types/index.native.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +3 -0
- package/src/seedVaultEvent.ts +101 -0
- package/src/types.ts +91 -0
- package/src/useSeedVault.ts +98 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +8 -0
- package/yarn.lock +611 -0
|
@@ -5,12 +5,13 @@ import android.content.Intent;
|
|
|
5
5
|
import android.database.ContentObserver
|
|
6
6
|
import android.net.Uri
|
|
7
7
|
import android.os.Bundle
|
|
8
|
+
import android.os.CountDownTimer
|
|
8
9
|
import android.os.Handler
|
|
10
|
+
import android.util.Base64
|
|
9
11
|
import android.util.Log
|
|
10
12
|
import com.facebook.react.bridge.*
|
|
11
13
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
14
|
import com.solanamobile.seedvault.*
|
|
13
|
-
import kotlinx.serialization.Serializable
|
|
14
15
|
import kotlinx.serialization.builtins.ListSerializer
|
|
15
16
|
import kotlinx.serialization.builtins.serializer
|
|
16
17
|
import kotlinx.serialization.json.Json
|
|
@@ -37,12 +38,22 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
private var activityResultTimeout: Long? = DEFAULT_ACTIVITY_RESULT_TIMEOUT_MS
|
|
42
|
+
|
|
40
43
|
init {
|
|
41
44
|
reactContext.addActivityEventListener(mActivityEventListener)
|
|
42
45
|
|
|
43
46
|
observeSeedVaultContentChanges()
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
@ReactMethod
|
|
50
|
+
fun setActivtyResultTimeout(timeout: Long) {
|
|
51
|
+
activityResultTimeout =
|
|
52
|
+
if (timeout > MINIMUM_ACTIVITY_RESULT_TIMEOUT_MS) timeout
|
|
53
|
+
else if (timeout == 0L) null
|
|
54
|
+
else MINIMUM_ACTIVITY_RESULT_TIMEOUT_MS
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
@ReactMethod
|
|
47
58
|
fun isSeedVaultAvailable(allowSimulated: Boolean = false, promise: Promise) {
|
|
48
59
|
val seedVaultAvailable = SeedVault.isAvailable(reactContext, allowSimulated)
|
|
@@ -122,24 +133,87 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
|
|
|
122
133
|
}
|
|
123
134
|
|
|
124
135
|
@ReactMethod
|
|
125
|
-
fun
|
|
136
|
+
fun requestAuthorizeNewSeed() {
|
|
137
|
+
Log.d(TAG, "Requesting authorization for a new seed...")
|
|
138
|
+
val intent = Wallet.authorizeSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
139
|
+
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_AUTHORIZE_SEED_ACCESS)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@ReactMethod
|
|
143
|
+
fun authorizeNewSeed(promise: Promise) {
|
|
126
144
|
Log.d(TAG, "Requesting authorization for a new seed...")
|
|
127
145
|
val intent = Wallet.authorizeSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
128
|
-
|
|
146
|
+
registerForActivityResult(intent, REQUEST_AUTHORIZE_SEED_ACCESS) { resultCode, data ->
|
|
147
|
+
try {
|
|
148
|
+
val authToken = Wallet.onAuthorizeSeedResult(resultCode, data)
|
|
149
|
+
Log.d(TAG, "Seed authorized, AuthToken=$authToken")
|
|
150
|
+
|
|
151
|
+
promise.resolve(
|
|
152
|
+
Arguments.createMap().apply {
|
|
153
|
+
putString("authToken", authToken.toString())
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
157
|
+
Log.e(TAG, "Seed authorization failed", e)
|
|
158
|
+
promise.reject(e)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@ReactMethod
|
|
164
|
+
fun requestCreateNewSeed() {
|
|
165
|
+
Log.d(TAG, "Requesting creation of a new seed...")
|
|
166
|
+
val intent = Wallet.createSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
167
|
+
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_CREATE_NEW_SEED)
|
|
129
168
|
}
|
|
130
169
|
|
|
131
170
|
@ReactMethod
|
|
132
|
-
fun createNewSeed() {
|
|
171
|
+
fun createNewSeed(promise: Promise) {
|
|
133
172
|
Log.d(TAG, "Requesting creation of a new seed...")
|
|
134
173
|
val intent = Wallet.createSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
135
|
-
|
|
174
|
+
registerForActivityResult(intent, REQUEST_CREATE_NEW_SEED) { resultCode, data ->
|
|
175
|
+
try {
|
|
176
|
+
val authToken = Wallet.onCreateSeedResult(resultCode, data)
|
|
177
|
+
Log.d(TAG, "Seed created, AuthToken=$authToken")
|
|
178
|
+
|
|
179
|
+
promise.resolve(
|
|
180
|
+
Arguments.createMap().apply {
|
|
181
|
+
putString("authToken", authToken.toString())
|
|
182
|
+
}
|
|
183
|
+
)
|
|
184
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
185
|
+
Log.e(TAG, "Seed creation failed", e)
|
|
186
|
+
promise.reject(e)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
136
189
|
}
|
|
137
190
|
|
|
138
191
|
@ReactMethod
|
|
139
|
-
fun
|
|
192
|
+
fun requestImportExistingSeed() {
|
|
140
193
|
Log.d(TAG, "Requesting import of an existing seed...")
|
|
141
194
|
val intent = Wallet.importSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
142
|
-
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_IMPORT_EXISTING_SEED)
|
|
195
|
+
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_IMPORT_EXISTING_SEED)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@ReactMethod
|
|
199
|
+
fun importExistingSeed(promise: Promise) {
|
|
200
|
+
Log.d(TAG, "Requesting import of an existing seed...")
|
|
201
|
+
val intent = Wallet.importSeed(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
|
|
202
|
+
registerForActivityResult(intent, REQUEST_IMPORT_EXISTING_SEED) { resultCode, data ->
|
|
203
|
+
try {
|
|
204
|
+
val authToken = Wallet.onImportSeedResult(resultCode, data)
|
|
205
|
+
Log.d(TAG, "Seed imported, AuthToken=$authToken")
|
|
206
|
+
|
|
207
|
+
promise.resolve(
|
|
208
|
+
Arguments.createMap().apply {
|
|
209
|
+
putString("authToken", authToken.toString())
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
213
|
+
Log.e(TAG, "Seed import failed", e)
|
|
214
|
+
promise.reject(e)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
143
217
|
}
|
|
144
218
|
|
|
145
219
|
@ReactMethod
|
|
@@ -155,37 +229,146 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
|
|
|
155
229
|
}
|
|
156
230
|
|
|
157
231
|
@ReactMethod
|
|
158
|
-
fun
|
|
159
|
-
|
|
232
|
+
fun requestSignMessage(authToken: String, derivationPath: String, message: ReadableArray) {
|
|
233
|
+
requestSignMessages(authToken, listOf(SigningRequest(message.toByteArray(), arrayListOf(Uri.parse(derivationPath)))))
|
|
160
234
|
}
|
|
161
235
|
|
|
162
236
|
@ReactMethod
|
|
163
|
-
fun
|
|
237
|
+
fun requestSignMessages(authToken: String, signingRequestsJson: String) {
|
|
164
238
|
val signingRequests: List<SigningRequest> = json.decodeFromString(ListSerializer(SigningRequestSerializer), signingRequestsJson)
|
|
165
|
-
|
|
239
|
+
requestSignMessages(authToken, signingRequests)
|
|
166
240
|
}
|
|
167
241
|
|
|
168
|
-
private fun
|
|
242
|
+
private fun requestSignMessages(authToken: String, signingRequests: List<SigningRequest>) {
|
|
169
243
|
Log.d(TAG, "Requesting provided messages to be signed...")
|
|
170
244
|
val intent = Wallet.signMessages(authToken.toLong(), ArrayList(signingRequests))
|
|
171
245
|
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_SIGN_MESSAGES);
|
|
172
246
|
}
|
|
173
247
|
|
|
174
248
|
@ReactMethod
|
|
175
|
-
fun
|
|
176
|
-
|
|
249
|
+
fun signMessage(authToken: String, derivationPath: String, message: String, promise: Promise) {
|
|
250
|
+
val messageBytes = Base64.decode(message, Base64.DEFAULT);
|
|
251
|
+
signMessagesAsync(authToken, listOf(SigningRequest(messageBytes, arrayListOf(Uri.parse(derivationPath))))) { result, error ->
|
|
252
|
+
result?.let { promise.resolve(Arguments.makeNativeMap(Arguments.toBundle(result.getMap(0)))) }
|
|
253
|
+
?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
254
|
+
}
|
|
177
255
|
}
|
|
178
256
|
|
|
179
257
|
@ReactMethod
|
|
180
|
-
fun
|
|
258
|
+
fun signMessages(authToken: String, signingRequests: ReadableArray, promise: Promise) {
|
|
259
|
+
val signingRequests: List<SigningRequest> = json.decodeFromJsonElement(ListSerializer(SigningRequestSerializer), signingRequests.toJson())
|
|
260
|
+
signMessagesAsync(authToken, signingRequests) { result, error ->
|
|
261
|
+
result?.let { promise.resolve(result) } ?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private fun signMessagesAsync(authToken: String, signingRequests: List<SigningRequest>, callback: (result: ReadableArray?, error: Throwable?) -> Unit) {
|
|
266
|
+
Log.d(TAG, "Requesting provided messages to be signed...")
|
|
267
|
+
val intent = Wallet.signMessages(authToken.toLong(), ArrayList(signingRequests))
|
|
268
|
+
registerForActivityResult(intent, REQUEST_SIGN_MESSAGES) { resultCode, data ->
|
|
269
|
+
try {
|
|
270
|
+
val result = Wallet.onSignMessagesResult(resultCode, data)
|
|
271
|
+
Log.d(TAG, "Message signed: signatures=$result")
|
|
272
|
+
|
|
273
|
+
callback(
|
|
274
|
+
Arguments.makeNativeArray(result.map { response ->
|
|
275
|
+
Arguments.createMap().apply {
|
|
276
|
+
putArray("signatures", Arguments.makeNativeArray(response.signatures.map { Base64.encodeToString(it, Base64.NO_WRAP) }))
|
|
277
|
+
putArray("resolvedDerivationPaths", Arguments.makeNativeArray(response.resolvedDerivationPaths.map { it.toString() }))
|
|
278
|
+
}
|
|
279
|
+
}), null
|
|
280
|
+
)
|
|
281
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
282
|
+
Log.e(TAG, "Message signing failed", e)
|
|
283
|
+
callback(null, e)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@ReactMethod
|
|
289
|
+
fun requestSignTransaction(authToken: String, derivationPath: String, transaction: ReadableArray) {
|
|
290
|
+
requestSignTransactions(authToken, listOf(SigningRequest(transaction.toByteArray(), arrayListOf(Uri.parse(derivationPath)))))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@ReactMethod
|
|
294
|
+
fun requestSignTransactions(authToken: String, signingRequestsJson: String) {
|
|
181
295
|
val signingRequests: List<SigningRequest> = json.decodeFromString(ListSerializer(SigningRequestSerializer), signingRequestsJson)
|
|
182
|
-
|
|
296
|
+
requestSignTransactions(authToken, signingRequests)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun requestSignTransactions(authToken: String, signingRequests: List<SigningRequest>) {
|
|
300
|
+
Log.d(TAG, "Requesting provided transactions to be signed...")
|
|
301
|
+
val intent = Wallet.signTransactions(authToken.toLong(), ArrayList(signingRequests))
|
|
302
|
+
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_SIGN_TRANSACTIONS)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@ReactMethod
|
|
306
|
+
fun signTransaction(authToken: String, derivationPath: String, transaction: String, promise: Promise) {
|
|
307
|
+
val txBytes = Base64.decode(transaction, Base64.DEFAULT);
|
|
308
|
+
signTransactionsAsync(authToken, listOf(SigningRequest(txBytes, arrayListOf(Uri.parse(derivationPath))))) { result, error ->
|
|
309
|
+
result?.let { promise.resolve(Arguments.makeNativeMap(Arguments.toBundle(result.getMap(0)))) }
|
|
310
|
+
?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@ReactMethod
|
|
315
|
+
fun signTransactions(authToken: String, signingRequests: ReadableArray, promise: Promise) {
|
|
316
|
+
val signingRequests: List<SigningRequest> = json.decodeFromJsonElement(ListSerializer(SigningRequestSerializer), signingRequests.toJson())
|
|
317
|
+
signTransactionsAsync(authToken, signingRequests) { result, error ->
|
|
318
|
+
result?.let { promise.resolve(result) } ?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
319
|
+
}
|
|
183
320
|
}
|
|
184
321
|
|
|
185
|
-
private fun
|
|
322
|
+
private fun signTransactionsAsync(authToken: String, signingRequests: List<SigningRequest>, callback: (result: ReadableArray?, error: Throwable?) -> Unit) {
|
|
186
323
|
Log.d(TAG, "Requesting provided transactions to be signed...")
|
|
187
324
|
val intent = Wallet.signTransactions(authToken.toLong(), ArrayList(signingRequests))
|
|
188
|
-
|
|
325
|
+
registerForActivityResult(intent, REQUEST_SIGN_TRANSACTIONS) { resultCode, data ->
|
|
326
|
+
try {
|
|
327
|
+
val result = Wallet.onSignTransactionsResult(resultCode, data)
|
|
328
|
+
Log.d(TAG, "Transactions signed: signatures=$result")
|
|
329
|
+
|
|
330
|
+
callback(
|
|
331
|
+
Arguments.makeNativeArray(result.map { response ->
|
|
332
|
+
Arguments.createMap().apply {
|
|
333
|
+
putArray("signatures", Arguments.makeNativeArray(response.signatures.map { Base64.encodeToString(it, Base64.NO_WRAP) }))
|
|
334
|
+
putArray("resolvedDerivationPaths", Arguments.makeNativeArray(response.resolvedDerivationPaths.map { it.toString() }))
|
|
335
|
+
}
|
|
336
|
+
}), null
|
|
337
|
+
)
|
|
338
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
339
|
+
Log.e(TAG, "Transaction signing failed", e)
|
|
340
|
+
callback(null, e)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private fun registerForActivityResult(intent: Intent, requestCode: Int, callback: (resultCode: Int, data: Intent?) -> Unit) {
|
|
346
|
+
val timeout = activityResultTimeout?.let { timeout ->
|
|
347
|
+
object : CountDownTimer(timeout, timeout) {
|
|
348
|
+
override fun onTick(millisUntilFinished: Long) {}
|
|
349
|
+
override fun onFinish() {
|
|
350
|
+
reactContext.currentActivity?.finishActivity(requestCode)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
reactContext.addActivityEventListener(object : BaseActivityEventListener() {
|
|
356
|
+
override fun onActivityResult(
|
|
357
|
+
activity: Activity?,
|
|
358
|
+
receivedRequestCode: Int,
|
|
359
|
+
resultCode: Int,
|
|
360
|
+
data: Intent?
|
|
361
|
+
) {
|
|
362
|
+
if (receivedRequestCode == requestCode) {
|
|
363
|
+
reactContext.removeActivityEventListener(this)
|
|
364
|
+
callback(resultCode, data)
|
|
365
|
+
timeout?.cancel()
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
reactContext.currentActivity?.startActivityForResult(intent, requestCode)
|
|
371
|
+
timeout?.start()
|
|
189
372
|
}
|
|
190
373
|
|
|
191
374
|
@ReactMethod
|
|
@@ -204,6 +387,49 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
|
|
|
204
387
|
reactContext.currentActivity?.startActivityForResult(intent, REQUEST_GET_PUBLIC_KEYS);
|
|
205
388
|
}
|
|
206
389
|
|
|
390
|
+
@ReactMethod
|
|
391
|
+
fun getPublicKey(authToken: String, derivationPath: String, promise: Promise) {
|
|
392
|
+
getPublicKeysAsync(authToken, Arguments.createArray().apply {
|
|
393
|
+
pushString(derivationPath)
|
|
394
|
+
}) { result, error ->
|
|
395
|
+
result?.let { promise.resolve(Arguments.makeNativeMap(Arguments.toBundle(result.getMap(0)))) }
|
|
396
|
+
?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@ReactMethod
|
|
401
|
+
fun getPublicKeys(authToken: String, derivationPaths: ReadableArray, promise: Promise) {
|
|
402
|
+
getPublicKeysAsync(authToken, derivationPaths) { result, error ->
|
|
403
|
+
result?.let { promise.resolve(result) } ?: promise.reject(error ?: Error("An unkown error occurred"))
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private fun getPublicKeysAsync(authToken: String, derivationPaths: ReadableArray, callback: (result: ReadableArray?, error: Throwable?) -> Unit) {
|
|
408
|
+
Log.d(TAG, "Requesting public keys for provided derviation paths...")
|
|
409
|
+
val intent = Wallet.requestPublicKeys(authToken.toLong(), Arguments.toList(derivationPaths)?.mapNotNull {
|
|
410
|
+
(it as? String)?.let { uriString -> Uri.parse(uriString) }
|
|
411
|
+
} as ArrayList ?: arrayListOf())
|
|
412
|
+
registerForActivityResult(intent, REQUEST_GET_PUBLIC_KEYS) { resultCode, data ->
|
|
413
|
+
try {
|
|
414
|
+
val result = Wallet.onRequestPublicKeysResult(resultCode, data)
|
|
415
|
+
Log.d(TAG, "Public key retrieved: publicKey=$result")
|
|
416
|
+
|
|
417
|
+
callback(
|
|
418
|
+
Arguments.makeNativeArray(result.map { response ->
|
|
419
|
+
Arguments.createMap().apply {
|
|
420
|
+
putArray("publicKey", response.publicKey.toWritableArray())
|
|
421
|
+
putString("publicKeyEncoded", response.publicKeyEncoded)
|
|
422
|
+
putString("resolvedDerviationPath", response.resolvedDerivationPath.toString())
|
|
423
|
+
}
|
|
424
|
+
}), null
|
|
425
|
+
)
|
|
426
|
+
} catch (e: Wallet.ActionFailedException) {
|
|
427
|
+
Log.e(TAG, "Public Key retrieval failed", e)
|
|
428
|
+
callback(null, e)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
207
433
|
private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap? = null) {
|
|
208
434
|
reactContext
|
|
209
435
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
@@ -313,80 +539,7 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
|
|
|
313
539
|
private const val REQUEST_SIGN_MESSAGES = 4
|
|
314
540
|
private const val REQUEST_GET_PUBLIC_KEYS = 5
|
|
315
541
|
private const val KEY_PENDING_EVENT = "pendingEvent"
|
|
542
|
+
private const val DEFAULT_ACTIVITY_RESULT_TIMEOUT_MS = 300000L
|
|
543
|
+
private const val MINIMUM_ACTIVITY_RESULT_TIMEOUT_MS = 30000L
|
|
316
544
|
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
interface RNWritable {
|
|
320
|
-
fun toWritableMap(): WritableMap
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
data class Account(
|
|
324
|
-
@WalletContractV1.AccountId val id: Long,
|
|
325
|
-
val name: String,
|
|
326
|
-
val derivationPath: Uri,
|
|
327
|
-
val publicKeyEncoded: String
|
|
328
|
-
) : RNWritable {
|
|
329
|
-
override fun toWritableMap() = Arguments.createMap().apply {
|
|
330
|
-
putString("id", "$id")
|
|
331
|
-
putString("name", name)
|
|
332
|
-
putString("derivationPath", "$derivationPath")
|
|
333
|
-
putString("publicKeyEncoded", publicKeyEncoded)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
data class Seed(
|
|
338
|
-
@WalletContractV1.AuthToken val authToken: Long,
|
|
339
|
-
val name: String,
|
|
340
|
-
@WalletContractV1.Purpose val purpose: Int,
|
|
341
|
-
// val accounts: List<Account> = listOf()
|
|
342
|
-
) : RNWritable {
|
|
343
|
-
override fun toWritableMap() = Arguments.createMap().apply {
|
|
344
|
-
putString("authToken", "$authToken")
|
|
345
|
-
putString("name", name)
|
|
346
|
-
putInt("purpose", purpose)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
fun List<RNWritable>.toWritableArray() = Arguments.createArray().apply {
|
|
351
|
-
forEach { writable ->
|
|
352
|
-
pushMap(writable.toWritableMap())
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
@Serializable
|
|
357
|
-
sealed interface SeedVaultEvent {
|
|
358
|
-
sealed class SeedEvent(val authToken: Long) : SeedVaultEvent
|
|
359
|
-
class SeedAuthorized(authToken: Long) : SeedEvent(authToken)
|
|
360
|
-
class NewSeedCreated(authToken: Long) : SeedEvent(authToken)
|
|
361
|
-
class ExistingSeedImported(authToken: Long) : SeedEvent(authToken)
|
|
362
|
-
|
|
363
|
-
data class PayloadsSigned(val result: List<SigningResponse>) : SeedVaultEvent
|
|
364
|
-
|
|
365
|
-
data class PublicKeysEvent(val result: List<PublicKeyResponse>) : SeedVaultEvent
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
internal fun SeedVaultEvent.toWritableMap() : WritableMap = Arguments.createMap().apply {
|
|
369
|
-
putString("__type", this@toWritableMap::class.simpleName)
|
|
370
|
-
when (this@toWritableMap) {
|
|
371
|
-
is SeedVaultEvent.SeedEvent -> {
|
|
372
|
-
putString("authToken", authToken.toString())
|
|
373
|
-
}
|
|
374
|
-
is SeedVaultEvent.PayloadsSigned -> {
|
|
375
|
-
putArray("result", Arguments.makeNativeArray(result.map { response ->
|
|
376
|
-
Arguments.createMap().apply {
|
|
377
|
-
putArray("signatures", Arguments.makeNativeArray(response.signatures.map { it.toWritableArray() }))
|
|
378
|
-
putArray("resolvedDerivationPaths", Arguments.makeNativeArray(response.resolvedDerivationPaths.map { it.toString() }))
|
|
379
|
-
}
|
|
380
|
-
}))
|
|
381
|
-
}
|
|
382
|
-
is SeedVaultEvent.PublicKeysEvent -> {
|
|
383
|
-
putArray("result", Arguments.makeNativeArray(result.map { response ->
|
|
384
|
-
Arguments.createMap().apply {
|
|
385
|
-
putArray("publicKey", response.publicKey.toWritableArray())
|
|
386
|
-
putString("publicKeyEncoded", response.publicKeyEncoded)
|
|
387
|
-
putString("resolvedDerviationPath", response.resolvedDerivationPath.toString())
|
|
388
|
-
}
|
|
389
|
-
}))
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
545
|
+
}
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useRef, useEffect } from 'react';
|
|
2
|
+
import { Platform, NativeModules, PermissionsAndroid, NativeEventEmitter } from 'react-native';
|
|
3
|
+
|
|
4
|
+
// EVENTS
|
|
5
|
+
// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
|
|
6
|
+
const SeedVaultEventType = {
|
|
7
|
+
AuthorizeSeedAccess: "SeedAuthorized",
|
|
8
|
+
CreateNewSeed: "NewSeedCreated",
|
|
9
|
+
ImportExistingSeed: "ExistingSeedImported",
|
|
10
|
+
PayloadsSigned: "PayloadsSigned",
|
|
11
|
+
GetPublicKeys: "PublicKeysEvent",
|
|
12
|
+
ContentChange: "SeedVaultContentChange"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const SeedPurpose = {
|
|
16
|
+
SignSolanaTransaction: 0,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/******************************************************************************
|
|
20
|
+
Copyright (c) Microsoft Corporation.
|
|
21
|
+
|
|
22
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
23
|
+
purpose with or without fee is hereby granted.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
26
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
27
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
28
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
29
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
30
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
31
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
32
|
+
***************************************************************************** */
|
|
33
|
+
|
|
34
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
35
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
36
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
37
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
38
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
39
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
40
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const LINKING_ERROR = `The package 'solana-mobile-seed-vault-lib' doesn't seem to be linked. Make sure: \n\n` +
|
|
45
|
+
'- You rebuilt the app after installing the package\n' +
|
|
46
|
+
'- If you are using Lerna workspaces\n' +
|
|
47
|
+
' - You have added `@solana-mobile/seed-vault-lib` as an explicit dependency, and\n' +
|
|
48
|
+
' - You have added `@solana-mobile/seed-vault-lib` to the `nohoist` section of your package.json\n' +
|
|
49
|
+
'- You are not using Expo managed workflow\n';
|
|
50
|
+
const SolanaMobileSeedVaultLib = Platform.OS === 'android' && NativeModules.SolanaMobileSeedVaultLib
|
|
51
|
+
? NativeModules.SolanaMobileSeedVaultLib
|
|
52
|
+
: new Proxy({}, {
|
|
53
|
+
get() {
|
|
54
|
+
throw new Error(Platform.OS !== 'android'
|
|
55
|
+
? 'The package `solana-mobile-seed-vault-lib` is only compatible with React Native Android'
|
|
56
|
+
: LINKING_ERROR);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const SeedVaultPermissionAndroid = 'com.solanamobile.seedvault.ACCESS_SEED_VAULT';
|
|
60
|
+
const checkSeedVaultPermission = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const granted = yield PermissionsAndroid.check(SeedVaultPermissionAndroid);
|
|
62
|
+
if (!granted) {
|
|
63
|
+
throw new Error('You do not have permission to access Seed Vault. You must request permission to use Seed Vault.');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const checkIsSeedVaultAvailable = (allowSimulated = false) => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const seedVaultAvailable = yield SolanaMobileSeedVaultLib.isSeedVaultAvailable(allowSimulated);
|
|
68
|
+
if (!seedVaultAvailable) {
|
|
69
|
+
throw new Error(allowSimulated
|
|
70
|
+
? 'Seed Vault is not available on this device, please install the Seed Vault Simulator'
|
|
71
|
+
: 'Seed Vault is not available on this device');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
const SEED_VAULT_EVENT_BRIDGE_NAME = 'SeedVaultEventBridge';
|
|
75
|
+
function useSeedVault(handleSeedVaultEvent, handleContentChange) {
|
|
76
|
+
const seedVaultEventHandler = useRef(handleSeedVaultEvent);
|
|
77
|
+
const contentChangeHandler = useRef(handleContentChange);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
seedVaultEventHandler.current = handleSeedVaultEvent;
|
|
80
|
+
contentChangeHandler.current = handleContentChange;
|
|
81
|
+
});
|
|
82
|
+
checkIsSeedVaultAvailable(true);
|
|
83
|
+
checkSeedVaultPermission();
|
|
84
|
+
// Start native event listener
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const seedVaultEventEmitter = new NativeEventEmitter();
|
|
87
|
+
const listener = seedVaultEventEmitter.addListener(SEED_VAULT_EVENT_BRIDGE_NAME, (nativeEvent) => {
|
|
88
|
+
if (isContentChangeEvent(nativeEvent)) {
|
|
89
|
+
contentChangeHandler.current(nativeEvent);
|
|
90
|
+
}
|
|
91
|
+
else if (isSeedVaultEvent(nativeEvent)) {
|
|
92
|
+
seedVaultEventHandler.current(nativeEvent);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.warn('Unexpected native event type');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return () => {
|
|
99
|
+
listener.remove();
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
}
|
|
103
|
+
function isSeedVaultEvent(nativeEvent) {
|
|
104
|
+
return Object.values(SeedVaultEventType).includes(nativeEvent.__type);
|
|
105
|
+
}
|
|
106
|
+
function isContentChangeEvent(nativeEvent) {
|
|
107
|
+
return nativeEvent.__type == SeedVaultEventType.ContentChange;
|
|
108
|
+
}
|
|
109
|
+
const SeedVault = SolanaMobileSeedVaultLib;
|
|
110
|
+
|
|
111
|
+
export { SeedPurpose, SeedVault, SeedVaultEventType, SeedVaultPermissionAndroid, useSeedVault };
|
package/lib/esm/index.native.js
CHANGED
|
@@ -12,6 +12,10 @@ const SeedVaultEventType = {
|
|
|
12
12
|
ContentChange: "SeedVaultContentChange"
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const SeedPurpose = {
|
|
16
|
+
SignSolanaTransaction: 0,
|
|
17
|
+
};
|
|
18
|
+
|
|
15
19
|
/******************************************************************************
|
|
16
20
|
Copyright (c) Microsoft Corporation.
|
|
17
21
|
|
|
@@ -83,10 +87,8 @@ function useSeedVault(handleSeedVaultEvent, handleContentChange) {
|
|
|
83
87
|
const listener = seedVaultEventEmitter.addListener(SEED_VAULT_EVENT_BRIDGE_NAME, (nativeEvent) => {
|
|
84
88
|
if (isContentChangeEvent(nativeEvent)) {
|
|
85
89
|
contentChangeHandler.current(nativeEvent);
|
|
86
|
-
handleContentChange(nativeEvent);
|
|
87
90
|
}
|
|
88
91
|
else if (isSeedVaultEvent(nativeEvent)) {
|
|
89
|
-
handleSeedVaultEvent(nativeEvent);
|
|
90
92
|
seedVaultEventHandler.current(nativeEvent);
|
|
91
93
|
}
|
|
92
94
|
else {
|
|
@@ -104,5 +106,6 @@ function isSeedVaultEvent(nativeEvent) {
|
|
|
104
106
|
function isContentChangeEvent(nativeEvent) {
|
|
105
107
|
return nativeEvent.__type == SeedVaultEventType.ContentChange;
|
|
106
108
|
}
|
|
109
|
+
const SeedVault = SolanaMobileSeedVaultLib;
|
|
107
110
|
|
|
108
|
-
export { SeedVaultEventType, SeedVaultPermissionAndroid, useSeedVault };
|
|
111
|
+
export { SeedPurpose, SeedVault, SeedVaultEventType, SeedVaultPermissionAndroid, useSeedVault };
|