Show account data specific to selected network (#11)

* Add state for selected network

* Make review changes

* Add cosmos signature

* Explicit check for cosmos

* Add dummy method for generating wallet

* Remove logic from component

* Add dummy sign method

* Change network state values

* Use separate file for types

* Add default case to switch

* Use consistent method names

---------

Co-authored-by: Adw8 <adwait@deepstacksoft.com>
This commit is contained in:
Adwait Gharpure 2024-02-14 13:45:02 +05:30 committed by GitHub
parent f026d9345f
commit 9ab3148aa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 151 additions and 39 deletions

13
App.tsx
View File

@ -6,14 +6,23 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import SignMessage from './components/SignMessage'; import SignMessage from './components/SignMessage';
import { HomeScreen } from './components/HomeScreen'; import { HomeScreen } from './components/HomeScreen';
const Stack = createNativeStackNavigator(); import { StackParamsList } from './types';
const Stack = createNativeStackNavigator<StackParamsList>();
const App = (): React.JSX.Element => { const App = (): React.JSX.Element => {
return ( return (
<NavigationContainer> <NavigationContainer>
<Stack.Navigator> <Stack.Navigator>
<Stack.Screen name="Laconic" component={HomeScreen} /> <Stack.Screen name="Laconic" component={HomeScreen} />
<Stack.Screen name="Sign Message" component={SignMessage} /> <Stack.Screen
name="SignMessage"
component={SignMessage}
options={{
title: 'Sign Message',
}}
initialParams={{ selectedNetwork: 'Ethereum' }}
/>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );

View File

@ -1,35 +1,45 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { Text, Button, Dialog, Portal, List } from 'react-native-paper'; import { Text, Button, Dialog, Portal } from 'react-native-paper';
import { HDNode } from 'ethers/lib/utils'; import { HDNode } from 'ethers/lib/utils';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { generateWallet, resetWallet } from '../utils'; import { createWallet, resetWallet } from '../utils';
import { DialogComponent } from './Dialog'; import { DialogComponent } from './Dialog';
import { NetworkDropdown } from './NetworkDropdown'; import { NetworkDropdown } from './NetworkDropdown';
import { StackParamsList, Account } from '../types';
const HomeScreen = () => { const HomeScreen = () => {
const navigation = useNavigation(); const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false); const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
const [wallet, setWallet] = useState<HDNode | null>(); const [wallet, setWallet] = useState<HDNode | null>();
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false); const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [walletDialog, setWalletDialog] = useState<boolean>(false); const [walletDialog, setWalletDialog] = useState<boolean>(false);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false); const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [network, setNetwork] = useState<string>('eth');
const [currentAccount, setCurrentAccount] = useState<Account>();
const [ethAccount, setEthAccount] = useState<Account>();
const [cosmosAccount, setCosmosAccount] = useState<Account>();
const hideWalletDialog = () => setWalletDialog(false); const hideWalletDialog = () => setWalletDialog(false);
const hideResetDialog = () => setResetWalletDialog(false); const hideResetDialog = () => setResetWalletDialog(false);
const createWallet = async () => { const createWalletHandler = async () => {
setIsWalletCreating(true); setIsWalletCreating(true);
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 2000));
const etherWallet = await generateWallet(); const { ethAccount, cosmosAccount } = createWallet();
setEthAccount(ethAccount);
setCosmosAccount(cosmosAccount);
setCurrentAccount(ethAccount);
setWalletDialog(true); setWalletDialog(true);
if (etherWallet) { setIsWalletCreated(true);
setWallet(etherWallet);
setIsWalletCreated(true);
}
}; };
const confirmResetWallet = async () => { const confirmResetWallet = async () => {
@ -40,6 +50,20 @@ const HomeScreen = () => {
hideResetDialog(); hideResetDialog();
}; };
const updateNetwork = (newNetwork: string) => {
setNetwork(newNetwork);
switch (newNetwork) {
case 'eth':
setCurrentAccount(ethAccount);
break;
case 'cosmos':
setCurrentAccount(cosmosAccount);
break;
default:
console.error('Error updating network');
}
};
return ( return (
<View style={{ marginTop: 24, paddingHorizontal: 24 }}> <View style={{ marginTop: 24, paddingHorizontal: 24 }}>
<DialogComponent <DialogComponent
@ -63,18 +87,22 @@ const HomeScreen = () => {
</Portal> </Portal>
{isWalletCreated ? ( {isWalletCreated ? (
<View> <View>
<NetworkDropdown /> <NetworkDropdown
<Text variant="headlineSmall">Account1</Text> selectedNetwork={network}
updateNetwork={updateNetwork}
/>
<Text variant="headlineSmall">Account 1</Text>
<View style={{ marginTop: 15, marginBottom: 15 }}> <View style={{ marginTop: 15, marginBottom: 15 }}>
<Text variant="bodyLarge"> <Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Address: </Text> <Text style={{ fontWeight: '700' }}>Address: </Text>
{wallet && wallet.address.toString()} {currentAccount && currentAccount.address}
</Text> </Text>
<Text variant="bodyLarge"> <Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Public Key: </Text> <Text style={{ fontWeight: '700' }}>Public Key: </Text>
{wallet && wallet.publicKey.toString()} {currentAccount && currentAccount.publicKey}
</Text> </Text>
</View> </View>
<View style={{ flexDirection: 'row', justifyContent: 'center' }}> <View style={{ flexDirection: 'row', justifyContent: 'center' }}>
<View <View
style={{ style={{
@ -86,7 +114,9 @@ const HomeScreen = () => {
<Button <Button
mode="contained" mode="contained"
onPress={() => { onPress={() => {
navigation.navigate('Sign Message' as never); navigation.navigate('SignMessage', {
selectedNetwork: network,
});
}}> }}>
Sign Message Sign Message
</Button> </Button>
@ -110,7 +140,7 @@ const HomeScreen = () => {
<Button <Button
mode="contained" mode="contained"
loading={isWalletCreating} loading={isWalletCreating}
onPress={createWallet}> onPress={createWalletHandler}>
{isWalletCreating ? 'Creating' : 'Create'}{' '} {isWalletCreating ? 'Creating' : 'Create'}{' '}
</Button> </Button>
</View> </View>

View File

@ -2,26 +2,38 @@ import React, { useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { List } from 'react-native-paper'; import { List } from 'react-native-paper';
const NetworkDropdown = () => { type NetworkDropdownProps = {
selectedNetwork: string;
updateNetwork: (network: string) => void;
};
const NetworkDropdown: React.FC<NetworkDropdownProps> = ({
updateNetwork,
}) => {
const [expanded, setExpanded] = useState<boolean>(false); const [expanded, setExpanded] = useState<boolean>(false);
const [title, setTitle] = useState<string>('Ethereum');
const expandNetworks = () => setExpanded(!expanded); const expandNetworks = () => setExpanded(!expanded);
return ( return (
<View style={{ marginBottom: 20 }}> <View style={{ marginBottom: 20 }}>
<List.Accordion <List.Accordion
title="Select Network" title={title}
expanded={expanded} expanded={expanded}
onPress={expandNetworks}> onPress={expandNetworks}>
<List.Item <List.Item
title="Ethereum" title="Ethereum"
onPress={() => { onPress={() => {
updateNetwork('eth');
setTitle('Ethereum');
setExpanded(false); setExpanded(false);
}} }}
/> />
<List.Item <List.Item
title="Cosmos" title="Cosmos"
onPress={() => { onPress={() => {
updateNetwork('cosmos');
setTitle('Cosmos');
setExpanded(false); setExpanded(false);
}} }}
/> />

View File

@ -1,8 +1,8 @@
import { PropsWithChildren } from "react"; import { PropsWithChildren } from 'react';
import { Text, View, useColorScheme } from "react-native"; import { Text, View, useColorScheme } from 'react-native';
import { Colors } from "react-native/Libraries/NewAppScreen"; import { Colors } from 'react-native/Libraries/NewAppScreen';
import styles from "../styles/stylesheet"; import styles from '../styles/stylesheet';
type SectionProps = PropsWithChildren<{ type SectionProps = PropsWithChildren<{
title: string; title: string;

View File

@ -2,10 +2,18 @@ import { View } from 'react-native';
import { Button, TextInput } from 'react-native-paper'; import { Button, TextInput } from 'react-native-paper';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { StackParamsList } from '../types';
import { signMessage } from '../utils'; import { signMessage } from '../utils';
export default function SignMessage() { type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
const SignMessage = ({ route }: SignProps) => {
const network = route.params?.selectedNetwork;
const [message, setMessage] = useState<string>(''); const [message, setMessage] = useState<string>('');
return ( return (
<View style={{ marginTop: 30, paddingHorizontal: 48 }}> <View style={{ marginTop: 30, paddingHorizontal: 48 }}>
<TextInput <TextInput
@ -18,11 +26,13 @@ export default function SignMessage() {
<Button <Button
mode="contained" mode="contained"
onPress={() => { onPress={() => {
signMessage(message); network && signMessage(network, 0, message);
}}> }}>
Sign Sign
</Button> </Button>
</View> </View>
</View> </View>
); );
} };
export default SignMessage;

5
constants.ts Normal file
View File

@ -0,0 +1,5 @@
export const COSMOS_SIGNATURE =
'0x56da25d5a9704e0cd685d52ecee5c14bf6637fa2f95653e8499eac4e8285f37b2d9f446c027cac56f3b7840d1b3879ea943415190d7a358cdb3ee05451cdcf7c1c';
export const COSMOS_ADDRESS = 'cosmos1sulk9q5fmagur6m3pctmcnfeeku25gp2ectt75';
export const COSMOS_PUBKEY =
'cosmospub1addwnpepqt9d597c5f6zqqyxy3msrstyc7zl3vyvrl5ku02r4ueuwt5vusw4gmt70dd';

14
types.ts Normal file
View File

@ -0,0 +1,14 @@
export type StackParamsList = {
Laconic: undefined;
SignMessage: { selectedNetwork: string } | undefined;
};
export type Account = {
address: string;
publicKey: string;
};
export type WalletDetails = {
ethAccount: Account;
cosmosAccount: Account;
};

View File

@ -3,16 +3,17 @@ For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#c
import 'react-native-get-random-values'; import 'react-native-get-random-values';
import '@ethersproject/shims'; import '@ethersproject/shims';
import { Wallet, utils } from 'ethers'; import { utils } from 'ethers';
import { HDNode } from 'ethers/lib/utils'; import { HDNode } from 'ethers/lib/utils';
import { Alert } from 'react-native'; import { Alert } from 'react-native';
import { import {
setInternetCredentials, setInternetCredentials,
getInternetCredentials,
resetInternetCredentials, resetInternetCredentials,
} from 'react-native-keychain'; } from 'react-native-keychain';
const generateWallet = async (): Promise<HDNode | undefined> => { import { Account, WalletDetails } from './types';
const generateEthNode = async (): Promise<HDNode | undefined> => {
try { try {
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(32)); const mnemonic = utils.entropyToMnemonic(utils.randomBytes(32));
const hdNode = HDNode.fromMnemonic(mnemonic); const hdNode = HDNode.fromMnemonic(mnemonic);
@ -27,16 +28,47 @@ const generateWallet = async (): Promise<HDNode | undefined> => {
} }
}; };
const signMessage = async (message: string) => { const createWallet = (): WalletDetails => {
try { try {
const keyCred = await getInternetCredentials('keyServer'); const ethAccount = {
const wallet = keyCred && new Wallet(keyCred.password); address: '0x873784c8A011A32C7635C8d8D3D2c83060532A49',
const signature = wallet && (await wallet.signMessage(message)); publicKey:
if (typeof signature === 'string') { '0x02fd66d3487eb0567c321dac48b5e17c469df8ede7c0c79b74a9d0492249b32f1e',
Alert.alert('Message signature: ', signature); };
} else {
Alert.alert('Message signing failed. Please try again.'); const cosmosAccount = {
address: 'cosmos1sulk9q5fmagur6m3pctmcnfeeku25gp2ectt75',
publicKey:
'cosmospub1addwnpepqt9d597c5f6zqqyxy3msrstyc7zl3vyvrl5ku02r4ueuwt5vusw4gmt70dd',
};
return { ethAccount, cosmosAccount };
} catch (error) {
console.error('Error creating wallet ', error);
throw error;
}
};
const signMessage = async (network: string, index: Number, message: string) => {
try {
let signature: string | false;
switch (network) {
case 'eth':
signature =
'0x43jv95d5a9704z83h85d52ecee5c14bf6637fa2f95653e8499eac4e8285f37b2d9f446c027cac56f3b7840d1b3879ea943415190d7a358cdb3ee05451cdcf7c1c';
break;
case 'cosmos':
signature =
'0x56da25d5a9704e0cd685d52ecee5c14bf6637fa2f95653e8499eac4e8285f37b2d9f446c027cac56f3b7840d1b3879ea943415190d7a358cdb3ee05451cdcf7c1c';
break;
default:
signature = '';
} }
Alert.alert('Message signature: ', signature as string);
} catch (error) { } catch (error) {
console.error('Error signing transaction ', error); console.error('Error signing transaction ', error);
} }
@ -47,4 +79,4 @@ const resetWallet = async () => {
await resetInternetCredentials('mnemonicServer'); await resetInternetCredentials('mnemonicServer');
}; };
export { generateWallet, signMessage, resetWallet }; export { createWallet, signMessage, resetWallet };