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,
|
CHAINID_DEBOUNCE_DELAY,
|
||||||
EMPTY_FIELD_ERROR,
|
EMPTY_FIELD_ERROR,
|
||||||
INVALID_URL_ERROR,
|
INVALID_URL_ERROR,
|
||||||
|
IS_NUMBER_REGEX,
|
||||||
} from '../utils/constants';
|
} from '../utils/constants';
|
||||||
import { getCosmosAccounts } from '../utils/accounts';
|
import { getCosmosAccounts } from '../utils/accounts';
|
||||||
import ETH_CHAINS from '../assets/ethereum-chains.json';
|
import ETH_CHAINS from '../assets/ethereum-chains.json';
|
||||||
@ -38,7 +39,10 @@ const ethNetworkDataSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.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 }),
|
currencySymbol: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -50,7 +54,10 @@ const cosmosNetworkDataSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.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 }),
|
nativeDenom: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
addressPrefix: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
addressPrefix: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
gasPrice: z
|
gasPrice: z
|
||||||
|
@ -7,8 +7,9 @@ import {
|
|||||||
Appbar,
|
Appbar,
|
||||||
TextInput,
|
TextInput,
|
||||||
} from 'react-native-paper';
|
} from 'react-native-paper';
|
||||||
import { providers, BigNumber, ethers } from 'ethers';
|
import { providers, BigNumber } from 'ethers';
|
||||||
import Config from 'react-native-config';
|
import Config from 'react-native-config';
|
||||||
|
import { Deferrable } from 'ethers/lib/utils';
|
||||||
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
@ -36,10 +37,12 @@ import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
|||||||
import DataBox from '../components/DataBox';
|
import DataBox from '../components/DataBox';
|
||||||
import { getPathKey } from '../utils/misc';
|
import { getPathKey } from '../utils/misc';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
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';
|
import TxErrorDialog from '../components/TxErrorDialog';
|
||||||
|
|
||||||
const MEMO = 'Sending signed tx from Laconic Wallet';
|
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<
|
type SignRequestProps = NativeStackScreenProps<
|
||||||
StackParamsList,
|
StackParamsList,
|
||||||
@ -67,8 +70,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
|
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
|
||||||
const [txError, setTxError] = useState<string>();
|
const [txError, setTxError] = useState<string>();
|
||||||
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
|
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
|
||||||
const [ethGasPrice, setEthGasPrice] = useState<string>();
|
const [ethGasPrice, setEthGasPrice] = useState<BigNumber | null>();
|
||||||
const [ethGasLimit, setEthGasLimit] = useState<string>();
|
const [ethGasLimit, setEthGasLimit] = useState<BigNumber>();
|
||||||
|
const [ethMaxFee, setEthMaxFee] = useState<BigNumber | null>();
|
||||||
|
const [ethMaxPriorityFee, setEthMaxPriorityFee] =
|
||||||
|
useState<BigNumber | null>();
|
||||||
|
|
||||||
const isSufficientFunds = useMemo(() => {
|
const isSufficientFunds = useMemo(() => {
|
||||||
if (!transaction.value) {
|
if (!transaction.value) {
|
||||||
@ -183,15 +189,27 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setAccount(requestAccount);
|
setAccount(requestAccount);
|
||||||
setIsLoading(false);
|
|
||||||
},
|
},
|
||||||
[navigation, requestedNetwork],
|
[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(() => {
|
useEffect(() => {
|
||||||
if (namespace === EIP155) {
|
if (namespace === EIP155) {
|
||||||
const ethFees = BigNumber.from(transaction.gasLimit ?? ethGasLimit ?? 0)
|
const ethFees = BigNumber.from(ethGasLimit ?? 0)
|
||||||
.mul(BigNumber.from(transaction.gasPrice ?? ethGasPrice ?? 0))
|
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice ?? 0))
|
||||||
.toString();
|
.toString();
|
||||||
setFees(ethFees);
|
setFees(ethFees);
|
||||||
} else {
|
} else {
|
||||||
@ -214,18 +232,39 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
ethGasPrice,
|
ethGasPrice,
|
||||||
cosmosGasLimit,
|
cosmosGasLimit,
|
||||||
requestedNetwork,
|
requestedNetwork,
|
||||||
|
ethMaxFee,
|
||||||
]);
|
]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
retrieveData(transaction.from!);
|
retrieveData(transaction.from!);
|
||||||
}, [retrieveData, transaction]);
|
}, [retrieveData, transaction]);
|
||||||
|
|
||||||
|
const isEIP1559 = useMemo(() => {
|
||||||
|
if (cosmosGasLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ethMaxFee !== null && ethMaxPriorityFee !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [cosmosGasLimit, ethMaxFee, ethMaxPriorityFee]);
|
||||||
|
|
||||||
const acceptRequestHandler = async () => {
|
const acceptRequestHandler = async () => {
|
||||||
setIsTxLoading(true);
|
setIsTxLoading(true);
|
||||||
if (!account) {
|
|
||||||
throw new Error('account not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
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({
|
const response = await approveWalletConnectRequest({
|
||||||
requestEvent,
|
requestEvent,
|
||||||
account,
|
account,
|
||||||
@ -244,12 +283,10 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
gas: cosmosGasLimit!,
|
gas: cosmosGasLimit!,
|
||||||
},
|
},
|
||||||
ethGasLimit:
|
ethGasLimit:
|
||||||
namespace === EIP155
|
namespace === EIP155 ? BigNumber.from(ethGasLimit) : undefined,
|
||||||
? BigNumber.from(transaction.gasLimit ?? ethGasLimit)
|
ethGasPrice: ethGasPrice?.toHexString(),
|
||||||
: undefined,
|
maxPriorityFeePerGas: ethMaxPriorityFee ?? undefined,
|
||||||
ethGasPrice: transaction.gasPrice
|
maxFeePerGas: ethMaxFee ?? undefined,
|
||||||
? String(transaction.gasPrice)
|
|
||||||
: ethGasPrice,
|
|
||||||
sendMsg,
|
sendMsg,
|
||||||
memo: MEMO,
|
memo: MEMO,
|
||||||
});
|
});
|
||||||
@ -331,34 +368,33 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getEthGas = async () => {
|
const getEthGas = async () => {
|
||||||
try {
|
try {
|
||||||
if (transaction.gasLimit && transaction.gasPrice) {
|
if (!isSufficientFunds || !provider) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSufficientFunds) {
|
const data = await provider.getFeeData();
|
||||||
return;
|
|
||||||
|
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) {
|
} catch (error: any) {
|
||||||
setTxError(error.message);
|
setTxError(error.message);
|
||||||
setIsTxErrorDialogOpen(true);
|
setIsTxErrorDialogOpen(true);
|
||||||
@ -452,19 +488,72 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
|
|
||||||
{namespace === EIP155 ? (
|
{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
|
<DataBox
|
||||||
label={`Gas Fees (${
|
label={`${
|
||||||
namespace === EIP155
|
isEIP1559 === true ? 'Max Fee' : 'Gas Fee'
|
||||||
? 'wei'
|
} (wei)`}
|
||||||
: requestedNetwork!.nativeDenom
|
|
||||||
})`}
|
|
||||||
data={fees!}
|
data={fees!}
|
||||||
/>
|
/>
|
||||||
<DataBox label="Data" data={transaction.data!} />
|
<DataBox label="Data" data={transaction.data!} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Text style={styles.dataBoxLabel}>{`Fees (${
|
<Text style={styles.dataBoxLabel}>{`Fee (${
|
||||||
requestedNetwork!.nativeDenom
|
requestedNetwork!.nativeDenom
|
||||||
})`}</Text>
|
})`}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -477,7 +566,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
|||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
value={cosmosGasLimit}
|
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({
|
const response = await approveWalletConnectRequest({
|
||||||
networksData,
|
|
||||||
requestEvent,
|
requestEvent,
|
||||||
account,
|
account,
|
||||||
namespace,
|
namespace,
|
||||||
|
@ -32,3 +32,5 @@ export const CHAINID_DEBOUNCE_DELAY = 250;
|
|||||||
|
|
||||||
export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
|
export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
|
||||||
export const INVALID_URL_ERROR = 'Invalid URL';
|
export const INVALID_URL_ERROR = 'Invalid URL';
|
||||||
|
|
||||||
|
export const IS_NUMBER_REGEX = /^\d+$/;
|
||||||
|
@ -28,6 +28,8 @@ export async function approveWalletConnectRequest({
|
|||||||
ethGasPrice,
|
ethGasPrice,
|
||||||
sendMsg,
|
sendMsg,
|
||||||
memo,
|
memo,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
maxFeePerGas,
|
||||||
}: {
|
}: {
|
||||||
requestEvent: SignClientTypes.EventArguments['session_request'];
|
requestEvent: SignClientTypes.EventArguments['session_request'];
|
||||||
account: Account;
|
account: Account;
|
||||||
@ -38,6 +40,8 @@ export async function approveWalletConnectRequest({
|
|||||||
cosmosFee?: StdFee;
|
cosmosFee?: StdFee;
|
||||||
ethGasLimit?: BigNumber;
|
ethGasLimit?: BigNumber;
|
||||||
ethGasPrice?: string;
|
ethGasPrice?: string;
|
||||||
|
maxPriorityFeePerGas?: BigNumber;
|
||||||
|
maxFeePerGas?: BigNumber;
|
||||||
sendMsg?: MsgSendEncodeObject;
|
sendMsg?: MsgSendEncodeObject;
|
||||||
memo?: string;
|
memo?: string;
|
||||||
}) {
|
}) {
|
||||||
@ -61,11 +65,20 @@ export async function approveWalletConnectRequest({
|
|||||||
).privKey;
|
).privKey;
|
||||||
const wallet = new Wallet(privKey);
|
const wallet = new Wallet(privKey);
|
||||||
const sendTransaction = request.params[0];
|
const sendTransaction = request.params[0];
|
||||||
const updatedTransaction = {
|
const updatedTransaction =
|
||||||
...sendTransaction,
|
maxFeePerGas && maxPriorityFeePerGas
|
||||||
gasLimit: ethGasLimit,
|
? {
|
||||||
gasPrice: ethGasPrice,
|
...sendTransaction,
|
||||||
};
|
gasLimit: ethGasLimit,
|
||||||
|
maxFeePerGas,
|
||||||
|
maxPriorityFeePerGas,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...sendTransaction,
|
||||||
|
gasLimit: ethGasLimit,
|
||||||
|
gasPrice: ethGasPrice,
|
||||||
|
type: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if (!(provider instanceof providers.JsonRpcProvider)) {
|
if (!(provider instanceof providers.JsonRpcProvider)) {
|
||||||
throw new Error('Provider not found');
|
throw new Error('Provider not found');
|
||||||
|
Loading…
Reference in New Issue
Block a user