forked from cerc-io/laconic-wallet
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:
parent
f026d9345f
commit
9ab3148aa9
13
App.tsx
13
App.tsx
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -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;
|
||||||
|
@ -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
5
constants.ts
Normal 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
14
types.ts
Normal 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;
|
||||||
|
};
|
56
utils.ts
56
utils.ts
@ -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 };
|
||||||
|
Loading…
Reference in New Issue
Block a user