From f82ff49d77e27a4c306ac5974933dc154f29edfb Mon Sep 17 00:00:00 2001 From: Isha Date: Thu, 7 Nov 2024 19:58:28 +0530 Subject: [PATCH] Implement functionality to approve tx --- src/screens/WalletEmbed.tsx | 243 +++++++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 2 deletions(-) diff --git a/src/screens/WalletEmbed.tsx b/src/screens/WalletEmbed.tsx index 652eb9c..ee9f150 100644 --- a/src/screens/WalletEmbed.tsx +++ b/src/screens/WalletEmbed.tsx @@ -1,9 +1,38 @@ -import { useCallback, useEffect } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; + +import { ScrollView, View } from 'react-native'; +import { + Button, + Text, + TextInput, +} from 'react-native-paper'; + +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { + calculateFee, + GasPrice, + SigningStargateClient, +} from '@cosmjs/stargate'; import { createWallet, retrieveAccounts } from '../utils/accounts'; +import AccountDetails from '../components/AccountDetails'; +import styles from '../styles/stylesheet'; +import { retrieveSingleAccount } from '../utils/accounts'; +import DataBox from '../components/DataBox'; +import { getPathKey } from '../utils/misc'; import { useNetworks } from '../context/NetworksContext'; +import TxErrorDialog from '../components/TxErrorDialog'; +import { BigNumber } from 'ethers'; +import { MEMO } from '../screens/ApproveTransfer'; export const WalletEmbed = () => { + const [isTxRequested, setIsTxRequested] = useState(false); + const [transactionDetails, setTransactionDetails] = useState(null); + const [fees, setFees] = useState(''); + const [gasLimit, setGasLimit] = useState(''); + const [isTxLoading, setIsTxLoading] = useState(false); + const [txError, setTxError] = useState(null); + const { networksData } = useNetworks(); const getAccountsData = useCallback(async (chainId: string) => { @@ -85,5 +114,215 @@ export const WalletEmbed = () => { }; }, [networksData, getAccountsData]); - return null; + const handleTxRequested = async (event: MessageEvent) => { + if (event.data.type !== 'REQUEST_TX') return; + + const { chainId, fromAddress, toAddress, amount } = event.data; + const network = networksData.find( + net => net.chainId === chainId + ); + + if (!network) { + console.error('Network not found'); + return; + } + + const account = await retrieveSingleAccount(network.namespace, network.chainId, fromAddress); + if (!account) { + throw new Error('Account not found'); + } + + const cosmosPrivKey = ( + await getPathKey(`${network.namespace}:${chainId}`, account.index) + ).privKey; + const sender = await DirectSecp256k1Wallet.fromKey( + Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'), + network.addressPrefix + ); + + const client = await SigningStargateClient.connectWithSigner( + network.rpcUrl!, + sender + ); + + const balance = await client?.getBalance( + account.address, + network!.nativeDenom!.toLowerCase(), + ); + + const gasPrice = GasPrice.fromString( + network.gasPrice! + network.nativeDenom, + ); + + const sendMsg = { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + fromAddress: fromAddress, + toAddress: toAddress, + amount: [ + { + amount: String(amount), + denom: network.nativeDenom!, + }, + ], + }, + }; + + const gasEstimation = await client.simulate( + fromAddress, + [sendMsg], + MEMO, + ); + + const gasLimit = + String( + Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT)), + ) + + const cosmosFees = calculateFee(Number(gasLimit), gasPrice); + setGasLimit(gasLimit); + + setFees(cosmosFees.amount[0].amount); + + setTransactionDetails({ + chainId, + fromAddress, + toAddress, + amount, + account, + balance: balance.amount, + requestedNetwork: network, + }); + + setIsTxRequested(true); + }; + + useEffect(() => { + window.addEventListener('message', handleTxRequested); + return () => window.removeEventListener('message', handleTxRequested); + }, [networksData]); + + const acceptRequestHandler = async () => { + try { + setIsTxLoading(true); + const { chainId, fromAddress, toAddress, amount, requestedNetwork } = + transactionDetails; + + const balanceBigNum = BigNumber.from(transactionDetails.balance); + const amountBigNum = BigNumber.from(String(amount)); + if (amountBigNum.gte(balanceBigNum)) { + throw new Error('Insufficient funds'); + } + + const cosmosPrivKey = ( + await getPathKey(`${requestedNetwork.namespace}:${chainId}`, transactionDetails.account.index) + ).privKey; + + const sender = await DirectSecp256k1Wallet.fromKey( + Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'), + requestedNetwork.addressPrefix + ); + + const client = await SigningStargateClient.connectWithSigner( + requestedNetwork.rpcUrl!, + sender + ); + + const fee = calculateFee( + Number(gasLimit), + GasPrice.fromString(`${requestedNetwork.gasPrice}${requestedNetwork.nativeDenom}`) + ); + + const txResult = await client.sendTokens( + fromAddress, + toAddress, + [{ amount: String(amount), denom: requestedNetwork.nativeDenom }], + fee + ); + + sendMessage(window, 'TRANSACTION_SUCCESS', txResult.transactionHash, '*'); + } catch (error) { + console.error('Transaction error:', error); + setTxError('Transaction failed'); + } finally { + setIsTxLoading(false); + } + }; + + const rejectRequestHandler = () => { + setIsTxRequested(false); + setTransactionDetails(null); + sendMessage(window, 'TRANSACTION_REJECTED', null, '*'); + }; + + return ( + <> + {isTxRequested && transactionDetails ? ( + <> + + + From + + + + + + + + + + + /^\d+$/.test(value) ? setGasLimit(value) : null + } + /> + + + + + + + + ) : null} + + setTxError(null)} + /> + + ); };