laconic-wallet/src/components/PairingModal.tsx
neerajvijay1997 290ea0097c
UI Improvements (#65)
* Rebase ui branch

* Fix scrollview (#69)

* Add loading prop to button

* Change function reference

* Remove fragment

---------

Co-authored-by: Adw8 <adwait@deepstacksoft.com>
Co-authored-by: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com>
2024-03-28 16:23:12 +05:30

270 lines
8.3 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import { Image, View, Modal, ScrollView } from 'react-native';
import { Button, Text } from 'react-native-paper';
import mergeWith from 'lodash/mergeWith';
import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils';
import { AccountsState, PairingModalProps } from '../types';
import styles from '../styles/stylesheet';
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
import { useAccounts } from '../context/AccountsContext';
import { useWalletConnect } from '../context/WalletConnectContext';
import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data';
import { COSMOS_CHAINS } from '../utils/wallet-connect/COSMOSData';
const PairingModal = ({
visible,
currentProposal,
setCurrentProposal,
setModalVisible,
setToastVisible,
}: PairingModalProps) => {
const { accounts, currentIndex } = useAccounts();
const [isLoading, setIsLoading] = useState(false);
const dappName = currentProposal?.params?.proposer?.metadata.name;
const url = currentProposal?.params?.proposer?.metadata.url;
const icon = currentProposal?.params.proposer?.metadata.icons[0];
const [walletConnectData, setWalletConnectData] = useState<{
walletConnectMethods: string[];
walletConnectEvents: string[];
walletConnectChains: string[];
}>({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
useEffect(() => {
if (!currentProposal) {
return;
}
const { params } = currentProposal;
const { requiredNamespaces, optionalNamespaces } = params;
setWalletConnectData({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
const combinedNamespaces = mergeWith(
requiredNamespaces,
optionalNamespaces,
(obj, src) =>
Array.isArray(obj) && Array.isArray(src) ? [...src, ...obj] : undefined,
);
Object.keys(combinedNamespaces).forEach(key => {
const { methods, events, chains } = combinedNamespaces[key];
setWalletConnectData(prevData => {
return {
walletConnectMethods: [...prevData.walletConnectMethods, ...methods],
walletConnectEvents: [...prevData.walletConnectEvents, ...events],
walletConnectChains: chains
? [...prevData.walletConnectChains, ...chains]
: [...prevData.walletConnectChains],
};
});
});
}, [currentProposal]);
const { setActiveSessions } = useWalletConnect();
const supportedNamespaces = useMemo(() => {
if (!currentProposal) {
return;
}
// eip155
const eip155Chains = Object.keys(EIP155_CHAINS);
// cosmos
const cosmosChains = Object.keys(COSMOS_CHAINS);
// Set selected account as the first account in supported namespaces
const sortedAccounts = Object.entries(accounts).reduce(
(acc: AccountsState, [key, value]) => {
let newValue = [...value];
// TODO: Implement selectedAccount instead of currentIndex in AccountsContext
if (value.length > currentIndex) {
const currentAccount = newValue[currentIndex];
const remainingAccounts = newValue.filter(
(_, index) => index !== currentIndex,
);
newValue = [currentAccount, ...remainingAccounts];
}
acc[key as 'ethAccounts' | 'cosmosAccounts'] = newValue;
return acc;
},
{ ethAccounts: [], cosmosAccounts: [] },
);
const { optionalNamespaces, requiredNamespaces } = currentProposal.params;
return {
eip155: {
chains: eip155Chains,
// TODO: Debug optional namespace methods and events being required for approval
methods: [
...(optionalNamespaces.eip155?.methods ?? []),
...(requiredNamespaces.eip155?.methods ?? []),
],
events: [
...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []),
],
accounts: eip155Chains
.map(chain =>
sortedAccounts.ethAccounts.map(
account => `${chain}:${account.address}`,
),
)
.flat(),
},
cosmos: {
chains: cosmosChains,
methods: [
...(optionalNamespaces.cosmos?.methods ?? []),
...(requiredNamespaces.cosmos?.methods ?? []),
],
events: [
...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []),
],
accounts: cosmosChains
.map(chain =>
sortedAccounts.cosmosAccounts.map(
account => `${chain}:${account.address}`,
),
)
.flat(),
},
};
}, [currentIndex, accounts, currentProposal]);
const namespaces = useMemo(() => {
return (
currentProposal &&
supportedNamespaces &&
buildApprovedNamespaces({
proposal: currentProposal.params,
supportedNamespaces,
})
);
}, [currentProposal, supportedNamespaces]);
const handleAccept = async () => {
try {
if (currentProposal && namespaces) {
setIsLoading(true);
const { id } = currentProposal;
await web3wallet!.approveSession({
id,
namespaces,
});
const sessions = web3wallet!.getActiveSessions();
setIsLoading(false);
setActiveSessions(sessions);
setModalVisible(false);
setToastVisible(true);
setCurrentProposal(undefined);
setWalletConnectData({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
}
} catch (error) {
console.error('Error in approve session:', error);
throw error;
}
};
const handleReject = async () => {
if (currentProposal) {
setIsLoading(true);
const { id } = currentProposal;
await web3wallet!.rejectSession({
id,
reason: getSdkError('USER_REJECTED_METHODS'),
});
setIsLoading(false);
setModalVisible(false);
setCurrentProposal(undefined);
setWalletConnectData({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
}
};
return (
<Modal visible={visible} animationType="slide" transparent>
<View style={{ flex: 1 }}>
<View style={styles.modalContentContainer}>
<ScrollView showsVerticalScrollIndicator={true}>
<View style={styles.container}>
{icon && (
<Image
style={styles.dappLogo}
source={icon ? { uri: icon } : undefined}
/>
)}
<Text variant="titleMedium">{dappName}</Text>
<Text variant="bodyMedium">{url}</Text>
<View style={styles.marginVertical8} />
<Text variant="titleMedium">Connect to this site?</Text>
<Text variant="titleMedium">Chains:</Text>
{walletConnectData.walletConnectChains.map(chain => (
<Text style={styles.centerText} key={chain}>
{chain}
</Text>
))}
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Methods Requested:</Text>
{walletConnectData.walletConnectMethods.map(method => (
<Text style={styles.centerText} key={method}>
{method}
</Text>
))}
</View>
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Events Requested:</Text>
{walletConnectData.walletConnectEvents.map(event => (
<Text style={styles.centerText} key={event}>
{event}
</Text>
))}
</View>
</View>
</ScrollView>
<View style={styles.flexRow}>
<Button mode="contained" onPress={handleAccept} loading={isLoading}>
{isLoading ? 'Connecting' : 'Yes'}
</Button>
<View style={styles.space} />
<Button mode="outlined" onPress={handleReject}>
No
</Button>
</View>
</View>
</View>
</Modal>
);
};
export default PairingModal;