@solana-mobile/seed-vault-lib 0.1.0 → 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.
package/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ lib/
2
+ android/build
package/README.md CHANGED
@@ -1,3 +1,97 @@
1
1
  # `@solana-mobile/seedvaultlib`
2
2
 
3
3
  A React Native wrapper of the Seed Vault SDK.
4
+
5
+ # Usage
6
+
7
+ ### Check if Seed Vault is Available
8
+ We first need to check if the seed vault service is available on the device. Currently only Saga implementes a seed vault.
9
+ ```javascript
10
+ const allowSimulated = false; // use true to allow simulated seed vault (for dev/testing)
11
+ const seedVaultAvailable = await SolanaMobileSeedVaultLib.isSeedVaultAvailable(allowSimulated);
12
+ if (!seedVaultAvailable) {
13
+ // seed vault is not available, we cant use it
14
+ }
15
+ ```
16
+
17
+ ### Request Seed Vault Permission
18
+ Before we can interact with Seed vault, we must request permission for our app to use Seed Vault.
19
+ ```javascript
20
+ import { PermissionsAndroid } from 'react-native';
21
+ import { SeedVaultPermissionAndroid } from '@solana-mobile/seed-vault-lib';
22
+
23
+ const permissionResult = await PermissionsAndroid.request(
24
+ SeedVaultPermissionAndroid,
25
+ { // customize verbage here to your liking
26
+ title: 'Seed Vault Permission',
27
+ message:
28
+ 'This app needs your permission to access Seed Vault',
29
+ buttonNeutral: 'Ask Me Later',
30
+ buttonNegative: 'Cancel',
31
+ buttonPositive: 'OK',
32
+ },
33
+ );
34
+
35
+ if (permissionResult === PermissionsAndroid.RESULTS.GRANTED) {
36
+ // we can use seed vault, continue
37
+ } else {
38
+ // permission was denied, fallback
39
+ }
40
+ ```
41
+
42
+ Read more about requesting Android Permission in React Natvie [here](https://reactnative.dev/docs/permissionsandroid).
43
+
44
+ ### Authorize a Seed
45
+ Before our app can access any seeds in the seed vault, we must first request authorization for our app to use a seed from the user.
46
+ ```javascript
47
+ import { SeedVault } from "@solana-mobile/seed-vault-lib";
48
+
49
+ const result = await SeedVault.authorizeNewSeed();
50
+ console.log(`New seed authorized! auth token: ${result.authToken}`);
51
+ ```
52
+
53
+ ### Retreive a list of Authorized Seeds
54
+ To retreive a list of all the seeds our app has been authorized to use, call `getAuthorizedSeeds()`.
55
+ ```javascript
56
+ const authorizedSeeds = await SeedVault.getAuthorizedSeeds()
57
+ ```
58
+
59
+ This will return a list of `Seed` objects with the following structure
60
+ ```
61
+ {
62
+ authToken: number;
63
+ name: string;
64
+ purpose: int;
65
+ }
66
+ ```
67
+
68
+ ### Get Accounts for a given seed
69
+ Once we have obtained an authorized seed, we can get a list of all the accounts (public keys) assocaited with that seed
70
+ ```javascript
71
+ const seed = authorizedSeeds[0]
72
+ const accounts = await SeedVault.getAccounts(seed.authToken)
73
+ ```
74
+
75
+ ### Retreive the PublicKey of an Account
76
+ Once we have obtained an authorized seed, we can get a list of all the accounts (public keys) assocaited with that seed
77
+ ```javascript
78
+ const account = account[0]
79
+ const publicKey = await SeedVault.getPublicKey(seed.authToken, account.derivationPath);
80
+ // can now build transaction using the public key
81
+ ```
82
+
83
+ This will return a `SeedPublicKey` object with the following structure
84
+ ```
85
+ {
86
+ publicKey: Uint8Array;
87
+ publicKeyEncoded: string;
88
+ resolvedDerivationPath: string;
89
+ }
90
+ ```
91
+
92
+ ### Sign a Payload
93
+ Once we have obtained an account, we can request signatures from seed vault for that account:
94
+ ```javascript
95
+ SeedVault.signMessage(seed.authToken, account.derivationPath, messageBytes);
96
+ SeedVault.signTransaction(seed.authToken, account.derivationPath, transactionByteArray);
97
+ ```
@@ -0,0 +1,14 @@
1
+ # OSX
2
+ #
3
+ .DS_Store
4
+
5
+ # Android/IJ
6
+ #
7
+ .classpath
8
+ .cxx
9
+ .gradle
10
+ .idea
11
+ .project
12
+ .settings
13
+ local.properties
14
+ android.iml
@@ -1,6 +1,7 @@
1
1
  package com.solanamobile.seedvault.model
2
2
 
3
3
  import android.net.Uri
4
+ import android.util.Base64
4
5
  import com.solanamobile.seedvault.SigningRequest
5
6
  import kotlinx.serialization.KSerializer
6
7
  import kotlinx.serialization.Serializable
@@ -11,7 +12,7 @@ import kotlinx.serialization.encoding.Encoder
11
12
 
12
13
  @Serializable
13
14
  data class SigningRequestSurrogate(
14
- val payload: ByteArray,
15
+ val payload: String,
15
16
  val requestedSignatures: List<@Serializable(with = UriAsStringSerializer::class) Uri>
16
17
  )
17
18
 
@@ -20,13 +21,14 @@ object SigningRequestSerializer : KSerializer<SigningRequest> {
20
21
 
21
22
  override fun serialize(encoder: Encoder, value: SigningRequest) {
22
23
  encoder.encodeSerializableValue(SigningRequestSurrogate.serializer(),
23
- SigningRequestSurrogate(value.payload, value.requestedSignatures)
24
+ SigningRequestSurrogate(Base64.encodeToString(value.payload, Base64.NO_WRAP), value.requestedSignatures)
24
25
  )
25
26
  }
26
27
 
27
28
  override fun deserialize(decoder: Decoder): SigningRequest =
28
29
  decoder.decodeSerializableValue(SigningRequestSurrogate.serializer()).let {
29
- SigningRequest(it.payload, ArrayList(it.requestedSignatures))
30
+ val payload = Base64.decode(it.payload, Base64.DEFAULT)
31
+ SigningRequest(payload, ArrayList(it.requestedSignatures))
30
32
  }
31
33
  }
32
34
 
@@ -1,13 +1,27 @@
1
1
  package com.solanamobile.seedvault.reactnative
2
2
 
3
3
  import com.facebook.react.bridge.*
4
+ import kotlinx.serialization.json.JsonArray
5
+ import kotlinx.serialization.json.JsonNull
6
+ import kotlinx.serialization.json.JsonObject
7
+ import kotlinx.serialization.json.JsonPrimitive
8
+ import kotlinx.serialization.json.add
9
+ import kotlinx.serialization.json.boolean
10
+ import kotlinx.serialization.json.booleanOrNull
11
+ import kotlinx.serialization.json.buildJsonArray
12
+ import kotlinx.serialization.json.buildJsonObject
13
+ import kotlinx.serialization.json.double
14
+ import kotlinx.serialization.json.doubleOrNull
15
+ import kotlinx.serialization.json.int
16
+ import kotlinx.serialization.json.intOrNull
17
+ import kotlinx.serialization.json.put
4
18
 
5
19
  // Converts a React ReadableArray into a Kotlin ByteArray.
6
20
  // Expects ReadableArray to be an Array of ints, where each int represents a byte.
7
21
  internal fun ReadableArray.toByteArray(): ByteArray =
8
- ByteArray(size()) { index ->
9
- getInt(index).toByte()
10
- }
22
+ ByteArray(size()) { index ->
23
+ getInt(index).toByte()
24
+ }
11
25
 
12
26
  // Converts a Kotlin ByteArray into a React ReadableArray of ints.
13
27
  internal fun ByteArray.toWritableArray(): ReadableArray =
@@ -15,4 +29,71 @@ internal fun ByteArray.toWritableArray(): ReadableArray =
15
29
  forEach {
16
30
  this.pushInt(it.toInt())
17
31
  }
18
- }
32
+ }
33
+
34
+ internal fun ReadableMap.toJson(): JsonObject = buildJsonObject {
35
+ keySetIterator().let { iterator ->
36
+ while (iterator.hasNextKey()) {
37
+ val key = iterator.nextKey()
38
+ when (getType(key)) {
39
+ ReadableType.Array -> put(key, getArray(key)!!.toJson())
40
+ ReadableType.Boolean -> put(key, getBoolean(key))
41
+ ReadableType.Map -> put(key, getMap(key)!!.toJson())
42
+ ReadableType.Null -> put(key, JsonNull)
43
+ ReadableType.Number -> put(key, getDouble(key))
44
+ ReadableType.String -> put(key, getString(key))
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ internal fun ReadableArray.toJson(): JsonArray = buildJsonArray {
51
+ for (i in 0 until size()) {
52
+ when (getType(i)) {
53
+ ReadableType.Array -> add(getArray(i)!!.toJson())
54
+ ReadableType.Boolean -> add(getBoolean(i))
55
+ ReadableType.Map -> add(getMap(i)!!.toJson())
56
+ ReadableType.Null -> {}
57
+ ReadableType.Number -> add(getDouble(i))
58
+ ReadableType.String -> add(getString(i))
59
+ }
60
+ }
61
+ }
62
+
63
+ internal fun JsonObject.toReadableMap(): ReadableMap {
64
+ val map: WritableMap = WritableNativeMap()
65
+ val iterator = keys.iterator()
66
+ while (iterator.hasNext()) {
67
+ val key = iterator.next()
68
+ when (val value = this[key]) {
69
+ is JsonPrimitive -> when {
70
+ value.booleanOrNull != null -> map.putBoolean(key, value.boolean)
71
+ value.doubleOrNull != null -> map.putDouble(key, value.double)
72
+ value.intOrNull != null -> map.putInt(key, value.int)
73
+ value.isString -> map.putString(key, value.content)
74
+ }
75
+ is JsonArray -> map.putArray(key, value.toReadableArray())
76
+ is JsonObject -> map.putMap(key, value.toReadableMap())
77
+ else -> map.putString(key, value.toString())
78
+ }
79
+ }
80
+ return map
81
+ }
82
+
83
+ internal fun JsonArray.toReadableArray(): ReadableArray {
84
+ val array: WritableArray = WritableNativeArray()
85
+ for (i in 0 until size) {
86
+ when (val value = this[i]) {
87
+ is JsonPrimitive -> when {
88
+ value.booleanOrNull != null -> array.pushBoolean(value.boolean)
89
+ value.doubleOrNull != null -> array.pushDouble(value.double)
90
+ value.intOrNull != null -> array.pushInt(value.int)
91
+ value.isString -> array.pushString(value.toString())
92
+ }
93
+ is JsonArray -> array.pushArray(value.toReadableArray())
94
+ is JsonObject -> array.pushMap(value.toReadableMap())
95
+ else -> array.pushString(value.toString())
96
+ }
97
+ }
98
+ return array
99
+ }
@@ -0,0 +1,81 @@
1
+ package com.solanamobile.seedvault.reactnative
2
+
3
+ import android.net.Uri
4
+ import com.facebook.react.bridge.*
5
+ import com.solanamobile.seedvault.*
6
+ import kotlinx.serialization.Serializable
7
+
8
+ interface RNWritable {
9
+ fun toWritableMap(): WritableMap
10
+ }
11
+
12
+ data class Account(
13
+ @WalletContractV1.AccountId val id: Long,
14
+ val name: String,
15
+ val derivationPath: Uri,
16
+ val publicKeyEncoded: String
17
+ ) : RNWritable {
18
+ override fun toWritableMap() = Arguments.createMap().apply {
19
+ putString("id", "$id")
20
+ putString("name", name)
21
+ putString("derivationPath", "$derivationPath")
22
+ putString("publicKeyEncoded", publicKeyEncoded)
23
+ }
24
+ }
25
+
26
+ data class Seed(
27
+ @WalletContractV1.AuthToken val authToken: Long,
28
+ val name: String,
29
+ @WalletContractV1.Purpose val purpose: Int,
30
+ // val accounts: List<Account> = listOf()
31
+ ) : RNWritable {
32
+ override fun toWritableMap() = Arguments.createMap().apply {
33
+ putString("authToken", "$authToken")
34
+ putString("name", name)
35
+ putInt("purpose", purpose)
36
+ }
37
+ }
38
+
39
+ fun List<RNWritable>.toWritableArray() = Arguments.createArray().apply {
40
+ forEach { writable ->
41
+ pushMap(writable.toWritableMap())
42
+ }
43
+ }
44
+
45
+ @Serializable
46
+ sealed interface SeedVaultEvent {
47
+ sealed class SeedEvent(val authToken: Long) : SeedVaultEvent
48
+ class SeedAuthorized(authToken: Long) : SeedEvent(authToken)
49
+ class NewSeedCreated(authToken: Long) : SeedEvent(authToken)
50
+ class ExistingSeedImported(authToken: Long) : SeedEvent(authToken)
51
+
52
+ data class PayloadsSigned(val result: List<SigningResponse>) : SeedVaultEvent
53
+
54
+ data class PublicKeysEvent(val result: List<PublicKeyResponse>) : SeedVaultEvent
55
+ }
56
+
57
+ internal fun SeedVaultEvent.toWritableMap() : WritableMap = Arguments.createMap().apply {
58
+ putString("__type", this@toWritableMap::class.simpleName)
59
+ when (this@toWritableMap) {
60
+ is SeedVaultEvent.SeedEvent -> {
61
+ putString("authToken", authToken.toString())
62
+ }
63
+ is SeedVaultEvent.PayloadsSigned -> {
64
+ putArray("result", Arguments.makeNativeArray(result.map { response ->
65
+ Arguments.createMap().apply {
66
+ putArray("signatures", Arguments.makeNativeArray(response.signatures.map { it.toWritableArray() }))
67
+ putArray("resolvedDerivationPaths", Arguments.makeNativeArray(response.resolvedDerivationPaths.map { it.toString() }))
68
+ }
69
+ }))
70
+ }
71
+ is SeedVaultEvent.PublicKeysEvent -> {
72
+ putArray("result", Arguments.makeNativeArray(result.map { response ->
73
+ Arguments.createMap().apply {
74
+ putArray("publicKey", response.publicKey.toWritableArray())
75
+ putString("publicKeyEncoded", response.publicKeyEncoded)
76
+ putString("resolvedDerviationPath", response.resolvedDerivationPath.toString())
77
+ }
78
+ }))
79
+ }
80
+ }
81
+ }