diff --git a/App.tsx b/App.tsx index f46a152dc7..43f7d00a7c 100644 --- a/App.tsx +++ b/App.tsx @@ -2,10 +2,10 @@ import * as SplashScreen from 'expo-splash-screen' import React from 'react' import { Provider as StoreProvider } from 'react-redux' import './_shim' -import { Logging } from './app/api/logging' +import { Logging } from './app/api' import { NetworkProvider } from './app/contexts/NetworkContext' import { PlaygroundProvider, useConnectedPlayground } from './app/contexts/PlaygroundContext' -import { WalletManagementProvider } from './app/contexts/WalletManagementContext' +import { WalletPersistenceProvider } from './app/contexts/WalletPersistenceContext' import { WhaleProvider } from './app/contexts/WhaleContext' import { useCachedResources } from './app/hooks/useCachedResources' import ErrorBoundary from './app/screens/ErrorBoundary/ErrorBoundary' @@ -41,11 +41,11 @@ export default function App (): JSX.Element | null { - +
- + diff --git a/app/api/index.ts b/app/api/index.ts index 5167b42c98..7a3f66392f 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -1,3 +1,2 @@ -export * from './wallet/persistence' export * from './storage' export * from './logging' diff --git a/app/api/wallet/provider/index.test.ts b/app/api/wallet/index.test.ts similarity index 73% rename from app/api/wallet/provider/index.test.ts rename to app/api/wallet/index.test.ts index 9f95120f61..d7b4929823 100644 --- a/app/api/wallet/provider/index.test.ts +++ b/app/api/wallet/index.test.ts @@ -1,8 +1,9 @@ import { MnemonicProviderData } from "@defichain/jellyfish-wallet-mnemonic"; import { WhaleApiClient } from "@defichain/whale-api-client"; -import { EnvironmentNetwork } from "../../../environment"; -import { WalletPersistenceData, WalletType } from "../persistence"; +import { EnvironmentNetwork } from "../../environment"; +import { WalletPersistenceData, WalletType } from "./persistence"; import { initWhaleWallet } from "./index"; +import { MnemonicUnprotected } from "./provider/mnemonic_unprotected"; beforeEach(async () => { jest.clearAllMocks() @@ -22,23 +23,11 @@ it('should initWhaleWallet', async () => { } } - const wallet = initWhaleWallet(data, network, client) + const provider = MnemonicUnprotected.initProvider(data, network) + const wallet = initWhaleWallet(provider, network, client) expect(wallet.get(0).withTransactionBuilder().utxo).toBeDefined() expect(wallet.get(0).withTransactionBuilder().dex).toBeDefined() expect(wallet.get(0).withTransactionBuilder().account).toBeDefined() expect(wallet.get(0).withTransactionBuilder().liqPool).toBeDefined() -}); - - -it('should fail as wallet type not available', async () => { - const data: WalletPersistenceData = { - version: "v1", - type: undefined as any, - raw: "" - } - - expect(() => { - initWhaleWallet(data, network, client) - }).toThrow('wallet undefined not available') }) diff --git a/app/api/wallet/index.ts b/app/api/wallet/index.ts new file mode 100644 index 0000000000..82ec86d0ce --- /dev/null +++ b/app/api/wallet/index.ts @@ -0,0 +1,21 @@ +import { JellyfishWallet, WalletHdNode, WalletHdNodeProvider } from '@defichain/jellyfish-wallet' +import { WhaleApiClient } from '@defichain/whale-api-client' +import { WhaleWalletAccount, WhaleWalletAccountProvider } from '@defichain/whale-api-wallet' +import { EnvironmentNetwork } from '../../environment' +import { getJellyfishNetwork } from './network' + +/** + * Whale JellyfishWallet connected to Whale APIs via the Ocean Infrastructure + */ +export type WhaleWallet = JellyfishWallet + +export function initWhaleWallet (provider: WalletHdNodeProvider, network: EnvironmentNetwork, client: WhaleApiClient): WhaleWallet { + const accountProvider = new WhaleWalletAccountProvider(client, getJellyfishNetwork(network)) + return new JellyfishWallet(provider, accountProvider) +} + +export * from './provider/mnemonic_encrypted' +export * from './provider/mnemonic_unprotected' +export * from './network' +export * from './passcode_attempt' +export * from './persistence' diff --git a/app/api/wallet/passcode_attempt.ts b/app/api/wallet/passcode_attempt.ts new file mode 100644 index 0000000000..848bd2ff6d --- /dev/null +++ b/app/api/wallet/passcode_attempt.ts @@ -0,0 +1,20 @@ +import { StorageAPI } from '../storage' + +const KEY = 'PASSCODE_ATTEMPT.count' + +async function get (): Promise { + const str = await StorageAPI.getItem(KEY) + return str === undefined ? 0 : Number(str) +} + +async function set (count: number): Promise { + await StorageAPI.setItem(KEY, `${count}`) +} + +/** + * Failed passcode input counter persistence layer + */ +export const PasscodeAttemptCounter = { + set, + get +} diff --git a/app/api/wallet/persistence.ts b/app/api/wallet/persistence.ts index e8e1eb2c61..cbb6828d3f 100644 --- a/app/api/wallet/persistence.ts +++ b/app/api/wallet/persistence.ts @@ -1,7 +1,8 @@ import { StorageAPI } from '../storage' export enum WalletType { - MNEMONIC_UNPROTECTED = 'MNEMONIC_UNPROTECTED' + MNEMONIC_UNPROTECTED = 'MNEMONIC_UNPROTECTED', + MNEMONIC_ENCRYPTED = 'MNEMONIC_ENCRYPTED' } export interface WalletPersistenceData { diff --git a/app/api/wallet/provider/index.ts b/app/api/wallet/provider/index.ts deleted file mode 100644 index 30270e9a51..0000000000 --- a/app/api/wallet/provider/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { JellyfishWallet, WalletHdNode, WalletHdNodeProvider } from '@defichain/jellyfish-wallet' -import { WhaleApiClient } from '@defichain/whale-api-client' -import { WhaleWalletAccount, WhaleWalletAccountProvider } from '@defichain/whale-api-wallet' -import { EnvironmentNetwork } from '../../../environment' -import { getJellyfishNetwork } from '../network' -import { WalletPersistenceData, WalletType } from '../persistence' -import { MnemonicUnprotected } from './mnemonic_unprotected' - -/** - * Whale JellyfishWallet connected to Whale APIs via the Ocean Infrastructure - */ -export type WhaleWallet = JellyfishWallet - -export function initWhaleWallet (data: WalletPersistenceData, network: EnvironmentNetwork, client: WhaleApiClient): WhaleWallet { - const jellyfishNetwork = getJellyfishNetwork(network) - - const walletProvider = resolveProvider(data, network) - const accountProvider = new WhaleWalletAccountProvider(client, jellyfishNetwork) - - return new JellyfishWallet(walletProvider, accountProvider) -} - -/** - * @param {WalletPersistenceData} data to resolve wallet provider for init - * @param {EnvironmentNetwork} network - */ -function resolveProvider (data: WalletPersistenceData, network: EnvironmentNetwork): WalletHdNodeProvider { - switch (data.type) { - case WalletType.MNEMONIC_UNPROTECTED: - return MnemonicUnprotected.initProvider(data, network) - - default: - throw new Error(`wallet ${data.type as string} not available`) - } -} diff --git a/app/api/wallet/provider/mnemonic_encrypted.ts b/app/api/wallet/provider/mnemonic_encrypted.ts new file mode 100644 index 0000000000..dae255c9eb --- /dev/null +++ b/app/api/wallet/provider/mnemonic_encrypted.ts @@ -0,0 +1,51 @@ +import { + EncryptedHdNodeProvider, + EncryptedProviderData, + PrivateKeyEncryption, + PromptPassphrase, + Scrypt +} from '@defichain/jellyfish-wallet-encrypted' +import * as Random from 'expo-random' +import { EnvironmentNetwork } from '../../../environment' +import { getBip32Option } from '../network' +import { WalletPersistenceData, WalletType } from '../persistence' + +const encryption = new PrivateKeyEncryption(new Scrypt(), numOfBytes => { + const bytes = Random.getRandomBytes(numOfBytes) + return Buffer.from(bytes) +}) + +function initProvider ( + data: WalletPersistenceData, + network: EnvironmentNetwork, + promptPassphrase: PromptPassphrase +): EncryptedHdNodeProvider { + if (data.type !== WalletType.MNEMONIC_ENCRYPTED || data.version !== 'v1') { + throw new Error('Unexpected WalletPersistenceData') + } + + const bip32Options = getBip32Option(network) + return EncryptedHdNodeProvider.init(data.raw, bip32Options, encryption, promptPassphrase) +} + +async function toData (mnemonic: string[], network: EnvironmentNetwork, passphrase: string): Promise> { + const options = getBip32Option(network) + const data = await EncryptedHdNodeProvider.wordsToEncryptedData(mnemonic, options, encryption, passphrase) + + return { + version: 'v1', + type: WalletType.MNEMONIC_ENCRYPTED, + raw: data + } +} + +export const MnemonicEncrypted = { + initProvider, + toData, + /** + * Convenience Abandon23 Art on Playground Network Data + */ + Abandon23Playground: toData([ + 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'abandon', 'art' + ], EnvironmentNetwork.LocalPlayground, '123456') +} diff --git a/app/contexts/WalletContext.tsx b/app/contexts/WalletContext.tsx index 9ab1207e4d..9008e42d15 100644 --- a/app/contexts/WalletContext.tsx +++ b/app/contexts/WalletContext.tsx @@ -1,9 +1,17 @@ -import React, { createContext, useContext, useEffect, useState } from 'react' -import { useDispatch } from 'react-redux' -import { Logging } from '../api/logging' -import { WhaleWallet } from '../api/wallet/provider' -import { wallet as store } from '../store/wallet' -import { useWalletManagementContext } from './WalletManagementContext' +import { EncryptedProviderData } from '@defichain/jellyfish-wallet-encrypted' +import { MnemonicProviderData } from '@defichain/jellyfish-wallet-mnemonic' +import { useNavigation } from '@react-navigation/native' +import React, { createContext, PropsWithChildren, useCallback, useContext, useMemo } from 'react' +import { + initWhaleWallet, + MnemonicEncrypted, + MnemonicUnprotected, + WalletPersistenceData, + WalletType, + WhaleWallet +} from '../api/wallet' +import { useNetworkContext } from './NetworkContext' +import { useWhaleApiClient } from './WhaleContext' const WalletContext = createContext(undefined as any) @@ -11,24 +19,73 @@ export function useWallet (): WhaleWallet { return useContext(WalletContext) } -export function WalletProvider (props: React.PropsWithChildren): JSX.Element | null { - const dispatch = useDispatch() - const management = useWalletManagementContext() - const wallet = management.wallets[0] - - const [isLoaded, setLoaded] = useState(false) +interface WalletProviderProps extends PropsWithChildren { + data: WalletPersistenceData +} - useEffect(() => { - wallet.get(0).getAddress().then(address => { - dispatch(store.actions.setAddress(address)) - setLoaded(true) - }).catch(Logging.error) - }, [management.wallets]) +/** + * Automatically determine the correct WalletProvider to use based on the wallet type. + */ +export function WalletProvider (props: WalletProviderProps): JSX.Element | null { + if (props.data.type === WalletType.MNEMONIC_UNPROTECTED) { + return ( + + {props.children} + + ) + } - if (!isLoaded) { - return null + if (props.data.type === WalletType.MNEMONIC_ENCRYPTED) { + return ( + + {props.children} + + ) } + throw new Error(`wallet type: ${props.data.type as string} not available`) +} + +function MnemonicUnprotectedProvider (props: WalletProviderProps): JSX.Element | null { + const { network } = useNetworkContext() + const client = useWhaleApiClient() + + const wallet = useMemo(() => { + const provider = MnemonicUnprotected.initProvider(props.data, network) + return initWhaleWallet(provider, network, client) + }, []) + + return ( + + {props.children} + + ) +} + +function MnemonicEncryptedProvider (props: WalletProviderProps): JSX.Element | null { + const { network } = useNetworkContext() + const client = useWhaleApiClient() + const navigation = useNavigation() + + const promptPassphrase = useCallback(async () => { + return await new Promise((resolve, reject) => { + // TODO(ivan): implementation + navigation.navigate('PromptPassphrase', { + resolve (passphrase: string) { + resolve(passphrase) + }, + reject (error: Error) { + reject(error) + } + }) + }) + }, []) + + const wallet = useMemo(() => { + const provider = MnemonicEncrypted.initProvider(props.data, network, promptPassphrase) + return initWhaleWallet(provider, network, client) + }, []) + return ( {props.children} diff --git a/app/contexts/WalletManagementContext.tsx b/app/contexts/WalletManagementContext.tsx deleted file mode 100644 index ea1a452c77..0000000000 --- a/app/contexts/WalletManagementContext.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react' -import { Logging } from '../api/logging' -import { WalletPersistence, WalletPersistenceData } from '../api/wallet/persistence' -import { initWhaleWallet, WhaleWallet } from '../api/wallet/provider' -import { useNetworkContext } from './NetworkContext' -import { useWhaleApiClient } from './WhaleContext' - -interface WalletManagement { - wallets: WhaleWallet[] - /** - * @param {WalletPersistenceData} data to set, only 1 wallet is supported for now - */ - setWallet: (data: WalletPersistenceData) => Promise - clearWallets: () => Promise -} - -const WalletManagementContext = createContext(undefined as any) - -/** - * WalletManagement Context wrapped within - * - * This context enable wallet management by allow access to all configured wallets. - * Setting, removing and getting individual wallet. - */ -export function useWalletManagementContext (): WalletManagement { - return useContext(WalletManagementContext) -} - -export function WalletManagementProvider (props: React.PropsWithChildren): JSX.Element | null { - const { network } = useNetworkContext() - const client = useWhaleApiClient() - const [dataList, setDataList] = useState>>([]) - - useEffect(() => { - WalletPersistence.get().then(dataList => { - setDataList(dataList) - }).catch(Logging.error) - }, [network]) - - const wallets = useMemo(() => { - return dataList.map(data => initWhaleWallet(data, network, client)) - }, [dataList]) - - const management: WalletManagement = { - wallets: wallets, - async setWallet (data: WalletPersistenceData): Promise { - await WalletPersistence.set([data]) - setDataList(await WalletPersistence.get()) - }, - async clearWallets (): Promise { - await WalletPersistence.set([]) - setDataList(await WalletPersistence.get()) - } - } - - return ( - - {props.children} - - ) -} diff --git a/app/contexts/WalletPersistenceContext.tsx b/app/contexts/WalletPersistenceContext.tsx new file mode 100644 index 0000000000..8d8ff3a665 --- /dev/null +++ b/app/contexts/WalletPersistenceContext.tsx @@ -0,0 +1,54 @@ +import React, { createContext, useContext, useEffect, useState } from 'react' +import { Logging } from '../api' +import { WalletPersistence, WalletPersistenceData } from '../api/wallet' +import { useNetworkContext } from './NetworkContext' + +interface WalletPersistenceContextI { + wallets: Array> + /** + * @param {WalletPersistenceData} data to set, only 1 wallet is supported for now + */ + setWallet: (data: WalletPersistenceData) => Promise + clearWallets: () => Promise +} + +const WalletPersistenceContext = createContext(undefined as any) + +/** + * WalletManagement Context wrapped within + * + * This context enable wallet management by allow access to all configured wallets. + * Setting, removing and getting individual wallet. + */ +export function useWalletPersistenceContext (): WalletPersistenceContextI { + return useContext(WalletPersistenceContext) +} + +export function WalletPersistenceProvider (props: React.PropsWithChildren): JSX.Element | null { + const { network } = useNetworkContext() + const [list, setList] = useState>>([]) + + useEffect(() => { + WalletPersistence.get().then(dataList => { + setList(dataList) + }).catch(Logging.error) + }, [network]) + + const management: WalletPersistenceContextI = { + wallets: list, + async setWallet (data: WalletPersistenceData): Promise { + await WalletPersistence.set([data]) + setList(await WalletPersistence.get()) + }, + async clearWallets (): Promise { + await WalletPersistence.set([]) + setList(await WalletPersistence.get()) + } + } + + return ( + + {props.children} + + ) +} diff --git a/app/screens/AppNavigator/screens/Settings/SettingsScreen.tsx b/app/screens/AppNavigator/screens/Settings/SettingsScreen.tsx index a8748d285e..8d65f2ed2b 100644 --- a/app/screens/AppNavigator/screens/Settings/SettingsScreen.tsx +++ b/app/screens/AppNavigator/screens/Settings/SettingsScreen.tsx @@ -6,7 +6,7 @@ import { Alert, Platform, ScrollView, TouchableOpacity } from 'react-native' import { Text } from '../../../../components' import { SectionTitle } from '../../../../components/SectionTitle' import { useNetworkContext } from '../../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../../contexts/WalletManagementContext' +import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext' import { EnvironmentNetwork, getEnvironment, isPlayground } from '../../../../environment' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' @@ -65,7 +65,7 @@ function RowNetworkItem (props: { network: EnvironmentNetwork }): JSX.Element { } function RowExitWalletItem (): JSX.Element { - const { clearWallets } = useWalletManagementContext() + const { clearWallets } = useWalletPersistenceContext() async function onExitWallet (): Promise { if (Platform.OS === 'web') { diff --git a/app/screens/PlaygroundNavigator/PlaygroundNavigator.tsx b/app/screens/PlaygroundNavigator/PlaygroundNavigator.tsx index a280487177..2668b56ff1 100644 --- a/app/screens/PlaygroundNavigator/PlaygroundNavigator.tsx +++ b/app/screens/PlaygroundNavigator/PlaygroundNavigator.tsx @@ -1,6 +1,6 @@ import { createStackNavigator } from '@react-navigation/stack' import React from 'react' -import { HeaderFont } from '../../components/Text' +import { HeaderFont } from '../../components' import { PlaygroundScreen } from './PlaygroundScreen' export interface PlaygroundParamList { diff --git a/app/screens/PlaygroundNavigator/PlaygroundScreen.tsx b/app/screens/PlaygroundNavigator/PlaygroundScreen.tsx index f1d2bd8ac0..0dfcc25ded 100644 --- a/app/screens/PlaygroundNavigator/PlaygroundScreen.tsx +++ b/app/screens/PlaygroundNavigator/PlaygroundScreen.tsx @@ -1,6 +1,8 @@ import React from 'react' import { ScrollView } from 'react-native' import { Text, View } from '../../components' +import { WalletProvider } from '../../contexts/WalletContext' +import { useWalletPersistenceContext } from '../../contexts/WalletPersistenceContext' import { tailwind } from '../../tailwind' import { PlaygroundConnection } from './sections/PlaygroundConnection' import { PlaygroundToken } from './sections/PlaygroundToken' @@ -25,6 +27,20 @@ export function PlaygroundScreen (): JSX.Element { + + + ) +} + +function PlaygroundWalletSection (): JSX.Element | null { + const { wallets } = useWalletPersistenceContext() + + if (wallets.length === 0) { + return null + } + + return ( + @@ -32,6 +48,6 @@ export function PlaygroundScreen (): JSX.Element { - + ) } diff --git a/app/screens/PlaygroundNavigator/sections/PlaygroundToken.tsx b/app/screens/PlaygroundNavigator/sections/PlaygroundToken.tsx index f5c787a4dd..5170ba63bc 100644 --- a/app/screens/PlaygroundNavigator/sections/PlaygroundToken.tsx +++ b/app/screens/PlaygroundNavigator/sections/PlaygroundToken.tsx @@ -3,13 +3,13 @@ import { PlaygroundRpcClient } from '@defichain/playground-api-client' import React, { useEffect, useState } from 'react' import { Text, View } from '../../../components' import { usePlaygroundContext } from '../../../contexts/PlaygroundContext' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' +import { useWallet } from '../../../contexts/WalletContext' import { tailwind } from '../../../tailwind' import { PlaygroundAction } from '../components/PlaygroundAction' import { PlaygroundStatus } from '../components/PlaygroundStatus' export function PlaygroundToken (): JSX.Element | null { - const { wallets } = useWalletManagementContext() + const wallet = useWallet() const { rpc, api } = usePlaygroundContext() const [status, setStatus] = useState('loading') const [tokens, setTokens] = useState([]) @@ -23,10 +23,6 @@ export function PlaygroundToken (): JSX.Element | null { }) }, []) - if (wallets.length === 0) { - return null - } - const actions = tokens.filter(({ symbol }) => symbol !== 'DFI').map(token => { return ( { - const address = await wallets[0].get(0).getAddress() + const address = await wallet.get(0).getAddress() await rpc.call('sendtokenstoaddress', [{}, { [address]: `10@${token.symbol}` }], 'number') @@ -62,7 +58,7 @@ export function PlaygroundToken (): JSX.Element | null { onPress={async () => { await api.wallet.sendTokenDfiToAddress({ amount: '10', - address: await wallets[0].get(0).getAddress() + address: await wallet.get(0).getAddress() }) }} /> diff --git a/app/screens/PlaygroundNavigator/sections/PlaygroundUTXO.tsx b/app/screens/PlaygroundNavigator/sections/PlaygroundUTXO.tsx index 7b58ae4923..48a013e116 100644 --- a/app/screens/PlaygroundNavigator/sections/PlaygroundUTXO.tsx +++ b/app/screens/PlaygroundNavigator/sections/PlaygroundUTXO.tsx @@ -1,13 +1,13 @@ import React, { useEffect, useState } from 'react' import { Text, View } from '../../../components' import { usePlaygroundContext } from '../../../contexts/PlaygroundContext' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' +import { useWallet } from '../../../contexts/WalletContext' import { tailwind } from '../../../tailwind' import { PlaygroundAction } from '../components/PlaygroundAction' import { PlaygroundStatus } from '../components/PlaygroundStatus' -export function PlaygroundUTXO (): JSX.Element | null { - const { wallets } = useWalletManagementContext() +export function PlaygroundUTXO (): JSX.Element { + const wallet = useWallet() const { api, rpc } = usePlaygroundContext() const [status, setStatus] = useState('loading') @@ -19,21 +19,6 @@ export function PlaygroundUTXO (): JSX.Element | null { }) }, []) - if (wallets.length === 0) { - return null - } - - const actions = status === 'online' ? ( - { - const address = await wallets[0].get(0).getAddress() - await rpc.wallet.sendToAddress(address, 10) - }} - /> - ) : null - return ( @@ -47,7 +32,16 @@ export function PlaygroundUTXO (): JSX.Element | null { - {actions} + {status === 'online' ? ( + { + const address = await wallet.get(0).getAddress() + await rpc.wallet.sendToAddress(address, 10) + }} + /> + ) : null} ) } diff --git a/app/screens/PlaygroundNavigator/sections/PlaygroundWallet.tsx b/app/screens/PlaygroundNavigator/sections/PlaygroundWallet.tsx index f156ad1a1d..ad2f26f9b2 100644 --- a/app/screens/PlaygroundNavigator/sections/PlaygroundWallet.tsx +++ b/app/screens/PlaygroundNavigator/sections/PlaygroundWallet.tsx @@ -2,10 +2,10 @@ import { generateMnemonicWords } from '@defichain/jellyfish-wallet-mnemonic' import * as Random from 'expo-random' import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { MnemonicUnprotected } from '../../../api/wallet/provider/mnemonic_unprotected' +import { MnemonicUnprotected } from '../../../api/wallet' import { Text, View } from '../../../components' import { useNetworkContext } from '../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../contexts/WalletManagementContext' +import { useWalletPersistenceContext } from '../../../contexts/WalletPersistenceContext' import { useWhaleApiClient } from '../../../contexts/WhaleContext' import { fetchTokens } from '../../../hooks/wallet/TokensAPI' import { RootState } from '../../../store' @@ -14,7 +14,7 @@ import { PlaygroundAction } from '../components/PlaygroundAction' import { PlaygroundStatus } from '../components/PlaygroundStatus' export function PlaygroundWallet (): JSX.Element | null { - const { wallets, clearWallets, setWallet } = useWalletManagementContext() + const { wallets, clearWallets, setWallet } = useWalletPersistenceContext() const network = useNetworkContext() const whaleApiClient = useWhaleApiClient() const dispatch = useDispatch() diff --git a/app/screens/RootNavigator.tsx b/app/screens/RootNavigator.tsx index ea1dfbc02a..779f261b6d 100644 --- a/app/screens/RootNavigator.tsx +++ b/app/screens/RootNavigator.tsx @@ -1,6 +1,9 @@ -import React from 'react' -import { WalletProvider } from '../contexts/WalletContext' -import { useWalletManagementContext } from '../contexts/WalletManagementContext' +import React, { PropsWithChildren, useEffect, useState } from 'react' +import { useDispatch } from 'react-redux' +import { Logging } from '../api' +import { useWallet, WalletProvider } from '../contexts/WalletContext' +import { useWalletPersistenceContext } from '../contexts/WalletPersistenceContext' +import { wallet as store } from '../store/wallet' import { AppNavigator } from './AppNavigator/AppNavigator' import { WalletNavigator } from './WalletNavigator/WalletNavigator' @@ -8,15 +11,40 @@ import { WalletNavigator } from './WalletNavigator/WalletNavigator' * Top Root Level Wallet State to control what screen to show */ export function RootNavigator (): JSX.Element { - const { wallets } = useWalletManagementContext() + const { wallets } = useWalletPersistenceContext() if (wallets.length === 0) { return } return ( - - + + + + ) } + +/** + * TODO(fuxingloh): to deprecate completely + * @deprecated included for legacy reasons, moving forward address should not be set in store + */ +function WalletAddressProvider (props: PropsWithChildren): JSX.Element | null { + const wallet = useWallet() + const dispatch = useDispatch() + const [isLoaded, setLoaded] = useState(false) + + useEffect(() => { + wallet.get(0).getAddress().then(address => { + dispatch(store.actions.setAddress(address)) + setLoaded(true) + }).catch(Logging.error) + }, [wallet]) + + if (!isLoaded) { + return null + } + + return props.children +} diff --git a/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx b/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx index e7eca04e08..c642b31dbe 100644 --- a/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx +++ b/app/screens/WalletNavigator/screens/CreateWallet/VerifyMnemonicWallet.tsx @@ -5,7 +5,7 @@ import { KeyboardAvoidingView, ScrollView, TouchableOpacity } from 'react-native import { MnemonicUnprotected } from '../../../../api/wallet/provider/mnemonic_unprotected' import { Text, TextInput, View } from '../../../../components' import { useNetworkContext } from '../../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../../contexts/WalletManagementContext' +import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext' import { tailwind } from '../../../../tailwind' import { WalletParamList } from '../../WalletNavigator' @@ -17,7 +17,7 @@ export function VerifyMnemonicWallet ({ route }: Props): JSX.Element { const [valid, setValid] = useState(true) const { network } = useNetworkContext() - const { setWallet } = useWalletManagementContext() + const { setWallet } = useWalletPersistenceContext() async function onVerify (): Promise { if (actualWords.join(' ') === enteredWords.join(' ')) { diff --git a/app/screens/WalletNavigator/screens/RestoreWallet/RestoreMnemonicWallet.tsx b/app/screens/WalletNavigator/screens/RestoreWallet/RestoreMnemonicWallet.tsx index 3254ca74ff..3a08bc339d 100644 --- a/app/screens/WalletNavigator/screens/RestoreWallet/RestoreMnemonicWallet.tsx +++ b/app/screens/WalletNavigator/screens/RestoreWallet/RestoreMnemonicWallet.tsx @@ -9,14 +9,14 @@ import { Text, View } from '../../../../components' import { Button } from '../../../../components/Button' import { SectionTitle } from '../../../../components/SectionTitle' import { useNetworkContext } from '../../../../contexts/NetworkContext' -import { useWalletManagementContext } from '../../../../contexts/WalletManagementContext' +import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' import LoadingScreen from '../../../LoadingNavigator/LoadingScreen' export function RestoreMnemonicWallet (): JSX.Element { const { network } = useNetworkContext() - const { setWallet } = useWalletManagementContext() + const { setWallet } = useWalletPersistenceContext() const { control, formState: { isValid }, getValues } = useForm({ mode: 'onChange' }) const [recoveryWords] = useState(Array.from(Array(24), (v, i) => i + 1)) const [isSubmitting, setIsSubmitting] = useState(false) diff --git a/app/screens/WalletNavigator/screens/components/OnboardingCarousel.tsx b/app/screens/WalletNavigator/screens/components/OnboardingCarousel.tsx index 630569afb9..13a42b0e0c 100644 --- a/app/screens/WalletNavigator/screens/components/OnboardingCarousel.tsx +++ b/app/screens/WalletNavigator/screens/components/OnboardingCarousel.tsx @@ -7,7 +7,7 @@ import ImageB from '../../../../assets/images/onboarding/b.png' import ImageC from '../../../../assets/images/onboarding/c.png' import { Text, View } from '../../../../components' import { AppIcon } from '../../../../components/icons/AppIcon' -import { useWalletManagementContext } from '../../../../contexts/WalletManagementContext' +import { useWalletPersistenceContext } from '../../../../contexts/WalletPersistenceContext' import { getEnvironment } from '../../../../environment' import { tailwind } from '../../../../tailwind' import { translate } from '../../../../translations' @@ -39,7 +39,7 @@ const slides: JSX.Element[] = [, const { width } = Platform.OS === 'web' ? { width: '375px' } : Dimensions.get('window') export function InitialSlide (): JSX.Element { - const { setWallet } = useWalletManagementContext() + const { setWallet } = useWalletPersistenceContext() const onDebugPress = getEnvironment().debug ? async () => { await setWallet(MnemonicUnprotected.Abandon23Playground) } : undefined diff --git a/app/store/wallet.ts b/app/store/wallet.ts index b306a6d7a7..9c07271d84 100644 --- a/app/store/wallet.ts +++ b/app/store/wallet.ts @@ -10,6 +10,9 @@ export interface WalletToken extends AddressToken { export interface WalletState { utxoBalance: string tokens: WalletToken[] + /** + * @deprecated use `useWalletContext().get(0).getAddress()` instead + */ address: string } diff --git a/package-lock.json b/package-lock.json index 9376f046d0..7483279dbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@defichain/jellyfish-transaction": ">=0.33.0", "@defichain/jellyfish-transaction-builder": ">=0.33.0", "@defichain/jellyfish-wallet": ">=0.33.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.33.0", "@defichain/jellyfish-wallet-mnemonic": ">=0.33.0", "@defichain/playground-api-client": ">=0.7.0", "@defichain/whale-api-client": ">=0.7.0", @@ -2120,6 +2121,15 @@ "bignumber.js": "^9.0.1" } }, + "node_modules/@defichain/jellyfish-wallet-encrypted": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.33.0.tgz", + "integrity": "sha512-HDgGl30xCIPx9E1efRF3d4n8MlhlbpJ2DGAnJUM9fOlXTtp9T0ihhFek8wqNOtj7hP6Y9H8ANW+KcuA4XwoTUA==", + "dependencies": { + "@defichain/jellyfish-wallet-mnemonic": "^0.33.0", + "scrypt-js": "^3.0.1" + } + }, "node_modules/@defichain/jellyfish-wallet-mnemonic": { "version": "0.33.0", "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.33.0.tgz", @@ -33297,6 +33307,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -42133,6 +42148,15 @@ "@defichain/jellyfish-transaction": "^0.33.0" } }, + "@defichain/jellyfish-wallet-encrypted": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-encrypted/-/jellyfish-wallet-encrypted-0.33.0.tgz", + "integrity": "sha512-HDgGl30xCIPx9E1efRF3d4n8MlhlbpJ2DGAnJUM9fOlXTtp9T0ihhFek8wqNOtj7hP6Y9H8ANW+KcuA4XwoTUA==", + "requires": { + "@defichain/jellyfish-wallet-mnemonic": "^0.33.0", + "scrypt-js": "^3.0.1" + } + }, "@defichain/jellyfish-wallet-mnemonic": { "version": "0.33.0", "resolved": "https://registry.npmjs.org/@defichain/jellyfish-wallet-mnemonic/-/jellyfish-wallet-mnemonic-0.33.0.tgz", @@ -61017,6 +61041,7 @@ "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.59.0.tgz", "integrity": "sha512-1O3wrnMq4NcPQ1asEcl9lRDn/t+F1Oef6S9WaYVIKEhg9m/EQRGVrrTVP+R6B5Eeaj3+zNKbzM8Dx/NWy1hUbQ==", "requires": { + "@babel/core": "^7.0.0", "babel-preset-fbjs": "^3.3.0", "metro-babel-transformer": "0.59.0", "metro-react-native-babel-preset": "0.59.0", @@ -66721,6 +66746,11 @@ "ajv-keywords": "^3.5.2" } }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index e24ef7d948..60204349b6 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@defichain/jellyfish-transaction": ">=0.33.0", "@defichain/jellyfish-transaction-builder": ">=0.33.0", "@defichain/jellyfish-wallet": ">=0.33.0", + "@defichain/jellyfish-wallet-encrypted": ">=0.33.0", "@defichain/jellyfish-wallet-mnemonic": ">=0.33.0", "@defichain/playground-api-client": ">=0.7.0", "@defichain/whale-api-client": ">=0.7.0",