Make gas values editable on approve transaction (#113)

* Make eth gas limit and gas price editable on approve transaction

* Make max fee and max priority fee editable for EIP 1559 supported chains

* Refactor approve transaction code

* Use gas limit from dapp if sent

* Refactor regex to constants file
This commit is contained in:
shreerang6921 2024-04-25 12:42:01 +05:30 committed by Nabarun Gogoi
parent 820ac62615
commit e98b1e1f8e
5 changed files with 170 additions and 56 deletions

View File

@ -26,6 +26,7 @@ import {
CHAINID_DEBOUNCE_DELAY,
EMPTY_FIELD_ERROR,
INVALID_URL_ERROR,
IS_NUMBER_REGEX,
} from '../utils/constants';
import { getCosmosAccounts } from '../utils/accounts';
import ETH_CHAINS from '../assets/ethereum-chains.json';
@ -38,7 +39,10 @@ const ethNetworkDataSchema = z.object({
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
coinType: z.string().nonempty({ message: EMPTY_FIELD_ERROR }).regex(/^\d+$/),
coinType: z
.string()
.nonempty({ message: EMPTY_FIELD_ERROR })
.regex(IS_NUMBER_REGEX),
currencySymbol: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
});
@ -50,7 +54,10 @@ const cosmosNetworkDataSchema = z.object({
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
coinType: z.string().nonempty({ message: EMPTY_FIELD_ERROR }).regex(/^\d+$/),
coinType: z
.string()
.nonempty({ message: EMPTY_FIELD_ERROR })
.regex(IS_NUMBER_REGEX),
nativeDenom: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
addressPrefix: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
gasPrice: z

View File

@ -7,8 +7,9 @@ import {
Appbar,
TextInput,
} from 'react-native-paper';
import { providers, BigNumber, ethers } from 'ethers';
import { providers, BigNumber } from 'ethers';
import Config from 'react-native-config';
import { Deferrable } from 'ethers/lib/utils';
import { useNavigation } from '@react-navigation/native';
import {
@ -36,10 +37,12 @@ import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
import DataBox from '../components/DataBox';
import { getPathKey } from '../utils/misc';
import { useNetworks } from '../context/NetworksContext';
import { COSMOS, EIP155 } from '../utils/constants';
import { COSMOS, EIP155, IS_NUMBER_REGEX } from '../utils/constants';
import TxErrorDialog from '../components/TxErrorDialog';
const MEMO = 'Sending signed tx from Laconic Wallet';
// Reference: https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit
const ETH_MINIMUM_GAS = 21000;
type SignRequestProps = NativeStackScreenProps<
StackParamsList,
@ -67,8 +70,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
const [txError, setTxError] = useState<string>();
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
const [ethGasPrice, setEthGasPrice] = useState<string>();
const [ethGasLimit, setEthGasLimit] = useState<string>();
const [ethGasPrice, setEthGasPrice] = useState<BigNumber | null>();
const [ethGasLimit, setEthGasLimit] = useState<BigNumber>();
const [ethMaxFee, setEthMaxFee] = useState<BigNumber | null>();
const [ethMaxPriorityFee, setEthMaxPriorityFee] =
useState<BigNumber | null>();
const isSufficientFunds = useMemo(() => {
if (!transaction.value) {
@ -183,15 +189,27 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
}
setAccount(requestAccount);
setIsLoading(false);
},
[navigation, requestedNetwork],
);
useEffect(() => {
// Set loading to false when gas values for requested chain are fetched
// If requested chain is EVM compatible, the cosmos gas values will be undefined and vice-versa, hence the condition checks only one of them at the same time
if (
// If requested chain is EVM compatible, set loading to false when ethMaxFee and ethPriorityFee have been populated
(ethMaxFee !== undefined && ethMaxPriorityFee !== undefined) ||
// Or if requested chain is a cosmos chain, set loading to false when cosmosGasLimit has been populated
cosmosGasLimit !== undefined
) {
setIsLoading(false);
}
}, [ethMaxFee, ethMaxPriorityFee, cosmosGasLimit]);
useEffect(() => {
if (namespace === EIP155) {
const ethFees = BigNumber.from(transaction.gasLimit ?? ethGasLimit ?? 0)
.mul(BigNumber.from(transaction.gasPrice ?? ethGasPrice ?? 0))
const ethFees = BigNumber.from(ethGasLimit ?? 0)
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice ?? 0))
.toString();
setFees(ethFees);
} else {
@ -214,18 +232,39 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
ethGasPrice,
cosmosGasLimit,
requestedNetwork,
ethMaxFee,
]);
useEffect(() => {
retrieveData(transaction.from!);
}, [retrieveData, transaction]);
const isEIP1559 = useMemo(() => {
if (cosmosGasLimit) {
return;
}
if (ethMaxFee !== null && ethMaxPriorityFee !== null) {
return true;
}
return false;
}, [cosmosGasLimit, ethMaxFee, ethMaxPriorityFee]);
const acceptRequestHandler = async () => {
setIsTxLoading(true);
if (!account) {
throw new Error('account not found');
}
try {
if (!account) {
throw new Error('account not found');
}
if (ethGasLimit && ethGasLimit.lt(ETH_MINIMUM_GAS)) {
throw new Error(`Atleast ${ETH_MINIMUM_GAS} gas limit is required`);
}
if (ethMaxFee && ethMaxPriorityFee && ethMaxFee.lte(ethMaxPriorityFee)) {
throw new Error(
`Max fee per gas (${ethMaxFee.toNumber()}) cannot be lower than or equal to max priority fee per gas (${ethMaxPriorityFee.toNumber()})`,
);
}
const response = await approveWalletConnectRequest({
requestEvent,
account,
@ -244,12 +283,10 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
gas: cosmosGasLimit!,
},
ethGasLimit:
namespace === EIP155
? BigNumber.from(transaction.gasLimit ?? ethGasLimit)
: undefined,
ethGasPrice: transaction.gasPrice
? String(transaction.gasPrice)
: ethGasPrice,
namespace === EIP155 ? BigNumber.from(ethGasLimit) : undefined,
ethGasPrice: ethGasPrice?.toHexString(),
maxPriorityFeePerGas: ethMaxPriorityFee ?? undefined,
maxFeePerGas: ethMaxFee ?? undefined,
sendMsg,
memo: MEMO,
});
@ -331,34 +368,33 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
useEffect(() => {
const getEthGas = async () => {
try {
if (transaction.gasLimit && transaction.gasPrice) {
if (!isSufficientFunds || !provider) {
return;
}
if (!isSufficientFunds) {
return;
const data = await provider.getFeeData();
setEthMaxFee(data.maxFeePerGas);
setEthMaxPriorityFee(data.maxPriorityFeePerGas);
setEthGasPrice(data.gasPrice);
if (transaction.gasLimit) {
setEthGasLimit(BigNumber.from(transaction.gasLimit));
} else {
const transactionObject: Deferrable<providers.TransactionRequest> = {
from: transaction.from!,
to: transaction.to!,
data: transaction.data!,
value: transaction.value!,
maxFeePerGas: data.maxFeePerGas ?? undefined,
maxPriorityFeePerGas: data.maxPriorityFeePerGas ?? undefined,
gasPrice: data.maxFeePerGas
? undefined
: data.gasPrice ?? undefined,
};
const gasLimit = await provider.estimateGas(transactionObject);
setEthGasLimit(gasLimit);
}
if (!provider) {
return;
}
const gasPriceVal = await provider.getGasPrice();
const gasPrice = ethers.utils.hexValue(gasPriceVal);
setEthGasPrice(String(gasPrice));
const transactionObject = {
from: transaction.from!,
to: transaction.to!,
data: transaction.data!,
gasPrice,
value: transaction.value!,
};
const gasLimit = await provider.estimateGas(transactionObject);
setEthGasLimit(String(gasLimit));
} catch (error: any) {
setTxError(error.message);
setIsTxErrorDialogOpen(true);
@ -452,19 +488,72 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
{namespace === EIP155 ? (
<>
{isEIP1559 === false ? (
<>
<Text style={styles.dataBoxLabel}>
{'Gas Price (wei)'}
</Text>
<TextInput
mode="outlined"
value={ethGasPrice?.toNumber().toString()}
onChangeText={value =>
setEthGasPrice(BigNumber.from(value))
}
style={styles.transactionFeesInput}
/>
</>
) : (
<>
<Text style={styles.dataBoxLabel}>
Max Fee Per Gas (wei)
</Text>
<TextInput
mode="outlined"
value={ethMaxFee?.toNumber().toString()}
onChangeText={value => {
if (IS_NUMBER_REGEX.test(value)) {
setEthMaxFee(BigNumber.from(value));
}
}}
style={styles.transactionFeesInput}
/>
<Text style={styles.dataBoxLabel}>
Max Priority Fee Per Gas (wei)
</Text>
<TextInput
mode="outlined"
value={ethMaxPriorityFee?.toNumber().toString()}
onChangeText={value => {
if (IS_NUMBER_REGEX.test(value)) {
setEthMaxPriorityFee(BigNumber.from(value));
}
}}
style={styles.transactionFeesInput}
/>
</>
)}
<Text style={styles.dataBoxLabel}>Gas Limit</Text>
<TextInput
mode="outlined"
value={ethGasLimit?.toNumber().toString()}
onChangeText={value => {
if (IS_NUMBER_REGEX.test(value)) {
setEthGasLimit(BigNumber.from(value));
}
}}
style={styles.transactionFeesInput}
/>
<DataBox
label={`Gas Fees (${
namespace === EIP155
? 'wei'
: requestedNetwork!.nativeDenom
})`}
label={`${
isEIP1559 === true ? 'Max Fee' : 'Gas Fee'
} (wei)`}
data={fees!}
/>
<DataBox label="Data" data={transaction.data!} />
</>
) : (
<>
<Text style={styles.dataBoxLabel}>{`Fees (${
<Text style={styles.dataBoxLabel}>{`Fee (${
requestedNetwork!.nativeDenom
})`}</Text>
<TextInput
@ -477,7 +566,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
<TextInput
mode="outlined"
value={cosmosGasLimit}
onChangeText={value => setCosmosGasLimit(value)}
onChangeText={value => {
if (IS_NUMBER_REGEX.test(value)) {
setCosmosGasLimit(value);
}
}}
/>
</>
)}

View File

@ -160,7 +160,6 @@ const SignRequest = ({ route }: SignRequestProps) => {
}
const response = await approveWalletConnectRequest({
networksData,
requestEvent,
account,
namespace,

View File

@ -32,3 +32,5 @@ export const CHAINID_DEBOUNCE_DELAY = 250;
export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
export const INVALID_URL_ERROR = 'Invalid URL';
export const IS_NUMBER_REGEX = /^\d+$/;

View File

@ -28,6 +28,8 @@ export async function approveWalletConnectRequest({
ethGasPrice,
sendMsg,
memo,
maxPriorityFeePerGas,
maxFeePerGas,
}: {
requestEvent: SignClientTypes.EventArguments['session_request'];
account: Account;
@ -38,6 +40,8 @@ export async function approveWalletConnectRequest({
cosmosFee?: StdFee;
ethGasLimit?: BigNumber;
ethGasPrice?: string;
maxPriorityFeePerGas?: BigNumber;
maxFeePerGas?: BigNumber;
sendMsg?: MsgSendEncodeObject;
memo?: string;
}) {
@ -61,11 +65,20 @@ export async function approveWalletConnectRequest({
).privKey;
const wallet = new Wallet(privKey);
const sendTransaction = request.params[0];
const updatedTransaction = {
...sendTransaction,
gasLimit: ethGasLimit,
gasPrice: ethGasPrice,
};
const updatedTransaction =
maxFeePerGas && maxPriorityFeePerGas
? {
...sendTransaction,
gasLimit: ethGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
}
: {
...sendTransaction,
gasLimit: ethGasLimit,
gasPrice: ethGasPrice,
type: 0,
};
if (!(provider instanceof providers.JsonRpcProvider)) {
throw new Error('Provider not found');