diff --git a/src/App.tsx b/src/App.tsx index 0a94d35..ec4a44c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,6 +25,7 @@ import { StackParamsList } from './types'; import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; +import ApproveTransaction from './screens/ApproveTransaction'; const Stack = createNativeStackNavigator(); @@ -65,10 +66,9 @@ const App = (): React.JSX.Element => { web3wallet!.engine.signClient.session.get(topic); switch (request.method) { case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - navigation.navigate('SignRequest', { + navigation.navigate('ApproveTransaction', { network: 'eth', - address: request.params[0].from, - message: JSON.stringify(request.params[0], undefined, 2), + transaction: request.params[0], requestEvent, requestSessionData, }); @@ -167,9 +167,7 @@ const App = (): React.JSX.Element => { name="SignRequest" component={SignRequest} options={{ - headerTitle: () => ( - Sign this message? - ), + headerTitle: () => Sign Request, }} /> { title: 'New session', }} /> + + { + return ( + + {label} + + {data} + + + ); +}; + +export default DataBox; diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx new file mode 100644 index 0000000..d017a8e --- /dev/null +++ b/src/screens/ApproveTransaction.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Image, ScrollView, View } from 'react-native'; +import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper'; +import { PopulatedTransaction, providers, BigNumber } from 'ethers'; + +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 { retrieveSingleAccount } from '../utils/accounts'; +import { + approveWalletConnectRequest, + rejectWalletConnectRequest, +} from '../utils/wallet-connect/WalletConnectRequests'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { + TEIP155Chain, + EIP155_CHAINS, +} from '../utils/wallet-connect/EIP155Data'; +import DataBox from '../components/DataBox'; + +type SignRequestProps = NativeStackScreenProps< + StackParamsList, + 'ApproveTransaction' +>; + +const ApproveTransaction = ({ route }: SignRequestProps) => { + 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(); + const [transactionData, setTransactionData] = + useState(); + const [network, setNetwork] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [balance, setBalance] = useState(''); + + const requestParams = route.params!.requestEvent; + const chainId = requestParams?.params.chainId; + const provider = useMemo(() => { + return new providers.JsonRpcProvider( + EIP155_CHAINS[chainId as TEIP155Chain].rpc, + ); + }, [chainId]); + + const navigation = + useNavigation>(); + + const retrieveData = async ( + requestNetwork: string, + requestAddress: string, + ) => { + const requestAccount = await retrieveSingleAccount( + requestNetwork, + requestAddress, + ); + if (!requestAccount) { + navigation.navigate('InvalidPath'); + return; + } + + if (requestAccount !== account) { + setAccount(requestAccount); + } + if (requestNetwork !== network) { + setNetwork(requestNetwork); + } + + if (route.params?.transaction) { + setTransactionData(route.params?.transaction); + } + + setIsLoading(false); + }; + + const gasFees = useMemo(() => { + if (!transactionData) { + return; + } + return BigNumber.from(transactionData?.gasLimit) + .mul(BigNumber.from(transactionData?.gasPrice)) + .toString(); + }, [transactionData]); + + useEffect(() => { + route.params && + retrieveData(route.params?.network, route.params?.transaction.from!); + }, [route]); + + const acceptRequestHandler = async () => { + const { requestEvent } = route.params || {}; + + if (!account) { + throw new Error('account not found'); + } + + if (!requestEvent) { + throw new Error('Request event not found'); + } + + const response = await approveWalletConnectRequest( + requestEvent, + account, + network, + '', + provider, + ); + + const { topic } = requestEvent; + await web3wallet!.respondSessionRequest({ topic, response }); + + navigation.navigate('Laconic'); + }; + + const rejectRequestHandler = async () => { + if (route.params?.requestEvent) { + const response = rejectWalletConnectRequest(route.params?.requestEvent); + const { topic } = route.params?.requestEvent; + await web3wallet!.respondSessionRequest({ + topic, + response, + }); + } + navigation.navigate('Laconic'); + }; + + useEffect(() => { + const getAccountBalance = async (account: Account) => { + const fetchedBalance = await provider.getBalance(account.address); + setBalance(fetchedBalance.toString()); + }; + + if (account) { + getAccountBalance(account); + } + }, [account, provider]); + + useEffect(() => { + navigation.setOptions({ + // eslint-disable-next-line react/no-unstable-nested-components + header: ({ options, back }) => { + const title = getHeaderTitle(options, 'Approve Transaction'); + + return ( + + {back && ( + { + await rejectRequestHandler(); + navigation.navigate('Laconic'); + }} + /> + )} + + + ); + }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [navigation, route.name]); + + return ( + <> + {isLoading ? ( + + + + ) : ( + + + {requestIcon && ( + + )} + {requestName} + {requestURL} + + + From + + + + + + {transactionData && ( + + + + + + + )} + + + + + + )} + + ); +}; + +export default ApproveTransaction; diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index d22c932..35bd254 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -185,7 +185,7 @@ const SignRequest = ({ route }: SignRequestProps) => { navigation.setOptions({ // eslint-disable-next-line react/no-unstable-nested-components header: ({ options, back }) => { - const title = getHeaderTitle(options, route.name); + const title = getHeaderTitle(options, 'Sign Request'); return ( @@ -212,7 +212,7 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( - + {requestIcon && ( { {requestURL} - {isCosmosSignDirect || isEthSendTransaction ? ( @@ -248,7 +247,7 @@ const SignRequest = ({ route }: SignRequestProps) => { No - + )} ); diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 22f96a1..7d4fe66 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -18,7 +18,8 @@ const styles = StyleSheet.create({ fontWeight: '700', }, accountContainer: { - marginTop: 12, + padding: 8, + paddingBottom: 0, }, addAccountButton: { marginTop: 24, @@ -129,6 +130,9 @@ const styles = StyleSheet.create({ justifyContent: 'center', padding: 8, }, + approveTransaction: { + height: '40%', + }, buttonContainer: { marginTop: 50, flexDirection: 'row', @@ -216,6 +220,34 @@ const styles = StyleSheet.create({ display: 'flex', alignItems: 'center', }, + dataBoxContainer: { + marginBottom: 10, + }, + dataBoxLabel: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 3, + color: 'black', + }, + dataBox: { + borderWidth: 1, + borderColor: '#ccc', + padding: 10, + borderRadius: 5, + }, + dataBoxData: { + fontSize: 16, + color: 'black', + }, + transactionText: { + padding: 8, + fontSize: 18, + fontWeight: 'bold', + color: 'black', + }, + balancePadding: { + padding: 8, + }, }); export default styles; diff --git a/src/types.ts b/src/types.ts index 4ac0616..40bf5f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { PopulatedTransaction } from 'ethers'; + import { SignClientTypes, SessionTypes } from '@walletconnect/types'; import { Web3WalletTypes } from '@walletconnect/web3wallet'; @@ -13,6 +15,14 @@ export type StackParamsList = { requestSessionData?: SessionTypes.Struct; } | undefined; + ApproveTransaction: + | { + network: string; + transaction: PopulatedTransaction; + requestEvent?: Web3WalletTypes.SessionRequest; + requestSessionData?: SessionTypes.Struct; + } + | undefined; InvalidPath: undefined; WalletConnect: undefined; AddSession: undefined; diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index 8f023c4..62c818c 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -10,16 +10,16 @@ import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; import { getCosmosAccounts } from '../accounts'; -import { TEIP155Chain, EIP155_CHAINS } from './EIP155Data'; export async function approveWalletConnectRequest( requestEvent: SignClientTypes.EventArguments['session_request'], account: Account, network: string, - message: string, + message?: string, + provider?: providers.JsonRpcProvider, ) { const { params, id } = requestEvent; - const { request, chainId } = params; + const { request } = params; const path = (await getPathKey(network, account.counterId)).path; const mnemonic = await getMnemonic(); @@ -27,7 +27,24 @@ export async function approveWalletConnectRequest( const address = cosmosAccount.data.address; switch (request.method) { + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + if (!provider) { + throw new Error('JSON RPC provider not found'); + } + + const privKey = (await getPathKey('eth', account.counterId)).privKey; + const wallet = new Wallet(privKey); + const sendTransaction = request.params[0]; + const connectedWallet = await wallet.connect(provider); + const hash = await connectedWallet.sendTransaction(sendTransaction); + const receipt = typeof hash === 'string' ? hash : hash?.hash; + return formatJsonRpcResult(id, receipt); + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + if (!message) { + throw new Error('Message to be signed not found'); + } + const ethSignature = await signEthMessage(message, account.counterId); return formatJsonRpcResult(id, ethSignature); @@ -69,18 +86,6 @@ export async function approveWalletConnectRequest( signature: cosmosAminoSignature.signature.signature, }); - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - const provider = new providers.JsonRpcProvider( - EIP155_CHAINS[chainId as TEIP155Chain].rpc, - ); - - const privKey = (await getPathKey('eth', account.counterId)).privKey; - const wallet = new Wallet(privKey); - const sendTransaction = request.params[0]; - const connectedWallet = await wallet.connect(provider); - const hash = await connectedWallet.sendTransaction(sendTransaction); - const receipt = typeof hash === 'string' ? hash : hash?.hash; - return formatJsonRpcResult(id, receipt); default: throw new Error(getSdkError('INVALID_METHOD').message); }