laconic-wallet-web/src/screens/SignRequest.tsx
nabarun 504badebb8 Remove deep link path and prevent re-render from useEffect (#7)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)
- Remove path config used for deep links

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#7
2024-08-01 06:18:02 +00:00

323 lines
9.1 KiB
TypeScript

import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Image, ScrollView, View } from 'react-native';
import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import {
NativeStackNavigationProp,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { getHeaderTitle } from '@react-navigation/elements';
import { Account, StackParamsList } from '../types';
import AccountDetails from '../components/AccountDetails';
import styles from '../styles/stylesheet';
import { signMessage } from '../utils/sign-message';
import { retrieveSingleAccount } from '../utils/accounts';
import {
approveWalletConnectRequest,
rejectWalletConnectRequest,
WalletConnectRequests,
} from '../utils/wallet-connect/wallet-connect-requests';
import { useWalletConnect } from '../context/WalletConnectContext';
import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data';
import { useNetworks } from '../context/NetworksContext';
import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData';
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'SignRequest'>;
const SignRequest = ({ route }: SignRequestProps) => {
const { networksData } = useNetworks();
const {web3wallet} = useWalletConnect();
const requestSession = route.params.requestSessionData;
const requestName = requestSession?.peer?.metadata?.name;
const requestIcon = requestSession?.peer?.metadata?.icons[0];
const requestURL = requestSession?.peer?.metadata?.url;
const [account, setAccount] = useState<Account>();
const [message, setMessage] = useState<string>('');
const [namespace, setNamespace] = useState<string>('');
const [chainId, setChainId] = useState<string>('');
const [isLoading, setIsLoading] = useState(true);
const [isApproving, setIsApproving] = useState(false);
const [isRejecting, setIsRejecting] = useState(false);
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const isCosmosSignDirect = useMemo(() => {
const requestParams = route.params.requestEvent;
if (!requestParams?.id) {
return false;
}
return requestParams.params.request.method === 'cosmos_signDirect';
}, [route.params]);
const isEthSendTransaction = useMemo(() => {
const requestParams = route.params.requestEvent;
if (!requestParams?.id) {
return false;
}
return (
requestParams.params.request.method ===
EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION
);
}, [route.params]);
const retrieveData = useCallback(
async (
requestNamespace: string,
requestChainId: string,
requestAddress: string,
requestMessage: string,
) => {
const requestAccount = await retrieveSingleAccount(
requestNamespace,
requestChainId,
requestAddress,
);
if (!requestAccount) {
navigation.navigate('InvalidPath');
return;
}
setAccount(requestAccount);
setMessage(decodeURIComponent(requestMessage));
setNamespace(requestNamespace);
setChainId(requestChainId);
setIsLoading(false);
},
[navigation],
);
const sanitizePath = useCallback(
(path: string) => {
const regex = /^\/sign\/(eip155|cosmos)\/(.+)\/(.+)\/(.+)$/;
const match = path.match(regex);
if (match) {
const [, pathNamespace, pathChainId, pathAddress, pathMessage] = match;
return {
namespace: pathNamespace,
chainId: pathChainId,
address: pathAddress,
message: pathMessage,
};
} else {
navigation.navigate('InvalidPath');
}
return null;
},
[navigation],
);
useEffect(() => {
if (route.path) {
const sanitizedRoute = sanitizePath(route.path);
sanitizedRoute &&
retrieveData(
sanitizedRoute.namespace,
sanitizedRoute.chainId,
sanitizedRoute.address,
sanitizedRoute.message,
);
return;
}
const requestEvent = route.params.requestEvent;
const requestChainId = requestEvent?.params.chainId;
const requestedChain = networksData.find(
networkData => networkData.chainId === requestChainId?.split(':')[1],
);
retrieveData(
requestedChain!.namespace,
requestedChain!.chainId,
route.params.address,
route.params.message,
);
}, [retrieveData, sanitizePath, route, networksData]);
const handleWalletConnectRequest = async () => {
const { requestEvent } = route.params || {};
if (!account) {
throw new Error('account not found');
}
if (!requestEvent) {
throw new Error('Request event not found');
}
let options: WalletConnectRequests;
switch (requestEvent.params.request.method) {
case COSMOS_METHODS.COSMOS_SIGN_DIRECT:
options = {
type: 'cosmos_signDirect',
message,
};
break;
case COSMOS_METHODS.COSMOS_SIGN_AMINO:
options = {
type: 'cosmos_signAmino',
message,
};
break;
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
options = {
type: 'personal_sign',
message,
};
break;
default:
throw new Error('Invalid Method');
}
const response = await approveWalletConnectRequest(
requestEvent,
account,
namespace,
chainId,
options,
);
const { topic } = requestEvent;
await web3wallet!.respondSessionRequest({ topic, response });
};
const handleIntent = async () => {
if (!account) {
throw new Error('Account is not valid');
}
if (message) {
const signedMessage = await signMessage({
message,
namespace,
chainId,
accountId: account.index,
});
alert(`Signature ${signedMessage}`);
}
};
const signMessageHandler = async () => {
setIsApproving(true);
if (route.params.requestEvent) {
await handleWalletConnectRequest();
} else {
await handleIntent();
}
setIsApproving(false);
navigation.navigate('Home');
};
const rejectRequestHandler = async () => {
setIsRejecting(true);
if (route.params?.requestEvent) {
const response = rejectWalletConnectRequest(route.params?.requestEvent);
const { topic } = route.params?.requestEvent;
await web3wallet!.respondSessionRequest({
topic,
response,
});
}
setIsRejecting(false);
navigation.navigate('Home');
};
useEffect(() => {
navigation.setOptions({
// eslint-disable-next-line react/no-unstable-nested-components
header: ({ options, back }) => {
const title = getHeaderTitle(options, 'Sign Request');
return (
<Appbar.Header>
{back && (
<Appbar.BackAction
onPress={async () => {
await rejectRequestHandler();
navigation.navigate('Home');
}}
/>
)}
<Appbar.Content title={title} />
</Appbar.Header>
);
},
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [navigation, route.name]);
return (
<>
{isLoading ? (
<View style={styles.spinnerContainer}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : (
<>
<ScrollView contentContainerStyle={styles.appContainer}>
<View style={styles.dappDetails}>
{requestIcon && (
<>
{requestIcon.endsWith('.svg') ? (
<View style={styles.dappLogo}>
<Text>SvgURI requstIcon</Text>
</View>
) : (
<Image
style={styles.dappLogo}
source={{ uri: requestIcon }}
/>
)}
</>
)}
<Text>{requestName}</Text>
<Text variant="bodyMedium">{requestURL}</Text>
</View>
<AccountDetails account={account} />
{isCosmosSignDirect || isEthSendTransaction ? (
<View style={styles.requestDirectMessage}>
<ScrollView nestedScrollEnabled>
<Text variant="bodyLarge">{message}</Text>
</ScrollView>
</View>
) : (
<View style={styles.requestMessage}>
<Text variant="bodyLarge">{message}</Text>
</View>
)}
</ScrollView>
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={signMessageHandler}
loading={isApproving}
disabled={isApproving}>
Yes
</Button>
<Button
mode="contained"
onPress={rejectRequestHandler}
loading={isRejecting}
buttonColor="#B82B0D">
No
</Button>
</View>
</>
)}
</>
);
};
export default SignRequest;