style: add network

This commit is contained in:
Cody Bender 2024-08-09 15:34:21 -04:00
parent 17e96d0e52
commit 43305a8d2f
5 changed files with 303 additions and 270 deletions

View File

@ -229,19 +229,7 @@ const App = (): React.JSX.Element => {
component={HomeScreen} component={HomeScreen}
options={{ options={{
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
header: () => ( header: () => <Header title="Wallet" />,
<Text
variant="titleLarge"
style={{
paddingLeft: 24,
paddingVertical: 12,
width: "auto",
backgroundColor: "#18181A",
}}
>
Laconic Wallet
</Text>
),
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -249,7 +237,7 @@ const App = (): React.JSX.Element => {
component={SignMessage} component={SignMessage}
options={{ options={{
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Message</Text>, header: () => <Header title="Wallet" />,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -257,7 +245,7 @@ const App = (): React.JSX.Element => {
component={SignRequest} component={SignRequest}
options={{ options={{
// eslint-disable-next-line react/no-unstable-nested-components // eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Request</Text>, header: () => <Header title="Wallet" />,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -272,7 +260,7 @@ const App = (): React.JSX.Element => {
name="AddSession" name="AddSession"
component={AddSession} component={AddSession}
options={{ options={{
title: "New session", header: () => <Header title="Wallet" />,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -297,14 +285,14 @@ const App = (): React.JSX.Element => {
name="ApproveTransfer" name="ApproveTransfer"
component={ApproveTransfer} component={ApproveTransfer}
options={{ options={{
title: "Approve transfer", header: () => <Header title="Wallet" />,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="AddNetwork" name="AddNetwork"
component={AddNetwork} component={AddNetwork}
options={{ options={{
title: "Add Network", header: () => <Header title="Wallet" />,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -318,7 +306,7 @@ const App = (): React.JSX.Element => {
name="ApproveTransaction" name="ApproveTransaction"
component={ApproveTransaction} component={ApproveTransaction}
options={{ options={{
title: "Approve Transaction", header: () => <Header title="Wallet" />,
}} }}
/> />
</Stack.Navigator> </Stack.Navigator>

View File

@ -1,9 +1,8 @@
import { View } from 'react-native'; import { View } from "react-native";
import React from 'react'; import React from "react";
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from "@mui/lab";
import { CreateWalletProps } from '../types'; import { CreateWalletProps } from "../types";
import styles from '../styles/stylesheet';
const CreateWallet = ({ const CreateWallet = ({
isWalletCreating, isWalletCreating,
@ -16,8 +15,9 @@ const CreateWallet = ({
variant="contained" variant="contained"
loading={isWalletCreating} loading={isWalletCreating}
disabled={isWalletCreating} disabled={isWalletCreating}
onClick={createWalletHandler}> onClick={createWalletHandler}
{isWalletCreating ? 'Creating' : 'CREATE WALLET'} >
{isWalletCreating ? "Creating" : "CREATE WALLET"}
</LoadingButton> </LoadingButton>
</View> </View>
</View> </View>

View File

@ -1,6 +1,5 @@
import { Divider, Stack, SvgIcon, Typography } from "@mui/material"; import { Divider, Stack, SvgIcon, Typography } from "@mui/material";
import React from "react"; import React from "react";
import { Text } from "react-native-paper";
export const Header: React.FC<{ title: string }> = ({ title }) => ( export const Header: React.FC<{ title: string }> = ({ title }) => (
<Stack <Stack

View File

@ -1,21 +1,20 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from "react";
import { View } from 'react-native'; import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form";
import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form'; import { TextInput, HelperText } from "react-native-paper";
import { TextInput, Button, HelperText } from 'react-native-paper';
import { HDNode } from 'ethers/lib/utils'; import { HDNode } from "ethers/lib/utils";
import { chains } from 'chain-registry'; import { chains } from "chain-registry";
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from "use-debounce";
import { z } from 'zod'; import { z } from "zod";
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from "@react-navigation/native";
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from "@hookform/resolvers/zod";
import { StackParamsList } from '../types'; import { StackParamsList } from "../types";
import { SelectNetworkType } from '../components/SelectNetworkType'; import { SelectNetworkType } from "../components/SelectNetworkType";
import { storeNetworkData } from '../utils/accounts'; import { storeNetworkData } from "../utils/accounts";
import { useNetworks } from '../context/NetworksContext'; import { useNetworks } from "../context/NetworksContext";
import { import {
COSMOS, COSMOS,
EIP155, EIP155,
@ -23,14 +22,17 @@ import {
EMPTY_FIELD_ERROR, EMPTY_FIELD_ERROR,
INVALID_URL_ERROR, INVALID_URL_ERROR,
IS_NUMBER_REGEX, IS_NUMBER_REGEX,
} from '../utils/constants'; } from "../utils/constants";
import { getCosmosAccounts } from '../utils/accounts'; import { getCosmosAccounts } from "../utils/accounts";
import ETH_CHAINS from '../assets/ethereum-chains.json'; import ETH_CHAINS from "../assets/ethereum-chains.json";
import { import {
getInternetCredentials, getInternetCredentials,
setInternetCredentials, setInternetCredentials,
} from '../utils/key-store'; } from "../utils/key-store";
import styles from '../styles/stylesheet'; import { Button, Divider, Grid, Typography } from "@mui/material";
import { Container } from "../components/Container";
import { ArrowBack } from "@mui/icons-material";
import { LoadingButton } from "@mui/lab";
const ethNetworkDataSchema = z.object({ const ethNetworkDataSchema = z.object({
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
@ -39,7 +41,7 @@ const ethNetworkDataSchema = z.object({
blockExplorerUrl: z blockExplorerUrl: z
.string() .string()
.url({ message: INVALID_URL_ERROR }) .url({ message: INVALID_URL_ERROR })
.or(z.literal('')), .or(z.literal("")),
coinType: z coinType: z
.string() .string()
.nonempty({ message: EMPTY_FIELD_ERROR }) .nonempty({ message: EMPTY_FIELD_ERROR })
@ -54,7 +56,7 @@ const cosmosNetworkDataSchema = z.object({
blockExplorerUrl: z blockExplorerUrl: z
.string() .string()
.url({ message: INVALID_URL_ERROR }) .url({ message: INVALID_URL_ERROR })
.or(z.literal('')), .or(z.literal("")),
coinType: z coinType: z
.string() .string()
.nonempty({ message: EMPTY_FIELD_ERROR }) .nonempty({ message: EMPTY_FIELD_ERROR })
@ -85,13 +87,13 @@ const AddNetwork = () => {
setValue, setValue,
reset, reset,
} = useForm<z.infer<typeof networksFormDataSchema>>({ } = useForm<z.infer<typeof networksFormDataSchema>>({
mode: 'onChange', mode: "onChange",
resolver: zodResolver(networksFormDataSchema), resolver: zodResolver(networksFormDataSchema),
}); });
const watchChainId = useWatch({ const watchChainId = useWatch({
control, control,
name: 'chainId', name: "chainId",
}); });
const updateNetworkType = (newNetworkType: string) => { const updateNetworkType = (newNetworkType: string) => {
@ -101,16 +103,16 @@ const AddNetwork = () => {
const fetchChainDetails = useDebouncedCallback((chainId: string) => { const fetchChainDetails = useDebouncedCallback((chainId: string) => {
if (namespace === EIP155) { if (namespace === EIP155) {
const ethChainDetails = ETH_CHAINS.find( const ethChainDetails = ETH_CHAINS.find(
chain => chain.chainId === Number(chainId), (chain) => chain.chainId === Number(chainId),
); );
if (!ethChainDetails) { if (!ethChainDetails) {
return; return;
} }
setValue('networkName', ethChainDetails.name); setValue("networkName", ethChainDetails.name);
setValue('rpcUrl', ethChainDetails.rpc[0]); setValue("rpcUrl", ethChainDetails.rpc[0]);
setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || ''); setValue("blockExplorerUrl", ethChainDetails.explorers?.[0].url || "");
setValue('coinType', String(ethChainDetails.slip44 ?? '60')); setValue("coinType", String(ethChainDetails.slip44 ?? "60"));
setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol); setValue("currencySymbol", ethChainDetails.nativeCurrency.symbol);
return; return;
} }
const cosmosChainDetails = chains.find( const cosmosChainDetails = chains.find(
@ -119,14 +121,14 @@ const AddNetwork = () => {
if (!cosmosChainDetails) { if (!cosmosChainDetails) {
return; return;
} }
setValue('networkName', cosmosChainDetails.pretty_name); setValue("networkName", cosmosChainDetails.pretty_name);
setValue('rpcUrl', cosmosChainDetails.apis?.rpc?.[0]?.address || ''); setValue("rpcUrl", cosmosChainDetails.apis?.rpc?.[0]?.address || "");
setValue('blockExplorerUrl', cosmosChainDetails.explorers?.[0].url || ''); setValue("blockExplorerUrl", cosmosChainDetails.explorers?.[0].url || "");
setValue('addressPrefix', cosmosChainDetails.bech32_prefix); setValue("addressPrefix", cosmosChainDetails.bech32_prefix);
setValue('coinType', String(cosmosChainDetails.slip44 ?? '118')); setValue("coinType", String(cosmosChainDetails.slip44 ?? "118"));
setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || ''); setValue("nativeDenom", cosmosChainDetails.fees?.fee_tokens[0].denom || "");
setValue( setValue(
'gasPrice', "gasPrice",
String( String(
cosmosChainDetails.fees?.fee_tokens[0].average_gas_price || cosmosChainDetails.fees?.fee_tokens[0].average_gas_price ||
String(process.env.DEFAULT_GAS_PRICE), String(process.env.DEFAULT_GAS_PRICE),
@ -142,11 +144,11 @@ const AddNetwork = () => {
isDefault: false, isDefault: false,
}; };
const mnemonicServer = await getInternetCredentials('mnemonicServer'); const mnemonicServer = await getInternetCredentials("mnemonicServer");
const mnemonic = mnemonicServer; const mnemonic = mnemonicServer;
if (!mnemonic) { if (!mnemonic) {
throw new Error('Mnemonic not found'); throw new Error("Mnemonic not found");
} }
const hdNode = HDNode.fromMnemonic(mnemonic); const hdNode = HDNode.fromMnemonic(mnemonic);
@ -172,7 +174,7 @@ const AddNetwork = () => {
break; break;
default: default:
throw new Error('Unsupported namespace'); throw new Error("Unsupported namespace");
} }
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
@ -183,22 +185,22 @@ const AddNetwork = () => {
await Promise.all([ await Promise.all([
setInternetCredentials( setInternetCredentials(
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
'_', "_",
accountInfo, accountInfo,
), ),
setInternetCredentials( setInternetCredentials(
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
'_', "_",
'1', "1",
), ),
setInternetCredentials( setInternetCredentials(
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
'_', "_",
'0', "0",
), ),
]); ]);
navigation.navigate('Home'); navigation.navigate("Home");
}, },
[navigation, namespace, setNetworksData], [navigation, namespace, setNetworksData],
); );
@ -212,208 +214,254 @@ const AddNetwork = () => {
}, [namespace, reset]); }, [namespace, reset]);
return ( return (
<View style={styles.appContainer}> <Container boxProps={{ sx: { backgroundColor: "inherit" } }}>
<SelectNetworkType updateNetworkType={updateNetworkType} /> <Button
startIcon={<ArrowBack />}
color="info"
sx={{ mb: 4 }}
onClick={() => navigation.navigate("Home")}
>
Home
</Button>
<Typography variant="h4" sx={{ mb: 4 }}>
Add Network
</Typography>
<Container>
<SelectNetworkType updateNetworkType={updateNetworkType} />
<Divider flexItem sx={{ my: 4 }} />
<Controller <Grid container spacing={2} sx={{ px: 1 }}>
control={control} <Grid item xs={6}>
name="chainId" <Controller
defaultValue="" control={control}
render={({ field: { onChange, onBlur, value } }) => ( name="chainId"
<> defaultValue=""
<TextInput render={({ field: { onChange, onBlur, value } }) => (
mode="outlined" <>
value={value} <TextInput
label="Chain ID" mode="outlined"
onBlur={onBlur} value={value}
onChangeText={textValue => onChange(textValue)} label="Chain ID"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.chainId?.message}
</HelperText>
</>
)}
/> />
<HelperText type="error">{errors.chainId?.message}</HelperText> </Grid>
</>
)}
/>
<Controller
control={control}
defaultValue=""
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="rpcUrl"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Controller <Grid item xs={6}>
control={control} <Controller
defaultValue="" control={control}
name="blockExplorerUrl" defaultValue=""
render={({ field: { onChange, onBlur, value } }) => ( name="networkName"
<> render={({ field: { onChange, onBlur, value } }) => (
<TextInput <>
mode="outlined" <TextInput
value={value} mode="outlined"
label="Block Explorer URL (Optional)" label="Network Name"
onBlur={onBlur} value={value}
onChangeText={textValue => onChange(textValue)} onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.networkName?.message}
</HelperText>
</>
)}
/> />
<HelperText type="error"> </Grid>
{errors.blockExplorerUrl?.message}
</HelperText> <Grid item xs={6}>
</> <Controller
)} control={control}
/> name="rpcUrl"
<Controller defaultValue=""
control={control} render={({ field: { onChange, onBlur, value } }) => (
name="coinType" <>
defaultValue="" <TextInput
render={({ field: { onChange, onBlur, value } }) => ( mode="outlined"
<> label="New RPC URL"
<TextInput onBlur={onBlur}
mode="outlined" value={value}
value={value} onChangeText={(textValue) => onChange(textValue)}
label="Coin Type" />
onBlur={onBlur} <HelperText type="error">{errors.rpcUrl?.message}</HelperText>
onChangeText={onChange} </>
)}
/> />
<HelperText type="error">{errors.coinType?.message}</HelperText> </Grid>
</>
)} <Grid item xs={6}>
/> <Controller
{namespace === EIP155 ? ( control={control}
<Controller defaultValue=""
control={control} name="blockExplorerUrl"
name="currencySymbol" render={({ field: { onChange, onBlur, value } }) => (
defaultValue="" <>
render={({ field: { onChange, onBlur, value } }) => ( <TextInput
<> mode="outlined"
<TextInput value={value}
mode="outlined" label="Block Explorer URL (Optional)"
value={value} onBlur={onBlur}
label="Currency Symbol" onChangeText={(textValue) => onChange(textValue)}
onBlur={onBlur} />
onChangeText={textValue => onChange(textValue)} <HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={namespace === EIP155 ? 12 : 6}>
<Controller
control={control}
name="coinType"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Coin Type"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{errors.coinType?.message}
</HelperText>
</>
)}
/>
</Grid>
{namespace === EIP155 ? (
<Grid item xs={12}>
<Controller
control={control}
name="currencySymbol"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof ethNetworkDataSchema>
>
).currencySymbol?.message
}
</HelperText>
</>
)}
/> />
<HelperText type="error"> </Grid>
{ ) : (
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>) <>
.currencySymbol?.message <Grid item xs={6}>
} <Controller
</HelperText> control={control}
name="nativeDenom"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Native Denom"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).nativeDenom?.message
}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="addressPrefix"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Address Prefix"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).addressPrefix?.message
}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
</Grid>
</> </>
)} )}
/> </Grid>
) : ( <LoadingButton
<> variant="contained"
<Controller loading={isSubmitting}
control={control} disabled={isSubmitting}
name="nativeDenom" onClick={handleSubmit(submit)}
defaultValue="" sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
render={({ field: { onChange, onBlur, value } }) => ( >
<> {isSubmitting ? "Adding" : "Submit"}
<TextInput </LoadingButton>
mode="outlined" </Container>
value={value} </Container>
label="Native Denom"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).nativeDenom?.message
}
</HelperText>
</>
)}
/>
<Controller
control={control}
name="addressPrefix"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Address Prefix"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).addressPrefix?.message
}
</HelperText>
</>
)}
/>
<Controller
control={control}
name="gasPrice"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
</>
)}
<Button
mode="contained"
loading={isSubmitting}
disabled={isSubmitting}
style={styles.networksButton}
onPress={handleSubmit(submit)}>
{isSubmitting ? 'Adding' : 'Submit'}
</Button>
</View>
); );
}; };

View File

@ -12,7 +12,6 @@ import { useNavigation } from "@react-navigation/native";
import { setInternetCredentials } from "../utils/key-store"; import { setInternetCredentials } from "../utils/key-store";
import { StackParamsList } from "../types"; import { StackParamsList } from "../types";
import styles from "../styles/stylesheet";
import { retrieveNetworksData } from "../utils/accounts"; import { retrieveNetworksData } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext"; import { useNetworks } from "../context/NetworksContext";
import { import {
@ -214,9 +213,8 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
variant="contained" variant="contained"
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
style={styles.networksButton}
onClick={handleSubmit(submit)} onClick={handleSubmit(submit)}
sx={{ minWidth: "200px", px: 4, py: 1 }} sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
> >
{isSubmitting ? "Adding" : "Submit"} {isSubmitting ? "Adding" : "Submit"}
</LoadingButton> </LoadingButton>