Integrate functionality to add new accounts with UI (#17)

* Create addAccount function

* Make review changes

* Create addAccount function

* Add id for each account

* Modify resetWallet function

* Make review changes

* Integrate functions
This commit is contained in:
IshaVenikar 2024-02-15 18:10:15 +05:30 committed by Ashwin Phatak
parent 31c6999e9f
commit 8685c94151
7 changed files with 164 additions and 75 deletions

View File

@ -19,13 +19,15 @@ const Accounts: React.FC<AccountsProps> = ({
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [expanded, setExpanded] = useState(false);
const [isAccountCreating, setIsAccountCreating] = useState(false);
const handlePress = () => setExpanded(!expanded);
const addAccountHandler = async () => {
setIsAccountCreating(true);
const newAccount = await addAccount(network);
setIsAccountCreating(false);
newAccount && updateAccounts(newAccount);
updateIndex(selectedAccounts[selectedAccounts.length -1].id);
updateIndex(selectedAccounts[selectedAccounts.length - 1].id + 1);
};
let selectedAccounts: Account[] = [];
@ -44,7 +46,7 @@ const Accounts: React.FC<AccountsProps> = ({
expanded={expanded}
onPress={handlePress}>
{selectedAccounts &&
selectedAccounts.map((account) => (
selectedAccounts.map(account => (
<List.Item
key={account.id}
title={`Account ${account.id + 1}`}
@ -56,8 +58,11 @@ const Accounts: React.FC<AccountsProps> = ({
))}
</List.Accordion>
<View style={{ alignItems: 'center', marginTop: 24 }}>
<Button mode="contained" onPress={addAccountHandler}>
Add Account
<Button
mode="contained"
onPress={addAccountHandler}
loading={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}{' '}
</Button>
</View>
<View style={{ marginTop: 24 }}>

View File

@ -29,11 +29,11 @@ const HomeScreen = () => {
await new Promise(resolve => setTimeout(resolve, 2000));
const { ethWalletInfo, cosmosWalletInfo } = await createWallet();
ethWalletInfo &&
cosmosWalletInfo &&
setAccounts({
ethAccounts: [...accounts.ethAccounts, ethWalletInfo],
cosmosAccounts: [...accounts.cosmosAccounts, cosmosWalletInfo],
});
cosmosWalletInfo &&
setAccounts({
ethAccounts: [...accounts.ethAccounts, ethWalletInfo],
cosmosAccounts: [...accounts.cosmosAccounts, cosmosWalletInfo],
});
setWalletDialog(true);
setIsWalletCreated(true);
};
@ -112,7 +112,7 @@ const HomeScreen = () => {
updateIndex={updateIndex}
updateAccounts={updateAccounts}
/>
<View style={{ marginTop: 300, alignSelf: 'center' }}>
<View style={{ marginTop: 200, alignSelf: 'center' }}>
<Button
mode="contained"
buttonColor="#B82B0D"

View File

@ -4,9 +4,7 @@ import { List } from 'react-native-paper';
import { NetworkDropdownProps } from '../types';
const NetworkDropdown: React.FC<NetworkDropdownProps> = ({
updateNetwork,
}) => {
const NetworkDropdown: React.FC<NetworkDropdownProps> = ({ updateNetwork }) => {
const [expanded, setExpanded] = useState<boolean>(false);
const [title, setTitle] = useState<string>('Ethereum');

View File

@ -8,7 +8,7 @@ type SectionProps = PropsWithChildren<{
title: string;
}>;
const Section = ({ children, title }: SectionProps): React.JSX.Element => {
const Section = ({ title }: SectionProps): React.JSX.Element => {
const isDarkMode = useColorScheme() === 'dark';
return (
<View style={styles.sectionContainer}>

View File

@ -17,8 +17,8 @@ const SignMessage = ({ route }: SignProps) => {
const signMessageHandler = async () => {
if (network) {
if (!account){
throw new Error("Account is not valid");
if (!account) {
throw new Error('Account is not valid');
}
const signedMessage = await signMessage(message, network, account.id);
Alert.alert('Signature', signedMessage);

View File

@ -4,7 +4,7 @@ export type StackParamsList = {
};
export type Account = {
id: number,
id: number;
pubKey: string;
address: string;
};

200
utils.ts
View File

@ -23,7 +23,6 @@ const createWallet = async (): Promise<{
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
const hdNode = HDNode.fromMnemonic(mnemonic);
const ethNode = hdNode.derivePath("m/44'/60'/0'/0/0");
const cosmosNode = hdNode.derivePath("m/44'/118'/0'/0/0");
@ -41,11 +40,15 @@ const createWallet = async (): Promise<{
cosmosNode.privateKey,
);
await setInternetCredentials('eth:accountIndices', 'ethAccount', '0');
await setInternetCredentials('cosmos:accountIndices', 'cosmosAccount', '0');
const ethWalletInfo = {
id: 0,
pubKey: ethNode.publicKey,
address: ethAddress,
};
const cosmosWalletInfo = {
id: 0,
pubKey: cosmosNode.publicKey,
@ -55,52 +58,107 @@ const createWallet = async (): Promise<{
return { ethWalletInfo, cosmosWalletInfo };
} catch (error) {
console.error('Error creating HD wallet:', error);
return { ethWalletInfo: undefined, cosmosWalletInfo: undefined };
}
};
async function getCosmosAccounts(
mnemonic: string,
index: number,
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> {
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
hdPaths: [stringToPath(`m/44'/118'/0'/0/${index}`)],
});
const addAccount = async (
network: string,
): Promise<
| {
pubKey: string;
address: string;
id: number;
}
| undefined
> => {
try {
const mnemonicStore = await getInternetCredentials('mnemonicServer');
if (!mnemonicStore) {
throw new Error('Mnemonic not found!');
}
const accountsData = await cosmosWallet.getAccounts();
const data = accountsData[index];
const mnemonic = mnemonicStore.password;
const hdNode = HDNode.fromMnemonic(mnemonic);
return { cosmosWallet, data };
}
const idStore = await getInternetCredentials(`${network}:accountIndices`);
if (!idStore) {
throw new Error('Account id not found');
}
const accountIds = idStore.password;
const ids = accountIds.split(',').map(Number);
const id = ids[ids.length - 1] + 1;
const derivationPath =
network === 'eth' ? `m/44'/60'/0'/0/${id}` : `m/44'/118'/0'/0/${id}`;
const node = hdNode.derivePath(derivationPath);
const privKey = node.privateKey;
const pubKey = node.publicKey;
let address: string;
switch (network) {
case 'eth':
address = node.address;
break;
case 'cosmos':
address = (await getCosmosAccounts(mnemonic, id)).data.address;
break;
default:
throw new Error('Invalid wallet type');
}
await setInternetCredentials(
`${network}:keyServer:${id}`,
`${network}:key:${id}`,
privKey,
);
const accountIndicesKey = `${network}:accountIndices`;
const accountIndices = await getInternetCredentials(accountIndicesKey);
if (!accountIndices) {
throw new Error('Account not found!');
}
let indices = accountIndices.password;
indices += `,${id.toString()}`;
await resetInternetCredentials(accountIndicesKey);
await setInternetCredentials(
accountIndicesKey,
`${network}Account`,
indices,
);
return { pubKey, address, id };
} catch (error) {
console.error('Error creating account:', error);
}
};
const signMessage = async (
message: string,
walletType: string,
index: number,
id: number,
): Promise<string | undefined> => {
try {
switch (walletType) {
case 'eth':
return await signEthMessage(message, index);
case 'cosmos':
return await signCosmosMessage(message, index);
default:
throw new Error('Invalid wallet type');
}
} catch (error) {
console.error('Error signing message:', error);
switch (walletType) {
case 'eth':
return await signEthMessage(message, id);
case 'cosmos':
return await signCosmosMessage(message, id);
default:
throw new Error('Invalid wallet type');
}
};
const signEthMessage = async (
message: string,
index: number,
id: number,
): Promise<string | undefined> => {
try {
const keyCred = await getInternetCredentials(`eth:keyServer:${index}`);
const keyCred = await getInternetCredentials(`eth:keyServer:${id}`);
if (!keyCred) {
throw new Error('Failed to retrieve internet credentials');
@ -112,24 +170,26 @@ const signEthMessage = async (
return signature;
} catch (error) {
console.error('Error signing Ethereum message:', error);
return undefined;
}
};
const signCosmosMessage = async (
message: string,
index: number,
id: number,
): Promise<string | undefined> => {
try {
const mnemonicStore = await getInternetCredentials('mnemonicServer');
if (mnemonicStore) {
const mnemonic = mnemonicStore.password;
const cosmosAddress = (await getCosmosAccounts(mnemonic, index)).data
.address;
if (!mnemonicStore) {
throw new Error('Mnemonic not found');
}
const cosmosSignature = await (
await getCosmosAccounts(mnemonic, index)
).cosmosWallet.signAmino(cosmosAddress, {
const mnemonic = mnemonicStore.password;
const cosmosAccount = await getCosmosAccounts(mnemonic, id);
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
cosmosAccount.data.address,
{
chain_id: '',
account_number: '0',
sequence: '0',
@ -141,38 +201,64 @@ const signCosmosMessage = async (
{
type: 'sign/MsgSignData',
value: {
signer: cosmosAddress,
signer: cosmosAccount.data.address,
data: btoa(message),
},
},
],
memo: '',
});
},
);
return cosmosSignature.signature.signature;
}
return cosmosSignature.signature.signature;
} catch (error) {
console.error('Error signing message:', error);
console.error('Error signing Cosmos message:', error);
return undefined;
}
};
// const createAccount
const addAccount = async (network: string) => {
// // const index = 5;
// switch (network) {
// case 'eth':
// return dummyEthAccounts[3];
// case 'cosmos':
// return dummyCosmosAccounts[3];
// }
const getCosmosAccounts = async (
mnemonic: string,
id: number,
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
hdPaths: [stringToPath(`m/44'/118'/0'/0/${id}`)],
});
const accountsData = await cosmosWallet.getAccounts();
const data = accountsData[0];
return { cosmosWallet, data };
};
const resetKeyServers = async (prefix: string) => {
const idStore = await getInternetCredentials(`${prefix}:accountIndices`);
if (!idStore) {
throw new Error('Account id not found.');
}
const accountIds = idStore.password;
const ids = accountIds.split(',').map(Number);
const id = ids[ids.length - 1];
for (let i = 0; i <= id; i++) {
await resetInternetCredentials(`${prefix}:keyServer:${i}`);
}
};
const resetWallet = async () => {
// TODO: Add method to reset all the accounts
await resetInternetCredentials('mnemonicServer');
await resetInternetCredentials('eth:keyServer:0');
await resetInternetCredentials('cosmos:keyServer:0');
try {
await resetInternetCredentials('mnemonicServer');
await resetKeyServers('eth');
await resetKeyServers('cosmos');
await resetInternetCredentials('eth:accountIndices');
await resetInternetCredentials('cosmos:accountIndices');
} catch (error) {
console.error('Error resetting wallet:', error);
throw error;
}
};
export { createWallet, signMessage, resetWallet, addAccount };
export { createWallet, addAccount, signMessage, resetWallet };