Compare commits

..

1 Commits

Author SHA1 Message Date
bb5223afda Add support for staking module tx MsgCreateValidator (#14)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: cerc-io/laconic-wallet#14
2024-08-09 09:41:46 +00:00
7 changed files with 44 additions and 125 deletions

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import React from 'react';
import { Button } from 'react-native-paper'; import { Button } from 'react-native-paper';
import { CreateWalletProps } from '../types'; import { CreateWalletProps } from '../types';

View File

@ -8,7 +8,7 @@ import styles from '../styles/stylesheet';
import GridView from './Grid'; import GridView from './Grid';
import { CustomDialogProps } from '../types'; import { CustomDialogProps } from '../types';
const MnemonicDialog = ({ const DialogComponent = ({
visible, visible,
hideDialog, hideDialog,
contentText, contentText,
@ -55,4 +55,4 @@ const MnemonicDialog = ({
); );
}; };
export { MnemonicDialog }; export { DialogComponent };

View File

@ -1,60 +0,0 @@
import React, { useState } from 'react';
import { View, TextInput } from 'react-native';
import { Dialog, Portal, Paragraph, Button } from 'react-native-paper';
import styles from '../styles/stylesheet';
const ImportWalletDialog = ({
visible,
hideDialog,
importWalletHandler,
}: {
visible: boolean;
hideDialog: () => void;
importWalletHandler: (recoveryPhrase: string) => Promise<void>;
}) => {
const [words, setWords] = useState<string[]>(Array(12).fill(''));
const handleWordChange = (index: number, value: string) => {
const newWords = [...words];
newWords[index] = value;
setWords(newWords);
};
return (
<Portal>
<Dialog visible={visible} onDismiss={hideDialog}>
<Dialog.Title>Import your wallet from your mnemonic</Dialog.Title>
<Dialog.Content>
<Paragraph>
(You can paste your entire mnemonic into the first textbox)
</Paragraph>
<View style={styles.container}>
{words.map((word, index) => (
<View style={styles.inputContainer} key={index}>
<TextInput
value={word}
onChangeText={text => handleWordChange(index, text)}
placeholder={`Word ${index + 1}`}
style={styles.textInput}
/>
</View>
))}
</View>
</Dialog.Content>
<Dialog.Actions>
<Button
mode="contained"
onPress={() => importWalletHandler(words.join(' '))}>
Import Wallet
</Button>
<Button mode="text" onPress={hideDialog}>
Cancel
</Button>
</Dialog.Actions>
</Dialog>
</Portal>
);
};
export default ImportWalletDialog;

View File

@ -1,15 +1,16 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Image, ScrollView, View } from 'react-native'; import { Image, ScrollView, View } from 'react-native';
import { Button, Text, TextInput } from 'react-native-paper'; import { Button, Text, TextInput } from 'react-native-paper';
import { SvgUri } from 'react-native-svg'; import { SvgUri } from 'react-native-svg';
import Config from 'react-native-config'; import Config from 'react-native-config';
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
import { import {
NativeStackNavigationProp, NativeStackNavigationProp,
NativeStackScreenProps, NativeStackScreenProps,
} from '@react-navigation/native-stack'; } from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { DirectSecp256k1Wallet, EncodeObject } from '@cosmjs/proto-signing';
import { LaconicClient } from '@cerc-io/registry-sdk'; import { LaconicClient } from '@cerc-io/registry-sdk';
import { GasPrice, calculateFee } from '@cosmjs/stargate'; import { GasPrice, calculateFee } from '@cosmjs/stargate';
import { formatJsonRpcError } from '@json-rpc-tools/utils'; import { formatJsonRpcError } from '@json-rpc-tools/utils';
@ -42,7 +43,6 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
const requestName = requestSession.peer.metadata.name; const requestName = requestSession.peer.metadata.name;
const requestIcon = requestSession.peer.metadata.icons[0]; const requestIcon = requestSession.peer.metadata.icons[0];
const requestURL = requestSession.peer.metadata.url; const requestURL = requestSession.peer.metadata.url;
const transactionMessage = route.params.transactionMessage;
const signer = route.params.signer; const signer = route.params.signer;
const requestEvent = route.params.requestEvent; const requestEvent = route.params.requestEvent;
const chainId = requestEvent.params.chainId; const chainId = requestEvent.params.chainId;
@ -69,6 +69,20 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
); );
const namespace = requestedNetwork!.namespace; const namespace = requestedNetwork!.namespace;
const transactionMessage = useMemo((): EncodeObject => {
const inputTxMsg = route.params.transactionMessage;
// If it's a MsgCreateValidator, decode the tx msg value using MsgCreateValidator type
if (inputTxMsg.typeUrl.includes('MsgCreateValidator')) {
return {
typeUrl: inputTxMsg.typeUrl,
value: MsgCreateValidator.fromJSON(inputTxMsg.value),
};
}
return inputTxMsg;
}, [route.params.transactionMessage]);
useEffect(() => { useEffect(() => {
if (namespace !== COSMOS) { if (namespace !== COSMOS) {
return; return;
@ -144,7 +158,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
return; return;
} }
const gasEstimation = await cosmosStargateClient!.simulate( const gasEstimation = await cosmosStargateClient!.simulate(
transactionMessage.value.participant!, signer,
[transactionMessage], [transactionMessage],
MEMO, MEMO,
); );
@ -168,6 +182,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
requestEventId, requestEventId,
topic, topic,
web3wallet, web3wallet,
signer,
]); ]);
useEffect(() => { useEffect(() => {
@ -243,6 +258,13 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
navigation.navigate('Laconic'); navigation.navigate('Laconic');
}; };
const replacer = (key: string, value: any): any => {
if (value instanceof Uint8Array) {
return Buffer.from(value).toString('hex');
}
return value;
};
return ( return (
<> <>
<ScrollView contentContainerStyle={styles.approveTransaction}> <ScrollView contentContainerStyle={styles.approveTransaction}>
@ -267,7 +289,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
</Text> </Text>
<View style={styles.messageBody}> <View style={styles.messageBody}>
<Text variant="bodyLarge"> <Text variant="bodyLarge">
{JSON.stringify(transactionMessage, null, 2)} {JSON.stringify(transactionMessage, replacer, 2)}
</Text> </Text>
</View> </View>
<> <>

View File

@ -7,8 +7,7 @@ import { useNavigation } from '@react-navigation/native';
import { getSdkError } from '@walletconnect/utils'; import { getSdkError } from '@walletconnect/utils';
import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts'; import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts';
import { MnemonicDialog } from '../components/MnemonicDialog'; import { DialogComponent } from '../components/Dialog';
import ImportWalletDialog from '../components/ImportWalletDialog';
import { NetworkDropdown } from '../components/NetworkDropdown'; import { NetworkDropdown } from '../components/NetworkDropdown';
import Accounts from '../components/Accounts'; import Accounts from '../components/Accounts';
import CreateWallet from '../components/CreateWallet'; import CreateWallet from '../components/CreateWallet';
@ -56,13 +55,12 @@ const HomeScreen = () => {
const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false); const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false); const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [importWalletDialog, setImportWalletDialog] = useState<boolean>(false); const [walletDialog, setWalletDialog] = useState<boolean>(false);
const [mnemonicDialog, setMnemonicDialog] = useState<boolean>(false);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false); const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(false); const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(false);
const [phrase, setPhrase] = useState(''); const [phrase, setPhrase] = useState('');
const hideMnemonicDialog = () => setMnemonicDialog(false); const hideWalletDialog = () => setWalletDialog(false);
const hideResetDialog = () => setResetWalletDialog(false); const hideResetDialog = () => setResetWalletDialog(false);
const fetchAccounts = useCallback(async () => { const fetchAccounts = useCallback(async () => {
@ -85,22 +83,12 @@ const HomeScreen = () => {
const mnemonic = await createWallet(networksData); const mnemonic = await createWallet(networksData);
if (mnemonic) { if (mnemonic) {
fetchAccounts(); fetchAccounts();
setMnemonicDialog(true); setWalletDialog(true);
setPhrase(mnemonic); setPhrase(mnemonic);
setSelectedNetwork(networksData[0]); setSelectedNetwork(networksData[0]);
} }
}; };
const importWalletHandler = async (recoveryPhrase: string) => {
const mnemonic = await createWallet(networksData, recoveryPhrase);
if (mnemonic) {
fetchAccounts();
setPhrase(mnemonic);
setSelectedNetwork(networksData[0]);
setImportWalletDialog(false);
}
};
const confirmResetWallet = useCallback(async () => { const confirmResetWallet = useCallback(async () => {
setIsWalletCreated(false); setIsWalletCreated(false);
setIsWalletCreating(false); setIsWalletCreating(false);
@ -164,28 +152,14 @@ const HomeScreen = () => {
</View> </View>
</> </>
) : ( ) : (
<> <CreateWallet
<CreateWallet isWalletCreating={isWalletCreating}
isWalletCreating={isWalletCreating} createWalletHandler={createWalletHandler}
createWalletHandler={createWalletHandler} />
/>
<View style={styles.createWalletContainer}>
<Button
mode="contained"
onPress={() => setImportWalletDialog(true)}>
Import Wallet
</Button>
</View>
</>
)} )}
<ImportWalletDialog <DialogComponent
visible={importWalletDialog} visible={walletDialog}
hideDialog={() => setImportWalletDialog(false)} hideDialog={hideWalletDialog}
importWalletHandler={importWalletHandler}
/>
<MnemonicDialog
visible={mnemonicDialog}
hideDialog={hideMnemonicDialog}
contentText={phrase} contentText={phrase}
/> />
<ConfirmDialog <ConfirmDialog

View File

@ -117,12 +117,6 @@ const styles = StyleSheet.create({
fontSize: 18, fontSize: 18,
padding: 10, padding: 10,
}, },
textInput: {
height: 40,
borderWidth: 1,
borderRadius: 4,
paddingHorizontal: 10,
},
requestMessage: { requestMessage: {
borderWidth: 1, borderWidth: 1,
borderRadius: 5, borderRadius: 5,

View File

@ -27,25 +27,12 @@ import { COSMOS, EIP155 } from './constants';
const createWallet = async ( const createWallet = async (
networksData: NetworksDataState[], networksData: NetworksDataState[],
recoveryPhrase?: string,
): Promise<string> => { ): Promise<string> => {
const mnemonic = recoveryPhrase const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
? recoveryPhrase
: utils.entropyToMnemonic(utils.randomBytes(16));
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic); await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
const hdNode = HDNode.fromMnemonic(mnemonic); const hdNode = HDNode.fromMnemonic(mnemonic);
await createWalletFromMnemonic(networksData, hdNode, mnemonic);
return mnemonic;
};
const createWalletFromMnemonic = async (
networksData: NetworksDataState[],
hdNode: HDNode,
mnemonic: string,
): Promise<void> => {
for (const network of networksData) { for (const network of networksData) {
const hdPath = `m/44'/${network.coinType}'/0'/0/0`; const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
const node = hdNode.derivePath(hdPath); const node = hdNode.derivePath(hdPath);
@ -86,6 +73,8 @@ const createWalletFromMnemonic = async (
), ),
]); ]);
} }
return mnemonic;
}; };
const addAccount = async ( const addAccount = async (