@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 +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 +249 -94
- package/lib/esm/index.js +111 -0
- package/lib/esm/index.native.js +6 -3
- package/lib/types/index.d.ts +151 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.native.d.ts +71 -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 +96 -0
- package/src/useSeedVault.ts +98 -0
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +8 -0
- package/yarn.lock +611 -0
package/.gitignore
ADDED
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
|
+
```
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
+
}
|