Skip to content

Commit

Permalink
React Native Lib Updates (#235)
Browse files Browse the repository at this point in the history
* update Accounts API

* add some missing methods

* fakewallet updates
  • Loading branch information
Funkatronics authored Apr 12, 2024
1 parent 508d52c commit daee4a6
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 75 deletions.
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

0 comments on commit daee4a6

Please sign in to comment.