Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Native Lib Updates #235

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 14 additions & 39 deletions fakewalletreact/components/SeedVaultExampleUsage.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,12 @@
import React, { useEffect, useState } from 'react';
import { View, PermissionsAndroid } from 'react-native';
import { View } from 'react-native';
import { Button, Text } from 'react-native-paper';
import { Account, Seed, SeedVault, SeedVaultPermissionAndroid, useSeedVault } from "@solana-mobile/seed-vault-lib";
import { Account, Seed, SeedVault, useSeedVault } from "@solana-mobile/seed-vault-lib";

export default function SeedVaultExampleUsage() {
const [hasUnauthorizedSeeds, setHasUnauthorizedSeeds] = useState(false);
const [authorizedSeeds, setAuthorizedSeeds] = useState<Seed[]>([]);

useEffect(() => {

async function requestSeedVaultPermission() {
try {
const granted = await PermissionsAndroid.request(
SeedVaultPermissionAndroid,
{
title: 'Seed Vault Permission',
message:
'This app needs your permission to access Seed Vault',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('You can use Seed Vault');
} else {
console.log('Seed Vault permission denied');
}
} catch (err) {
console.warn(err);
}
};

requestSeedVaultPermission();
}, []);

useEffect(() => {

async function updateHasUnauthorizedSeeds() {
Expand All @@ -50,10 +22,10 @@ export default function SeedVaultExampleUsage() {
async function getAuthorizedSeeds() {
const authorizedSeeds = await SeedVault.getAuthorizedSeeds();
authorizedSeeds.forEach(async (authorizedSeed: Seed) => {
console.log('Authorized seed = ' + authorizedSeed.name + ', ' + authorizedSeed.authToken)
const accounts = await SeedVault.getAccounts(authorizedSeed.authToken);
console.log('Authorized seed = ' + authorizedSeed.name + ', ' + authorizedSeed.authToken);
const accounts = await SeedVault.getUserWallets(authorizedSeed.authToken);
accounts.forEach((account: Account) => {
console.log(' account: ' + account.name + ', ' + account.publicKeyEncoded + ', ' + account.derivationPath)
console.log(' account: ' + account.name + ', ' + account.publicKeyEncoded + ', ' + account.derivationPath);
})
});
setAuthorizedSeeds(authorizedSeeds)
Expand All @@ -78,20 +50,23 @@ export default function SeedVaultExampleUsage() {
onPress={async () => {
const result = await SeedVault.authorizeNewSeed();
console.log(`New seed authorized! auth token: ${result.authToken}`);
const authorizedSeeds = await SeedVault.getAuthorizedSeeds();
setAuthorizedSeeds(authorizedSeeds);
}}>
Authorize another seed for PURPOSE_SIGN_SOLANA_TRANSACTION
</Button> : null}
<Button
onPress={async () => {
const authorizedSeeds = await SeedVault.getAuthorizedSeeds()
console.log(authorizedSeeds)
const authorizedSeeds = await SeedVault.getAuthorizedSeeds();
setAuthorizedSeeds(authorizedSeeds);
console.log(authorizedSeeds);
}}>
Get Authorized Seeds
</Button>
{authorizedSeeds.length ? <Button
onPress={async () => {
const seed = authorizedSeeds[0]
const accounts = await SeedVault.getAccounts(seed.authToken)
const accounts = await SeedVault.getUserWallets(seed.authToken)
console.log(accounts)
if (accounts.length && accounts[0].derivationPath) {
try {
Expand All @@ -108,7 +83,7 @@ export default function SeedVaultExampleUsage() {
{authorizedSeeds.length ? <Button
onPress={async () => {
const seed = authorizedSeeds[0]
const accounts = await SeedVault.getAccounts(seed.authToken)
const accounts = await SeedVault.getUserWallets(seed.authToken)
console.log(accounts)
if (accounts.length && accounts[0].derivationPath) {
try {
Expand All @@ -125,7 +100,7 @@ export default function SeedVaultExampleUsage() {
{authorizedSeeds.length ? <Button
onPress={async () => {
const seed = authorizedSeeds[0]
const accounts = await SeedVault.getAccounts(seed.authToken)
const accounts = await SeedVault.getUserWallets(seed.authToken)
console.log(accounts)
if (accounts.length && accounts[0].derivationPath) {
try {
Expand All @@ -146,7 +121,7 @@ export default function SeedVaultExampleUsage() {
{authorizedSeeds.length ? <Button
onPress={async () => {
const seed = authorizedSeeds[0]
const accounts = await SeedVault.getAccounts(seed.authToken)
const accounts = await SeedVault.getUserWallets(seed.authToken)
console.log(accounts)
if (accounts.length && accounts[0].derivationPath) {
try {
Expand Down
91 changes: 65 additions & 26 deletions fakewalletreact/screens/MainScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
import React, { useEffect, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import { Appbar, Text } from 'react-native-paper';
import { SeedVault } from "@solana-mobile/seed-vault-lib";
import { View, StyleSheet, PermissionsAndroid } from 'react-native';
import { Appbar, Button, Text } from 'react-native-paper';
import { SeedVault, SeedVaultPermissionAndroid } from "@solana-mobile/seed-vault-lib";
import SeedVaultExampleUsage from '../components/SeedVaultExampleUsage';

export default function MainScreen() {
const [seedVaultAvailable, setSeedVAultAvailable] = useState(false);

useEffect(() => {

async function updateIsSeedVaultAvailable() {
const seedVaultAvailable = await SeedVault.isSeedVaultAvailable(true);
setSeedVAultAvailable(seedVaultAvailable)
}

updateIsSeedVaultAvailable()
}, []);

return (
<>
<Appbar.Header elevated mode="center-aligned">
<Appbar.Content title="React Native SV Wallet" />
</Appbar.Header>
<View style={styles.container}>
{seedVaultAvailable ? <SeedVaultExampleUsage /> :
<Text style={styles.text}>Seed Vault is not available on this device, please install the seed vault simulator</Text>}
</View>
</>
);
const [seedVaultAvailable, setSeedVaultAvailable] = useState(false);
const [seedVaultPermissionGranted, setSeedVaultPermissionGranted] = useState(false);

useEffect(() => {

async function checkSeedVaultPermission() {
try {
const granted = await PermissionsAndroid.check(SeedVaultPermissionAndroid);
setSeedVaultPermissionGranted(granted)
} catch (err) {
console.warn(err);
}
};

checkSeedVaultPermission();
}, []);

useEffect(() => {

async function updateIsSeedVaultAvailable() {
const seedVaultAvailable = await SeedVault.isSeedVaultAvailable(true);
setSeedVaultAvailable(seedVaultAvailable)
}

updateIsSeedVaultAvailable()
}, []);

return (
<>
<Appbar.Header elevated mode="center-aligned">
<Appbar.Content title="React Native SV Wallet" />
</Appbar.Header>
<View style={styles.container}>
{!seedVaultAvailable ?
<Text style={styles.text}>
Seed Vault is not available on this device, please install the seed vault simulator
</Text>
: !seedVaultPermissionGranted ?
<Button onPress={async () => {
try {
const granted = await PermissionsAndroid.request(
SeedVaultPermissionAndroid,
{
title: 'Seed Vault Permission',
message:
'This app needs your permission to access Seed Vault',
buttonNeutral: 'Ask Me Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
},
);
setSeedVaultPermissionGranted(granted === PermissionsAndroid.RESULTS.GRANTED)
} catch (err) {
console.warn(err);
}
}}>
Grant Seed Vault Permission
</Button>
: <SeedVaultExampleUsage/>}
</View>
</>
);
}

const styles = StyleSheet.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ internal fun SeedVaultEvent.toWritableMap() : WritableMap = Arguments.createMap(
Arguments.createMap().apply {
putArray("publicKey", response.publicKey.toWritableArray())
putString("publicKeyEncoded", response.publicKeyEncoded)
putString("resolvedDerviationPath", response.resolvedDerivationPath.toString())
putString("resolvedDerivationPath", response.resolvedDerivationPath.toString())
}
}))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.solanamobile.seedvault.reactnative

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager
import android.database.ContentObserver
import android.net.Uri
import android.os.Bundle
Expand Down Expand Up @@ -43,7 +44,8 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
init {
reactContext.addActivityEventListener(mActivityEventListener)

if (SeedVault.isAvailable(reactContext, true)) {
if (reactContext.checkSelfPermission(WalletContractV1.PERMISSION_ACCESS_SEED_VAULT) == PackageManager.PERMISSION_GRANTED &&
SeedVault.isAvailable(reactContext, true)) {
observeSeedVaultContentChanges()
}
}
Expand All @@ -64,9 +66,17 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)

@ReactMethod
fun hasUnauthorizedSeeds(promise: Promise) {
hasUnauthorizedSeedsForPurpose(WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION, promise)
}

@ReactMethod
fun hasUnauthorizedSeedsForPurpose(purpose: Double, promise: Promise) {
hasUnauthorizedSeedsForPurpose(purpose.toInt(), promise)
}

private fun hasUnauthorizedSeedsForPurpose(purpose: Int, promise: Promise) {
val application = reactContext.currentActivity?.application!!
val hasUnauthorizedSeeds = Wallet.hasUnauthorizedSeedsForPurpose(application,
WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION)
val hasUnauthorizedSeeds = Wallet.hasUnauthorizedSeedsForPurpose(application, purpose)
promise.resolve(hasUnauthorizedSeeds)
}

Expand Down Expand Up @@ -110,11 +120,10 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
}

@ReactMethod
fun getAccounts(authToken: String, promise: Promise) {
fun getAccounts(authToken: String, filterOnColumn: String, value: Any, promise: Promise) {
val application = reactContext.currentActivity?.application!!
val accountsCursor = Wallet.getAccounts(application, authToken.toLong(),
WalletContractV1.ACCOUNTS_ALL_COLUMNS,
WalletContractV1.ACCOUNTS_ACCOUNT_IS_USER_WALLET, "1")!!
WalletContractV1.ACCOUNTS_ALL_COLUMNS, filterOnColumn, value)!!

val accounts = mutableListOf<Account>()

Expand All @@ -134,6 +143,11 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
promise.resolve(accounts.toWritableArray())
}

@ReactMethod
fun getUserWallets(authToken: String, promise: Promise) {
getAccounts(authToken, WalletContractV1.ACCOUNTS_ACCOUNT_IS_USER_WALLET, "1", promise)
}

@ReactMethod
fun requestAuthorizeNewSeed() {
Log.d(TAG, "Requesting authorization for a new seed...")
Expand Down Expand Up @@ -230,6 +244,16 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
Log.d(TAG, "Account name updated (to '$name')")
}

@ReactMethod
fun updateAccountIsUserWallet(authToken: String, accountId: String, isUserWallet: Boolean) {
Wallet.updateAccountIsUserWallet(reactContext, authToken.toLong(), accountId.toLong(), isUserWallet)
}

@ReactMethod
fun updateAccountIsValid(authToken: String, accountId: String, isValid: Boolean) {
Wallet.updateAccountIsValid(reactContext, authToken.toLong(), accountId.toLong(), isValid)
}

@ReactMethod
fun requestSignMessage(authToken: String, derivationPath: String, message: ReadableArray) {
requestSignMessages(authToken, listOf(SigningRequest(message.toByteArray(), arrayListOf(Uri.parse(derivationPath)))))
Expand Down Expand Up @@ -421,7 +445,7 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
Arguments.createMap().apply {
putArray("publicKey", response.publicKey.toWritableArray())
putString("publicKeyEncoded", response.publicKeyEncoded)
putString("resolvedDerviationPath", response.resolvedDerivationPath.toString())
putString("resolvedDerivationPath", response.resolvedDerivationPath.toString())
}
}), null
)
Expand All @@ -432,6 +456,21 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
}
}

@ReactMethod
fun resolveDerivationPath(derivationPath: String, promise: Promise) {
resolveDerivationPath(Uri.parse(derivationPath), WalletContractV1.PURPOSE_SIGN_SOLANA_TRANSACTION, promise)
}

@ReactMethod
fun resolveDerivationPathForPurpose(derivationPath: String, purpose: Double, promise: Promise) {
resolveDerivationPath(Uri.parse(derivationPath), purpose.toInt(), promise)
}

private fun resolveDerivationPath(derivationPath: Uri, purpose: Int, promise: Promise) {
val resolvedDerivationPath = Wallet.resolveDerivationPath(reactContext, derivationPath, purpose)
promise.resolve(resolvedDerivationPath.toString())
}

private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap? = null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
Expand All @@ -455,7 +494,6 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext)
throw NotImplementedError("Stub for legacy onChange")

override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int) {
Log.d(TAG, "Received change notification for $uris (flags=$flags); refreshing viewmodel")
sendEvent(reactContext, SEED_VAULT_CONTENT_CHANGE_EVENT_BRIDGE_NAME,
params = Arguments.createMap().apply {
putString("__type", "SeedVaultContentChange")
Expand Down
14 changes: 13 additions & 1 deletion js/packages/seed-vault/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,39 @@ export type SigningResult = Readonly<{

interface AuthorizeSeedAPI {
hasUnauthorizedSeeds(): boolean
hasUnauthorizedSeedsForPurpose(purpose: SeedPurpose): boolean
getAuthorizedSeeds(): Seed[]
authorizeNewSeed(): {authToken: AuthToken}
deauthorizeSeed(authToken: AuthToken): void
}

interface AccountAPI {
getAccounts(authToken: AuthToken): Account[]
getAccounts(authToken: AuthToken, filterOnColumn: string, value: any): Account[]
getUserWallets(authToken: AuthToken): Account[]
updateAccountName(authToken: AuthToken, accountId: number, name?: string): void
updateAccountIsUserWallet(authToken: AuthToken, accountId: number, isUserWallet: boolean): void
updateAccountIsValid(authToken: AuthToken, accountId: number, isValid: boolean): void
}

interface CreateNewSeedAPI {
createNewSeed(): {authToken: AuthToken}
}

// TODO
// interface ImplementationLimitsAPI {
// getImplementationLimits(): void
// getImplementationLimitsForPurpose()
// }

interface ImportExistingSeedAPI {
importExistingSeed(): {authToken: AuthToken}
}

interface PublicKeyAPI {
getPublicKey(authToken: AuthToken, derivationPath: DerivationPath): SeedPublicKey
getPublicKeys(authToken: AuthToken, derivationPaths: DerivationPath[]): SeedPublicKey[]
resolveDerivationPath(derivationPath: DerivationPath): DerivationPath
resolveDerivationPathForPurpose(derivationPath: DerivationPath, purpose: SeedPurpose): DerivationPath
}

interface SignMessagesAPI {
Expand Down