forked from cerc-io/laconic-wallet
Merge pull request #20 from deep-stack/iv-add-account-from-hdpath
[WIP] Create account from hd path given by user
This commit is contained in:
commit
c1ca38ff23
@ -1,26 +1,27 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { TouchableOpacity, View } from 'react-native';
|
import { TouchableOpacity, View } from 'react-native';
|
||||||
import { Button, List, Text, useTheme } from 'react-native-paper';
|
import { Button, List, Text, useTheme } from 'react-native-paper';
|
||||||
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
|
|
||||||
import { AccountsProps, StackParamsList, Account } from '../types';
|
import { AccountsProps, StackParamsList, Account } from '../types';
|
||||||
import { addAccount } from '../utils';
|
import { addAccount } from '../utils/Accounts';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
|
import HDPathDialog from './HDPathDialog';
|
||||||
|
|
||||||
const Accounts: React.FC<AccountsProps> = ({
|
const Accounts = ({
|
||||||
network,
|
network,
|
||||||
accounts,
|
accounts,
|
||||||
updateAccounts,
|
updateAccounts,
|
||||||
currentIndex,
|
currentIndex,
|
||||||
updateIndex,
|
updateIndex: updateId,
|
||||||
}) => {
|
}: AccountsProps) => {
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
|
const [hdDialog, setHdDialog] = useState(false);
|
||||||
|
const [pathCode, setPathCode] = useState('');
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const handlePress = () => setExpanded(!expanded);
|
const handlePress = () => setExpanded(!expanded);
|
||||||
|
|
||||||
@ -28,11 +29,9 @@ const Accounts: React.FC<AccountsProps> = ({
|
|||||||
setIsAccountCreating(true);
|
setIsAccountCreating(true);
|
||||||
const newAccount = await addAccount(network);
|
const newAccount = await addAccount(network);
|
||||||
setIsAccountCreating(false);
|
setIsAccountCreating(false);
|
||||||
setExpanded(false);
|
|
||||||
|
|
||||||
if (newAccount) {
|
if (newAccount) {
|
||||||
updateAccounts(newAccount);
|
updateAccounts(newAccount);
|
||||||
updateIndex(selectedAccounts[selectedAccounts.length - 1].id + 1);
|
updateId(newAccount.counterId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,18 +49,24 @@ const Accounts: React.FC<AccountsProps> = ({
|
|||||||
const renderAccountItems = () =>
|
const renderAccountItems = () =>
|
||||||
selectedAccounts.map(account => (
|
selectedAccounts.map(account => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={account.id}
|
key={account.counterId}
|
||||||
title={`Account ${account.id + 1}`}
|
title={`Account ${account.counterId + 1}`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
updateIndex(account.id);
|
updateId(account.counterId);
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
<HDPathDialog
|
||||||
|
visible={hdDialog}
|
||||||
|
hideDialog={() => setHdDialog(false)}
|
||||||
|
updateAccounts={updateAccounts}
|
||||||
|
updateIndex={updateId}
|
||||||
|
pathCode={pathCode}
|
||||||
|
/>
|
||||||
<List.Accordion
|
<List.Accordion
|
||||||
title={`Account ${currentIndex + 1}`}
|
title={`Account ${currentIndex + 1}`}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
@ -78,6 +83,17 @@ const Accounts: React.FC<AccountsProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.addAccountButton}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={() => {
|
||||||
|
setHdDialog(true);
|
||||||
|
setPathCode(network === 'eth' ? "m/44'/60'/" : "m/44'/118'/");
|
||||||
|
}}>
|
||||||
|
Add Account from HD path
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={styles.accountContainer}>
|
<View style={styles.accountContainer}>
|
||||||
<Text variant="bodyLarge">
|
<Text variant="bodyLarge">
|
||||||
<Text style={styles.highlight}>Address: </Text>
|
<Text style={styles.highlight}>Address: </Text>
|
||||||
@ -87,6 +103,10 @@ const Accounts: React.FC<AccountsProps> = ({
|
|||||||
<Text style={styles.highlight}>Public Key: </Text>
|
<Text style={styles.highlight}>Public Key: </Text>
|
||||||
{selectedAccounts[currentIndex]?.pubKey}
|
{selectedAccounts[currentIndex]?.pubKey}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text variant="bodyLarge">
|
||||||
|
<Text style={{ fontWeight: '700' }}>HD Path: </Text>
|
||||||
|
{selectedAccounts[currentIndex]?.hdPath}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.signLink}>
|
<View style={styles.signLink}>
|
||||||
|
@ -12,11 +12,11 @@ type CustomDialogProps = {
|
|||||||
titleText?: string;
|
titleText?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DialogComponent: React.FC<CustomDialogProps> = ({
|
const DialogComponent = ({
|
||||||
visible,
|
visible,
|
||||||
hideDialog,
|
hideDialog,
|
||||||
contentText,
|
contentText,
|
||||||
}) => {
|
}: CustomDialogProps) => {
|
||||||
const words = contentText.split(' ');
|
const words = contentText.split(' ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -5,7 +5,7 @@ import { Text } from 'react-native-paper';
|
|||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import { GridViewProps } from '../types';
|
import { GridViewProps } from '../types';
|
||||||
|
|
||||||
const GridView: React.FC<GridViewProps> = ({ words }) => {
|
const GridView = ({ words }: GridViewProps) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.gridContainer}>
|
<View style={styles.gridContainer}>
|
||||||
{words.map((word, index) => (
|
{words.map((word, index) => (
|
||||||
|
64
components/HDPath.tsx
Normal file
64
components/HDPath.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { ScrollView, View, Text } from 'react-native';
|
||||||
|
import { Button, TextInput } from 'react-native-paper';
|
||||||
|
|
||||||
|
import { addAccountFromHDPath } from '../utils/Accounts';
|
||||||
|
import { Account } from '../types';
|
||||||
|
|
||||||
|
const HDPath = ({
|
||||||
|
pathCode,
|
||||||
|
updateAccounts,
|
||||||
|
updateIndex,
|
||||||
|
hideDialog,
|
||||||
|
}: {
|
||||||
|
pathCode: string;
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
|
updateAccounts: (account: Account) => void;
|
||||||
|
hideDialog: () => void;
|
||||||
|
}) => {
|
||||||
|
const [path, setPath] = useState<string>('');
|
||||||
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
|
|
||||||
|
const createFromHDPathHandler = async () => {
|
||||||
|
setIsAccountCreating(true);
|
||||||
|
const hdPath = pathCode + path;
|
||||||
|
try {
|
||||||
|
const newAccount = await addAccountFromHDPath(hdPath);
|
||||||
|
if (newAccount) {
|
||||||
|
updateAccounts(newAccount);
|
||||||
|
updateIndex(newAccount.counterId);
|
||||||
|
hideDialog();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating account:', error);
|
||||||
|
} finally {
|
||||||
|
setIsAccountCreating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView style={{ marginTop: 24, paddingHorizontal: 24 }}>
|
||||||
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
|
<Text style={{ color: 'black', fontSize: 18, padding: 10 }}>
|
||||||
|
{pathCode}
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
onChangeText={setPath}
|
||||||
|
value={path}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={{ marginTop: 20, width: 200, alignSelf: 'center' }}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={createFromHDPathHandler}
|
||||||
|
loading={isAccountCreating}>
|
||||||
|
{isAccountCreating ? 'Adding' : 'Add Account'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HDPath;
|
30
components/HDPathDialog.tsx
Normal file
30
components/HDPathDialog.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Portal, Dialog } from 'react-native-paper';
|
||||||
|
import { HDPathDialogProps } from '../types';
|
||||||
|
import HDPath from './HDPath';
|
||||||
|
|
||||||
|
const HDPathDialog = ({
|
||||||
|
visible,
|
||||||
|
hideDialog,
|
||||||
|
updateIndex,
|
||||||
|
updateAccounts,
|
||||||
|
pathCode,
|
||||||
|
}: HDPathDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<Dialog visible={visible} onDismiss={hideDialog}>
|
||||||
|
<Dialog.Title>Add account from HD path</Dialog.Title>
|
||||||
|
<Dialog.Content>
|
||||||
|
<HDPath
|
||||||
|
pathCode={pathCode}
|
||||||
|
updateIndex={updateIndex}
|
||||||
|
updateAccounts={updateAccounts}
|
||||||
|
hideDialog={hideDialog}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog>
|
||||||
|
</Portal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HDPathDialog;
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Alert, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
|
|
||||||
import { createWallet, resetWallet } from '../utils';
|
import { createWallet, resetWallet } from '../utils/Accounts';
|
||||||
import { DialogComponent } from './Dialog';
|
import { DialogComponent } from './Dialog';
|
||||||
import { NetworkDropdown } from './NetworkDropdown';
|
import { NetworkDropdown } from './NetworkDropdown';
|
||||||
import { Account, AccountsState } from '../types';
|
import { Account, AccountsState } from '../types';
|
||||||
@ -19,7 +19,6 @@ const HomeScreen = () => {
|
|||||||
const [network, setNetwork] = useState<string>('eth');
|
const [network, setNetwork] = useState<string>('eth');
|
||||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||||
const [phrase, setPhrase] = useState('');
|
const [phrase, setPhrase] = useState('');
|
||||||
|
|
||||||
const [accounts, setAccounts] = useState<AccountsState>({
|
const [accounts, setAccounts] = useState<AccountsState>({
|
||||||
ethAccounts: [],
|
ethAccounts: [],
|
||||||
cosmosAccounts: [],
|
cosmosAccounts: [],
|
||||||
@ -30,17 +29,16 @@ const HomeScreen = () => {
|
|||||||
|
|
||||||
const createWalletHandler = async () => {
|
const createWalletHandler = async () => {
|
||||||
setIsWalletCreating(true);
|
setIsWalletCreating(true);
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
const { mnemonic, ethAccounts, cosmosAccounts } = await createWallet();
|
const { mnemonic, ethAccounts, cosmosAccounts } = await createWallet();
|
||||||
ethAccounts &&
|
if (ethAccounts && cosmosAccounts) {
|
||||||
cosmosAccounts &&
|
|
||||||
setAccounts({
|
setAccounts({
|
||||||
ethAccounts: [...accounts.ethAccounts, ethAccounts],
|
ethAccounts: [...accounts.ethAccounts, ethAccounts],
|
||||||
cosmosAccounts: [...accounts.cosmosAccounts, cosmosAccounts],
|
cosmosAccounts: [...accounts.cosmosAccounts, cosmosAccounts],
|
||||||
});
|
});
|
||||||
setWalletDialog(true);
|
setWalletDialog(true);
|
||||||
setIsWalletCreated(true);
|
setIsWalletCreated(true);
|
||||||
setPhrase(mnemonic);
|
setPhrase(mnemonic);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmResetWallet = async () => {
|
const confirmResetWallet = async () => {
|
||||||
@ -80,9 +78,10 @@ const HomeScreen = () => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Alert.alert('Select a valid network!');
|
console.error('Select a valid network!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.appContainer}>
|
<View style={styles.appContainer}>
|
||||||
<DialogComponent
|
<DialogComponent
|
||||||
|
@ -5,7 +5,7 @@ import { List } from 'react-native-paper';
|
|||||||
import { NetworkDropdownProps } from '../types';
|
import { NetworkDropdownProps } from '../types';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
|
|
||||||
const NetworkDropdown: React.FC<NetworkDropdownProps> = ({ updateNetwork }) => {
|
const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
|
||||||
const [expanded, setExpanded] = useState<boolean>(false);
|
const [expanded, setExpanded] = useState<boolean>(false);
|
||||||
const [selectedNetwork, setSelectedNetwork] = useState<string>('Ethereum');
|
const [selectedNetwork, setSelectedNetwork] = useState<string>('Ethereum');
|
||||||
|
|
||||||
|
29
components/Section.tsx
Normal file
29
components/Section.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { Text, View, useColorScheme } from 'react-native';
|
||||||
|
import { Colors } from 'react-native/Libraries/NewAppScreen';
|
||||||
|
|
||||||
|
import styles from '../styles/stylesheet';
|
||||||
|
|
||||||
|
type SectionProps = PropsWithChildren<{
|
||||||
|
title: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const Section = ({ title }: SectionProps): React.JSX.Element => {
|
||||||
|
const isDarkMode = useColorScheme() === 'dark';
|
||||||
|
return (
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.sectionTitle,
|
||||||
|
{
|
||||||
|
color: isDarkMode ? Colors.white : Colors.black,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
{title}
|
||||||
|
<Text style={styles.sectionDescription} />
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Section };
|
@ -5,8 +5,8 @@ import { Button, Text, TextInput } from 'react-native-paper';
|
|||||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||||
|
|
||||||
import { StackParamsList } from '../types';
|
import { StackParamsList } from '../types';
|
||||||
import { signMessage } from '../utils';
|
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
|
import { signMessage } from '../utils/SignMessage';
|
||||||
|
|
||||||
type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
|
type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ const SignMessage = ({ route }: SignProps) => {
|
|||||||
const signedMessage = await signMessage({
|
const signedMessage = await signMessage({
|
||||||
message,
|
message,
|
||||||
network,
|
network,
|
||||||
accountId: account.id,
|
accountId: account.counterId,
|
||||||
});
|
});
|
||||||
Alert.alert('Signature', signedMessage);
|
Alert.alert('Signature', signedMessage);
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ const SignMessage = ({ route }: SignProps) => {
|
|||||||
<View style={styles.accountInfo}>
|
<View style={styles.accountInfo}>
|
||||||
<View>
|
<View>
|
||||||
<Text variant="headlineSmall">
|
<Text variant="headlineSmall">
|
||||||
{account && `Account ${account.id + 1}`}
|
{account && `Account ${account.counterId + 1}`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.accountContainer}>
|
<View style={styles.accountContainer}>
|
||||||
|
17
types.ts
17
types.ts
@ -1,12 +1,19 @@
|
|||||||
export type StackParamsList = {
|
export type StackParamsList = {
|
||||||
Laconic: undefined;
|
Laconic: undefined;
|
||||||
SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined;
|
SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined;
|
||||||
|
HDPath:
|
||||||
|
| {
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
|
updateAccounts: (account: Account) => void;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Account = {
|
export type Account = {
|
||||||
id: number;
|
counterId: number;
|
||||||
pubKey: string;
|
pubKey: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
hdPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WalletDetails = {
|
export type WalletDetails = {
|
||||||
@ -53,6 +60,14 @@ export type ResetDialogProps = {
|
|||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type HDPathDialogProps = {
|
||||||
|
pathCode: string;
|
||||||
|
visible: boolean;
|
||||||
|
hideDialog: () => void;
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
|
updateAccounts: (account: Account) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type GridViewProps = {
|
export type GridViewProps = {
|
||||||
words: string[];
|
words: string[];
|
||||||
};
|
};
|
||||||
|
254
utils.ts
254
utils.ts
@ -1,254 +0,0 @@
|
|||||||
/* Importing this library provides react native with a secure random source.
|
|
||||||
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
|
||||||
import 'react-native-get-random-values';
|
|
||||||
import '@ethersproject/shims';
|
|
||||||
|
|
||||||
import { Wallet, utils } from 'ethers';
|
|
||||||
import { HDNode } from 'ethers/lib/utils';
|
|
||||||
import {
|
|
||||||
setInternetCredentials,
|
|
||||||
getInternetCredentials,
|
|
||||||
resetInternetCredentials,
|
|
||||||
} from 'react-native-keychain';
|
|
||||||
|
|
||||||
import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino';
|
|
||||||
import { stringToPath } from '@cosmjs/crypto';
|
|
||||||
|
|
||||||
import { Account, SignMessageParams, WalletDetails } from './types';
|
|
||||||
|
|
||||||
const createWallet = async (): Promise<WalletDetails> => {
|
|
||||||
try {
|
|
||||||
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
|
|
||||||
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");
|
|
||||||
|
|
||||||
const ethAddress = ethNode.address;
|
|
||||||
const cosmosAddress = (await getCosmosAccounts(mnemonic, 0)).data.address;
|
|
||||||
|
|
||||||
await setInternetCredentials(
|
|
||||||
'eth:keyServer:0',
|
|
||||||
'eth:key:0',
|
|
||||||
ethNode.privateKey,
|
|
||||||
);
|
|
||||||
await setInternetCredentials(
|
|
||||||
'cosmos:keyServer:0',
|
|
||||||
'cosmos:key:0',
|
|
||||||
cosmosNode.privateKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
await setInternetCredentials('eth:accountIndices', 'ethAccount', '0');
|
|
||||||
await setInternetCredentials('cosmos:accountIndices', 'cosmosAccount', '0');
|
|
||||||
|
|
||||||
const ethAccounts = {
|
|
||||||
id: 0,
|
|
||||||
pubKey: ethNode.publicKey,
|
|
||||||
address: ethAddress,
|
|
||||||
};
|
|
||||||
|
|
||||||
const cosmosAccounts = {
|
|
||||||
id: 0,
|
|
||||||
pubKey: cosmosNode.publicKey,
|
|
||||||
address: cosmosAddress,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { mnemonic, ethAccounts, cosmosAccounts };
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating HD wallet:', error);
|
|
||||||
return { mnemonic: '', ethAccounts: undefined, cosmosAccounts: undefined };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addAccount = async (network: string): Promise<Account | undefined> => {
|
|
||||||
try {
|
|
||||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
|
||||||
if (!mnemonicStore) {
|
|
||||||
throw new Error('Mnemonic not found!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const mnemonic = mnemonicStore.password;
|
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
|
||||||
|
|
||||||
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,
|
|
||||||
network,
|
|
||||||
accountId,
|
|
||||||
}: SignMessageParams): Promise<string | undefined> => {
|
|
||||||
switch (network) {
|
|
||||||
case 'eth':
|
|
||||||
return await signEthMessage(message, accountId);
|
|
||||||
case 'cosmos':
|
|
||||||
return await signCosmosMessage(message, accountId);
|
|
||||||
default:
|
|
||||||
throw new Error('Invalid wallet type');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const signEthMessage = async (
|
|
||||||
message: string,
|
|
||||||
id: number,
|
|
||||||
): Promise<string | undefined> => {
|
|
||||||
try {
|
|
||||||
const keyCred = await getInternetCredentials(`eth:keyServer:${id}`);
|
|
||||||
|
|
||||||
if (!keyCred) {
|
|
||||||
throw new Error('Failed to retrieve internet credentials');
|
|
||||||
}
|
|
||||||
|
|
||||||
const wallet = new Wallet(keyCred.password);
|
|
||||||
const signature = await wallet.signMessage(message);
|
|
||||||
|
|
||||||
return signature;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error signing Ethereum message:', error);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const signCosmosMessage = async (
|
|
||||||
message: string,
|
|
||||||
id: number,
|
|
||||||
): Promise<string | undefined> => {
|
|
||||||
try {
|
|
||||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
|
||||||
|
|
||||||
if (!mnemonicStore) {
|
|
||||||
throw new Error('Mnemonic not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
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',
|
|
||||||
fee: {
|
|
||||||
gas: '0',
|
|
||||||
amount: [],
|
|
||||||
},
|
|
||||||
msgs: [
|
|
||||||
{
|
|
||||||
type: 'sign/MsgSignData',
|
|
||||||
value: {
|
|
||||||
signer: cosmosAccount.data.address,
|
|
||||||
data: btoa(message),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
memo: '',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return cosmosSignature.signature.signature;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error signing Cosmos message:', error);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 () => {
|
|
||||||
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, addAccount, signMessage, resetWallet };
|
|
168
utils/Accounts.ts
Normal file
168
utils/Accounts.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/* Importing this library provides react native with a secure random source.
|
||||||
|
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||||
|
import 'react-native-get-random-values';
|
||||||
|
import '@ethersproject/shims';
|
||||||
|
|
||||||
|
import { utils } from 'ethers';
|
||||||
|
import { HDNode } from 'ethers/lib/utils';
|
||||||
|
import {
|
||||||
|
setInternetCredentials,
|
||||||
|
resetInternetCredentials,
|
||||||
|
} from 'react-native-keychain';
|
||||||
|
|
||||||
|
import { Account, WalletDetails } from '../types';
|
||||||
|
import {
|
||||||
|
accountInfoFromHDPath,
|
||||||
|
getAddress,
|
||||||
|
getCosmosAccounts,
|
||||||
|
getHDPath,
|
||||||
|
getMnemonic,
|
||||||
|
getNextAccountId,
|
||||||
|
resetKeyServers,
|
||||||
|
updateAccountIndices,
|
||||||
|
updateGlobalCounter,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
const createWallet = async (): Promise<WalletDetails> => {
|
||||||
|
try {
|
||||||
|
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
|
||||||
|
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");
|
||||||
|
|
||||||
|
const ethAddress = ethNode.address;
|
||||||
|
const cosmosAddress = (await getCosmosAccounts(mnemonic, `0'/0/0`)).data
|
||||||
|
.address;
|
||||||
|
|
||||||
|
const ethAccountInfo = `${`0'/0/0`},${ethNode.privateKey}`;
|
||||||
|
const cosmosAccountInfo = `${`0'/0/0`},${cosmosNode.privateKey}`;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
setInternetCredentials(
|
||||||
|
'eth:keyServer:0',
|
||||||
|
'eth:pathKey:0',
|
||||||
|
ethAccountInfo,
|
||||||
|
),
|
||||||
|
setInternetCredentials(
|
||||||
|
'cosmos:keyServer:0',
|
||||||
|
'cosmos:pathKey:0',
|
||||||
|
cosmosAccountInfo,
|
||||||
|
),
|
||||||
|
setInternetCredentials('eth:accountIndices', 'ethCounter', '0'),
|
||||||
|
setInternetCredentials('cosmos:accountIndices', 'cosmosCounter', '0'),
|
||||||
|
setInternetCredentials('eth:globalCounter', 'ethGlobal', '0'),
|
||||||
|
setInternetCredentials('cosmos:globalCounter', 'cosmosGlobal', '0'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ethAccounts = {
|
||||||
|
counterId: 0,
|
||||||
|
pubKey: ethNode.publicKey,
|
||||||
|
address: ethAddress,
|
||||||
|
hdPath: "m/44'/60'/0'/0/0",
|
||||||
|
};
|
||||||
|
|
||||||
|
const cosmosAccounts = {
|
||||||
|
counterId: 0,
|
||||||
|
pubKey: cosmosNode.publicKey,
|
||||||
|
address: cosmosAddress,
|
||||||
|
hdPath: "m/44'/118'/0'/0/0",
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mnemonic, ethAccounts, cosmosAccounts };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating HD wallet:', error);
|
||||||
|
return { mnemonic: '', ethAccounts: undefined, cosmosAccounts: undefined };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAccount = async (network: string): Promise<Account | undefined> => {
|
||||||
|
try {
|
||||||
|
const mnemonic = await getMnemonic();
|
||||||
|
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||||
|
const id = await getNextAccountId(network);
|
||||||
|
const hdPath = getHDPath(network, id);
|
||||||
|
|
||||||
|
const node = hdNode.derivePath(hdPath);
|
||||||
|
const pubKey = node.publicKey;
|
||||||
|
const address = await getAddress(network, mnemonic, id);
|
||||||
|
|
||||||
|
await updateAccountIndices(network, id);
|
||||||
|
const { accountCounter, counterId } = await updateGlobalCounter(network);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
resetInternetCredentials(`${network}:globalCounter`),
|
||||||
|
setInternetCredentials(
|
||||||
|
`${network}:globalCounter`,
|
||||||
|
`${network}Global`,
|
||||||
|
accountCounter,
|
||||||
|
),
|
||||||
|
setInternetCredentials(
|
||||||
|
`${network}:keyServer:${counterId}`,
|
||||||
|
`${network}:pathKey:${counterId}`,
|
||||||
|
`0'/0/${id},${node.privateKey}`,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { counterId, pubKey, address, hdPath };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating account:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAccountFromHDPath = async (
|
||||||
|
hdPath: string,
|
||||||
|
): Promise<Account | undefined> => {
|
||||||
|
try {
|
||||||
|
const account = await accountInfoFromHDPath(hdPath);
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('Error while creating account');
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = hdPath.split('/');
|
||||||
|
const path = parts.slice(-3).join('/');
|
||||||
|
|
||||||
|
const { privKey, pubKey, address, network } = account;
|
||||||
|
|
||||||
|
const { accountCounter, counterId } = await updateGlobalCounter(network);
|
||||||
|
const updatedAccountCounter = `${accountCounter},${counterId.toString()}`;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
resetInternetCredentials(`${network}:globalCounter`),
|
||||||
|
setInternetCredentials(
|
||||||
|
`${network}:globalCounter`,
|
||||||
|
`${network}Global`,
|
||||||
|
updatedAccountCounter,
|
||||||
|
),
|
||||||
|
setInternetCredentials(
|
||||||
|
`${network}:keyServer:${counterId}`,
|
||||||
|
`${network}:pathKey:${counterId}`,
|
||||||
|
`${path},${privKey}`,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { counterId, pubKey, address, hdPath };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetWallet = async () => {
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
resetInternetCredentials('mnemonicServer'),
|
||||||
|
resetKeyServers('eth'),
|
||||||
|
resetKeyServers('cosmos'),
|
||||||
|
resetInternetCredentials('eth:accountIndices'),
|
||||||
|
resetInternetCredentials('cosmos:accountIndices'),
|
||||||
|
resetInternetCredentials('eth:globalCounter'),
|
||||||
|
resetInternetCredentials('cosmos:globalCounter'),
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resetting wallet:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { createWallet, addAccount, addAccountFromHDPath, resetWallet };
|
82
utils/SignMessage.ts
Normal file
82
utils/SignMessage.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* Importing this library provides react native with a secure random source.
|
||||||
|
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||||
|
import 'react-native-get-random-values';
|
||||||
|
import '@ethersproject/shims';
|
||||||
|
|
||||||
|
import { Wallet } from 'ethers';
|
||||||
|
|
||||||
|
import { SignMessageParams } from '../types';
|
||||||
|
import { getCosmosAccounts, getMnemonic, getPathKey } from './utils';
|
||||||
|
|
||||||
|
const signMessage = async ({
|
||||||
|
message,
|
||||||
|
network,
|
||||||
|
accountId,
|
||||||
|
}: SignMessageParams): Promise<string | undefined> => {
|
||||||
|
const hdPath = (await getPathKey(network, accountId)).hdPath;
|
||||||
|
|
||||||
|
switch (network) {
|
||||||
|
case 'eth':
|
||||||
|
return await signEthMessage(message, accountId);
|
||||||
|
case 'cosmos':
|
||||||
|
return await signCosmosMessage(message, hdPath);
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid wallet type');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signEthMessage = async (
|
||||||
|
message: string,
|
||||||
|
accountId: number,
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
try {
|
||||||
|
const privKey = (await getPathKey('eth', accountId)).privKey;
|
||||||
|
const wallet = new Wallet(privKey);
|
||||||
|
const signature = await wallet.signMessage(message);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error signing Ethereum message:', error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signCosmosMessage = async (
|
||||||
|
message: string,
|
||||||
|
hdPath: string,
|
||||||
|
): Promise<string | undefined> => {
|
||||||
|
try {
|
||||||
|
const mnemonic = await getMnemonic();
|
||||||
|
const cosmosAccount = await getCosmosAccounts(mnemonic, hdPath);
|
||||||
|
const address = cosmosAccount.data.address;
|
||||||
|
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
|
||||||
|
address,
|
||||||
|
{
|
||||||
|
chain_id: '',
|
||||||
|
account_number: '0',
|
||||||
|
sequence: '0',
|
||||||
|
fee: {
|
||||||
|
gas: '0',
|
||||||
|
amount: [],
|
||||||
|
},
|
||||||
|
msgs: [
|
||||||
|
{
|
||||||
|
type: 'sign/MsgSignData',
|
||||||
|
value: {
|
||||||
|
signer: address,
|
||||||
|
data: btoa(message),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
memo: '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return cosmosSignature.signature.signature;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error signing Cosmos message:', error);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { signMessage, signEthMessage, signCosmosMessage };
|
213
utils/utils.ts
Normal file
213
utils/utils.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
/* Importing this library provides react native with a secure random source.
|
||||||
|
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||||
|
import 'react-native-get-random-values';
|
||||||
|
import '@ethersproject/shims';
|
||||||
|
|
||||||
|
import { HDNode } from 'ethers/lib/utils';
|
||||||
|
import {
|
||||||
|
getInternetCredentials,
|
||||||
|
resetInternetCredentials,
|
||||||
|
setInternetCredentials,
|
||||||
|
} from 'react-native-keychain';
|
||||||
|
|
||||||
|
import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino';
|
||||||
|
import { stringToPath } from '@cosmjs/crypto';
|
||||||
|
|
||||||
|
const getMnemonic = async (): Promise<string> => {
|
||||||
|
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||||
|
if (!mnemonicStore) {
|
||||||
|
throw new Error('Mnemonic not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mnemonic = mnemonicStore.password;
|
||||||
|
return mnemonic;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHDPath = (network: string, id: number): string => {
|
||||||
|
return network === 'eth' ? `m/44'/60'/0'/0/${id}` : `m/44'/118'/0'/0/${id}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAddress = async (
|
||||||
|
network: string,
|
||||||
|
mnemonic: string,
|
||||||
|
id: number,
|
||||||
|
): Promise<string> => {
|
||||||
|
switch (network) {
|
||||||
|
case 'eth':
|
||||||
|
return HDNode.fromMnemonic(mnemonic).derivePath(`m/44'/60'/0'/0/${id}`)
|
||||||
|
.address;
|
||||||
|
case 'cosmos':
|
||||||
|
return (await getCosmosAccounts(mnemonic, `0'/0/${id}`)).data.address;
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid wallet type');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCosmosAccounts = async (
|
||||||
|
mnemonic: string,
|
||||||
|
hdPath: string,
|
||||||
|
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
|
||||||
|
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
|
||||||
|
hdPaths: [stringToPath(`m/44'/118'/${hdPath}`)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const accountsData = await cosmosWallet.getAccounts();
|
||||||
|
const data = accountsData[0];
|
||||||
|
|
||||||
|
return { cosmosWallet, data };
|
||||||
|
};
|
||||||
|
|
||||||
|
const accountInfoFromHDPath = async (
|
||||||
|
hdPath: string,
|
||||||
|
): Promise<
|
||||||
|
| { privKey: string; pubKey: string; address: string; network: string }
|
||||||
|
| undefined
|
||||||
|
> => {
|
||||||
|
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||||
|
if (!mnemonicStore) {
|
||||||
|
throw new Error('Mnemonic not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mnemonic = mnemonicStore.password;
|
||||||
|
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||||
|
const node = hdNode.derivePath(hdPath);
|
||||||
|
|
||||||
|
const privKey = node.privateKey;
|
||||||
|
const pubKey = node.publicKey;
|
||||||
|
|
||||||
|
const parts = hdPath.split('/');
|
||||||
|
const path = parts.slice(-3).join('/');
|
||||||
|
const coinType = parts[2];
|
||||||
|
|
||||||
|
let network: string;
|
||||||
|
let address: string;
|
||||||
|
|
||||||
|
switch (coinType) {
|
||||||
|
case "60'":
|
||||||
|
network = 'eth';
|
||||||
|
address = node.address;
|
||||||
|
break;
|
||||||
|
case "118'":
|
||||||
|
network = 'cosmos';
|
||||||
|
address = (await getCosmosAccounts(mnemonic, path)).data.address;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid wallet type');
|
||||||
|
}
|
||||||
|
return { privKey, pubKey, address, network };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPathKey = async (
|
||||||
|
network: string,
|
||||||
|
accountId: number,
|
||||||
|
): Promise<{ hdPath: string; privKey: string }> => {
|
||||||
|
const pathKeyStore = await getInternetCredentials(
|
||||||
|
`${network}:keyServer:${accountId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!pathKeyStore) {
|
||||||
|
throw new Error('Error while fetching counter');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathKeyVal = pathKeyStore.password;
|
||||||
|
const pathkey = pathKeyVal.split(',');
|
||||||
|
const hdPath = pathkey[0];
|
||||||
|
const privKey = pathkey[1];
|
||||||
|
|
||||||
|
return { hdPath, privKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGlobalCounter = async (
|
||||||
|
network: string,
|
||||||
|
): Promise<{
|
||||||
|
accountCounter: string;
|
||||||
|
counterIds: number[];
|
||||||
|
counterId: number;
|
||||||
|
}> => {
|
||||||
|
const counterStore = await getInternetCredentials(`${network}:globalCounter`);
|
||||||
|
|
||||||
|
if (!counterStore) {
|
||||||
|
throw new Error('Error while fetching counter');
|
||||||
|
}
|
||||||
|
|
||||||
|
let accountCounter = counterStore.password;
|
||||||
|
const counterIds = accountCounter.split(',').map(Number);
|
||||||
|
const counterId = counterIds[counterIds.length - 1] + 1;
|
||||||
|
|
||||||
|
return { accountCounter, counterIds, counterId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGlobalCounter = async (
|
||||||
|
network: string,
|
||||||
|
): Promise<{ accountCounter: string; counterId: number }> => {
|
||||||
|
const globalCounterData = await getGlobalCounter(network);
|
||||||
|
const accountCounter = globalCounterData.accountCounter;
|
||||||
|
const counterId = globalCounterData.counterId;
|
||||||
|
const updatedAccountCounter = `${accountCounter},${counterId.toString()}`;
|
||||||
|
|
||||||
|
await resetInternetCredentials(`${network}:globalCounter`);
|
||||||
|
await setInternetCredentials(
|
||||||
|
`${network}:globalCounter`,
|
||||||
|
`${network}Global`,
|
||||||
|
updatedAccountCounter,
|
||||||
|
);
|
||||||
|
|
||||||
|
return { accountCounter: updatedAccountCounter, counterId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextAccountId = async (network: string): Promise<number> => {
|
||||||
|
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);
|
||||||
|
return ids[ids.length - 1] + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAccountIndices = async (
|
||||||
|
network: string,
|
||||||
|
id: number,
|
||||||
|
): Promise<void> => {
|
||||||
|
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||||
|
if (!idStore) {
|
||||||
|
throw new Error('Account id not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedIndices = `${idStore.password},${id.toString()}`;
|
||||||
|
await resetInternetCredentials(`${network}:accountIndices`);
|
||||||
|
await setInternetCredentials(
|
||||||
|
`${network}:accountIndices`,
|
||||||
|
`${network}Counter`,
|
||||||
|
updatedIndices,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
accountInfoFromHDPath,
|
||||||
|
getCosmosAccounts,
|
||||||
|
getMnemonic,
|
||||||
|
getPathKey,
|
||||||
|
getNextAccountId,
|
||||||
|
updateGlobalCounter,
|
||||||
|
updateAccountIndices,
|
||||||
|
getHDPath,
|
||||||
|
getAddress,
|
||||||
|
resetKeyServers,
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user