UI to add multiple accounts (#16)

* Change button position

* Keep reset button at the bottom

* Use dropdown for accounts in separate component

* Display data of selected account

* Add method to add multiple accounts

* Change reset button position

* Clear account state on reset

* Display correct account info after creating

* Added account info to sign page

* Change variable names

* Use consistent variable names

* Use account id in ui

* Make review changes

* Fix imports

---------

Co-authored-by: Adw8 <adwait@deepstacksoft.com>
This commit is contained in:
Adwait Gharpure 2024-02-15 16:23:23 +05:30 committed by Ashwin Phatak
parent a158abac0b
commit 31c6999e9f
7 changed files with 217 additions and 72 deletions

93
components/Accounts.tsx Normal file
View File

@ -0,0 +1,93 @@
import { View } from 'react-native';
import React, { useState } from 'react';
import { Button, List, Text } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AccountsProps, StackParamsList, Account } from '../types';
import { addAccount } from '../utils';
const Accounts: React.FC<AccountsProps> = ({
network,
accounts,
updateAccounts,
currentIndex,
updateIndex,
}) => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [expanded, setExpanded] = useState(false);
const handlePress = () => setExpanded(!expanded);
const addAccountHandler = async () => {
const newAccount = await addAccount(network);
newAccount && updateAccounts(newAccount);
updateIndex(selectedAccounts[selectedAccounts.length -1].id);
};
let selectedAccounts: Account[] = [];
if (network === 'eth') {
selectedAccounts = accounts.ethAccounts;
}
if (network === 'cosmos') {
selectedAccounts = accounts.cosmosAccounts;
}
return (
<View>
<List.Accordion
title={`Account ${currentIndex + 1}`}
expanded={expanded}
onPress={handlePress}>
{selectedAccounts &&
selectedAccounts.map((account) => (
<List.Item
key={account.id}
title={`Account ${account.id + 1}`}
onPress={() => {
updateIndex(account.id);
setExpanded(false);
}}
/>
))}
</List.Accordion>
<View style={{ alignItems: 'center', marginTop: 24 }}>
<Button mode="contained" onPress={addAccountHandler}>
Add Account
</Button>
</View>
<View style={{ marginTop: 24 }}>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Address: </Text>
{selectedAccounts &&
selectedAccounts[currentIndex] &&
selectedAccounts[currentIndex].address}
</Text>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Public Key: </Text>
{selectedAccounts &&
selectedAccounts[currentIndex] &&
selectedAccounts[currentIndex].pubKey}
</Text>
</View>
<View style={{ alignItems: 'center', marginTop: 24 }}>
<Button
mode="contained"
onPress={() => {
navigation.navigate('SignMessage', {
selectedNetwork: network,
accountInfo: selectedAccounts[currentIndex],
});
}}>
Sign Message
</Button>
</View>
</View>
);
};
export default Accounts;

View File

@ -1,5 +1,5 @@
import React from "react";
import { Button, Dialog, Portal, Text } from "react-native-paper";
import React from 'react';
import { Button, Dialog, Portal, Text } from 'react-native-paper';
type CustomDialogProps = {
visible: boolean;
@ -8,7 +8,11 @@ type CustomDialogProps = {
titleText?: string;
};
const DialogComponent: React.FC<CustomDialogProps> = ({ visible, hideDialog, titleText, contentText }) => {
const DialogComponent: React.FC<CustomDialogProps> = ({
visible,
hideDialog,
contentText,
}) => {
return (
<Portal>
<Dialog visible={visible} onDismiss={hideDialog}>

View File

@ -1,27 +1,25 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Alert, ScrollView, View } from 'react-native';
import { Text, Button, Dialog, Portal } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { createWallet, resetWallet } from '../utils';
import { DialogComponent } from './Dialog';
import { NetworkDropdown } from './NetworkDropdown';
import { StackParamsList, Account } from '../types';
import { Account, AccountsState } from '../types';
import Accounts from './Accounts';
const HomeScreen = () => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [walletDialog, setWalletDialog] = 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 [currentIndex, setCurrentIndex] = useState<number>(0);
const [accounts, setAccounts] = useState<AccountsState>({
ethAccounts: [],
cosmosAccounts: [],
});
const hideWalletDialog = () => setWalletDialog(false);
const hideResetDialog = () => setResetWalletDialog(false);
@ -30,10 +28,12 @@ const HomeScreen = () => {
setIsWalletCreating(true);
await new Promise(resolve => setTimeout(resolve, 2000));
const { ethWalletInfo, cosmosWalletInfo } = await createWallet();
setEthAccount(ethWalletInfo);
setCosmosAccount(cosmosWalletInfo);
setCurrentAccount(ethWalletInfo);
ethWalletInfo &&
cosmosWalletInfo &&
setAccounts({
ethAccounts: [...accounts.ethAccounts, ethWalletInfo],
cosmosAccounts: [...accounts.cosmosAccounts, cosmosWalletInfo],
});
setWalletDialog(true);
setIsWalletCreated(true);
};
@ -42,25 +42,44 @@ const HomeScreen = () => {
await resetWallet();
setIsWalletCreated(false);
setIsWalletCreating(false);
setAccounts({
ethAccounts: [],
cosmosAccounts: [],
});
setCurrentIndex(0);
hideResetDialog();
setNetwork('eth');
};
const updateNetwork = (newNetwork: string) => {
setNetwork(newNetwork);
switch (newNetwork) {
case 'eth':
setCurrentAccount(ethAccount);
break;
case 'cosmos':
setCurrentAccount(cosmosAccount);
break;
default:
console.error('Error updating network');
}
setCurrentIndex(0);
};
const updateIndex = (index: number) => {
setCurrentIndex(index);
};
const updateAccounts = (account: Account) => {
switch (network) {
case 'eth':
setAccounts({
...accounts,
ethAccounts: [...accounts.ethAccounts, account],
});
break;
case 'cosmos':
setAccounts({
...accounts,
cosmosAccounts: [...accounts.cosmosAccounts, account],
});
break;
default:
Alert.alert('Select a valid network!');
}
};
return (
<View style={{ marginTop: 24, paddingHorizontal: 24 }}>
<ScrollView style={{ marginTop: 24, paddingHorizontal: 24 }}>
<DialogComponent
visible={walletDialog}
hideDialog={hideWalletDialog}
@ -86,30 +105,14 @@ const HomeScreen = () => {
selectedNetwork={network}
updateNetwork={updateNetwork}
/>
<Text variant="headlineSmall">Account 1</Text>
<View style={{ marginTop: 15, marginBottom: 15 }}>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Address: </Text>
{currentAccount && currentAccount.address}
</Text>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Public Key: </Text>
{currentAccount && currentAccount.pubKey}
</Text>
</View>
<View style={{ alignItems: 'center', marginTop: 30 }}>
<Button
mode="contained"
onPress={() => {
navigation.navigate('SignMessage', {
selectedNetwork: network,
});
}}>
Sign Message
</Button>
</View>
<View style={{ marginTop: 400, alignSelf: 'center' }}>
<Accounts
network={network}
accounts={accounts}
currentIndex={currentIndex}
updateIndex={updateIndex}
updateAccounts={updateAccounts}
/>
<View style={{ marginTop: 300, alignSelf: 'center' }}>
<Button
mode="contained"
buttonColor="#B82B0D"
@ -122,18 +125,17 @@ const HomeScreen = () => {
</View>
) : (
<View>
<Text variant="headlineSmall">Create Wallet</Text>
<View style={{ marginTop: 20, width: 150, alignSelf: 'center' }}>
<Button
mode="contained"
loading={isWalletCreating}
onPress={createWalletHandler}>
{isWalletCreating ? 'Creating' : 'Create'}{' '}
{isWalletCreating ? 'Creating' : 'Create Wallet'}{' '}
</Button>
</View>
</View>
)}
</View>
</ScrollView>
);
};

View File

@ -2,10 +2,7 @@ import React, { useState } from 'react';
import { View } from 'react-native';
import { List } from 'react-native-paper';
type NetworkDropdownProps = {
selectedNetwork: string;
updateNetwork: (network: string) => void;
};
import { NetworkDropdownProps } from '../types';
const NetworkDropdown: React.FC<NetworkDropdownProps> = ({
updateNetwork,

View File

@ -1,7 +1,6 @@
import { View } from 'react-native';
import { Button, TextInput } from 'react-native-paper';
import React, { useState } from 'react';
import { Alert } from 'react-native';
import { ScrollView, View, Alert } from 'react-native';
import { Button, Text, TextInput } from 'react-native-paper';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
@ -12,18 +11,32 @@ type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
const SignMessage = ({ route }: SignProps) => {
const network = route.params?.selectedNetwork;
const account = route.params?.accountInfo;
const [message, setMessage] = useState<string>('');
const signMessageHandler = async () => {
if (network) {
const signedMessage = await signMessage(message, network, 0);
if (!account){
throw new Error("Account is not valid");
}
const signedMessage = await signMessage(message, network, account.id);
Alert.alert('Signature', signedMessage);
}
};
return (
<View style={{ marginTop: 30, paddingHorizontal: 48 }}>
<ScrollView style={{ marginTop: 24, paddingHorizontal: 24 }}>
<View style={{ marginTop: 24, marginBottom: 30 }}>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Address: </Text>
{account && account.address}
</Text>
<Text variant="bodyLarge">
<Text style={{ fontWeight: '700' }}>Public Key: </Text>
{account && account.pubKey}
</Text>
</View>
<TextInput
mode="outlined"
placeholder="Enter your message"
@ -35,7 +48,7 @@ const SignMessage = ({ route }: SignProps) => {
Sign
</Button>
</View>
</View>
</ScrollView>
);
};

View File

@ -1,9 +1,10 @@
export type StackParamsList = {
Laconic: undefined;
SignMessage: { selectedNetwork: string } | undefined;
SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined;
};
export type Account = {
id: number,
pubKey: string;
address: string;
};
@ -12,3 +13,24 @@ export type WalletDetails = {
ethAccount: Account;
cosmosAccount: Account;
};
export type AccountsProps = {
network: string;
accounts: {
ethAccounts: Account[];
cosmosAccounts: Account[];
};
currentIndex: number;
updateIndex: (index: number) => void;
updateAccounts: (account: Account) => void;
};
export type NetworkDropdownProps = {
selectedNetwork: string;
updateNetwork: (network: string) => void;
};
export type AccountsState = {
ethAccounts: Account[];
cosmosAccounts: Account[];
};

View File

@ -15,8 +15,8 @@ import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino';
import { stringToPath } from '@cosmjs/crypto';
const createWallet = async (): Promise<{
ethWalletInfo: { pubKey: string; address: string } | undefined;
cosmosWalletInfo: { pubKey: string; address: string } | undefined;
ethWalletInfo: { id: number; pubKey: string; address: string } | undefined;
cosmosWalletInfo: { id: number; pubKey: string; address: string } | undefined;
}> => {
try {
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(32));
@ -41,8 +41,13 @@ const createWallet = async (): Promise<{
cosmosNode.privateKey,
);
const ethWalletInfo = { pubKey: ethNode.publicKey, address: ethAddress };
const ethWalletInfo = {
id: 0,
pubKey: ethNode.publicKey,
address: ethAddress,
};
const cosmosWalletInfo = {
id: 0,
pubKey: cosmosNode.publicKey,
address: cosmosAddress,
};
@ -75,7 +80,6 @@ const signMessage = async (
index: number,
): Promise<string | undefined> => {
try {
console.log(walletType);
switch (walletType) {
case 'eth':
return await signEthMessage(message, index);
@ -153,6 +157,16 @@ const signCosmosMessage = async (
};
// const createAccount
const addAccount = async (network: string) => {
// // const index = 5;
// switch (network) {
// case 'eth':
// return dummyEthAccounts[3];
// case 'cosmos':
// return dummyCosmosAccounts[3];
// }
};
const resetWallet = async () => {
// TODO: Add method to reset all the accounts
@ -161,4 +175,4 @@ const resetWallet = async () => {
await resetInternetCredentials('cosmos:keyServer:0');
};
export { createWallet, signMessage, resetWallet };
export { createWallet, signMessage, resetWallet, addAccount };