forked from cerc-io/laconic-wallet
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:
parent
31c6999e9f
commit
8685c94151
@ -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 }}>
|
||||
|
@ -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"
|
||||
|
@ -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');
|
||||
|
||||
|
@ -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}>
|
||||
|
@ -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);
|
||||
|
2
types.ts
2
types.ts
@ -4,7 +4,7 @@ export type StackParamsList = {
|
||||
};
|
||||
|
||||
export type Account = {
|
||||
id: number,
|
||||
id: number;
|
||||
pubKey: string;
|
||||
address: string;
|
||||
};
|
||||
|
200
utils.ts
200
utils.ts
@ -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 };
|
||||
|
Loading…
Reference in New Issue
Block a user