forked from cerc-io/laconic-wallet
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:
parent
820ac62615
commit
e98b1e1f8e
@ -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
|
||||
|
@ -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);
|
||||
try {
|
||||
if (!account) {
|
||||
throw new Error('account not found');
|
||||
}
|
||||
|
||||
try {
|
||||
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();
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
setEthMaxFee(data.maxFeePerGas);
|
||||
setEthMaxPriorityFee(data.maxPriorityFeePerGas);
|
||||
setEthGasPrice(data.gasPrice);
|
||||
|
||||
const gasPriceVal = await provider.getGasPrice();
|
||||
const gasPrice = ethers.utils.hexValue(gasPriceVal);
|
||||
|
||||
setEthGasPrice(String(gasPrice));
|
||||
|
||||
const transactionObject = {
|
||||
if (transaction.gasLimit) {
|
||||
setEthGasLimit(BigNumber.from(transaction.gasLimit));
|
||||
} else {
|
||||
const transactionObject: Deferrable<providers.TransactionRequest> = {
|
||||
from: transaction.from!,
|
||||
to: transaction.to!,
|
||||
data: transaction.data!,
|
||||
gasPrice,
|
||||
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(String(gasLimit));
|
||||
setEthGasLimit(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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -160,7 +160,6 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
}
|
||||
|
||||
const response = await approveWalletConnectRequest({
|
||||
networksData,
|
||||
requestEvent,
|
||||
account,
|
||||
namespace,
|
||||
|
@ -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+$/;
|
||||
|
@ -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,10 +65,19 @@ export async function approveWalletConnectRequest({
|
||||
).privKey;
|
||||
const wallet = new Wallet(privKey);
|
||||
const sendTransaction = request.params[0];
|
||||
const updatedTransaction = {
|
||||
const updatedTransaction =
|
||||
maxFeePerGas && maxPriorityFeePerGas
|
||||
? {
|
||||
...sendTransaction,
|
||||
gasLimit: ethGasLimit,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
}
|
||||
: {
|
||||
...sendTransaction,
|
||||
gasLimit: ethGasLimit,
|
||||
gasPrice: ethGasPrice,
|
||||
type: 0,
|
||||
};
|
||||
|
||||
if (!(provider instanceof providers.JsonRpcProvider)) {
|
||||
|
Loading…
Reference in New Issue
Block a user