From daee4a6594238a137a2a224eddd2efd36d11b550 Mon Sep 17 00:00:00 2001 From: Marco Martinez Date: Fri, 12 Apr 2024 12:23:18 -0600 Subject: [PATCH] React Native Lib Updates (#235) * update Accounts API * add some missing methods * fakewallet updates --- .../components/SeedVaultExampleUsage.tsx | 53 +++-------- fakewalletreact/screens/MainScreen.tsx | 91 +++++++++++++------ .../reactnative/SerializationUtils.kt | 2 +- .../SolanaMobileSeedVaultLibModule.kt | 54 +++++++++-- js/packages/seed-vault/src/types.ts | 14 ++- 5 files changed, 139 insertions(+), 75 deletions(-) diff --git a/fakewalletreact/components/SeedVaultExampleUsage.tsx b/fakewalletreact/components/SeedVaultExampleUsage.tsx index baabedd..d693565 100644 --- a/fakewalletreact/components/SeedVaultExampleUsage.tsx +++ b/fakewalletreact/components/SeedVaultExampleUsage.tsx @@ -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([]); - 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() { @@ -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) @@ -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 : null} {authorizedSeeds.length ? + : } + + + ); } const styles = StyleSheet.create({ diff --git a/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SerializationUtils.kt b/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SerializationUtils.kt index 7a2ee30..2bf1e93 100644 --- a/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SerializationUtils.kt +++ b/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SerializationUtils.kt @@ -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()) } })) } diff --git a/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt b/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt index 381292e..958d4f2 100644 --- a/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt +++ b/js/packages/seed-vault/android/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt @@ -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 @@ -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() } } @@ -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) } @@ -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() @@ -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...") @@ -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))))) @@ -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 ) @@ -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) @@ -455,7 +494,6 @@ class SolanaMobileSeedVaultLibModule(val reactContext: ReactApplicationContext) throw NotImplementedError("Stub for legacy onChange") override fun onChange(selfChange: Boolean, uris: Collection, 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") diff --git a/js/packages/seed-vault/src/types.ts b/js/packages/seed-vault/src/types.ts index 175a983..eabd226 100644 --- a/js/packages/seed-vault/src/types.ts +++ b/js/packages/seed-vault/src/types.ts @@ -48,20 +48,30 @@ 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} } @@ -69,6 +79,8 @@ interface ImportExistingSeedAPI { 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 {