Skip to content

Commit

Permalink
[fix]: Allow users to edit gas limit on bridges (#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
fpezzola authored Jan 18, 2023
1 parent 4c7b046 commit 3b58d4b
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 50 deletions.
6 changes: 5 additions & 1 deletion packages/background/src/controllers/BridgeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export interface BridgeTransaction extends BridgeParameters {
customNonce?: number;
flashbots?: boolean;
gasPrice?: BigNumber;
gasLimit?: BigNumber;
maxFeePerGas?: BigNumber;
maxPriorityFeePerGas?: BigNumber;
}
Expand Down Expand Up @@ -590,6 +591,7 @@ export default class BridgeController extends BaseController<
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
gasLimit,
params: {
transactionRequest,
fromToken,
Expand Down Expand Up @@ -622,7 +624,9 @@ export default class BridgeController extends BaseController<
maxFeePerGas: maxFeePerGas
? BigNumber.from(maxFeePerGas)
: undefined,
gasLimit: BigNumber.from(transactionRequest.gasLimit),
gasLimit: BigNumber.from(
gasLimit ?? transactionRequest.gasLimit
),
nonce: customNonce,
},
origin: 'blank',
Expand Down
74 changes: 51 additions & 23 deletions packages/ui/src/components/transactions/GasPriceComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FunctionComponent, useRef, useState, useEffect } from "react"
import { FunctionComponent, useRef, useState, useEffect, useMemo } from "react"
import classnames from "classnames"
import { BigNumber } from "@ethersproject/bignumber"

Expand Down Expand Up @@ -49,6 +49,7 @@ interface GasComponentProps {
gasFees: TransactionFeeData
selectedOption: GasPriceOption
options: GasPriceOption[]
minGasLimit?: string
setSelectedGas: (option: GasPriceOption) => void
getGasOption: (label: string, gasFees: TransactionFeeData) => GasPriceOption
}
Expand Down Expand Up @@ -160,15 +161,21 @@ const GasSelectorBasic = (props: GasComponentProps) => {
}

// Schema
const schema = yup.object({
gasLimit: makeStringNumberFormField("Gas limit is required", false),
maxPriorityFeePerGas: makeStringNumberFormField(
"Max tip is required",
true
),
maxFeePerGas: makeStringNumberFormField("Max fee is required", false),
})
type GasAdvancedForm = InferType<typeof schema>
const schemaBuilder = ({ minGasLimit }: { minGasLimit?: string } = {}) =>
yup.object({
gasLimit: makeStringNumberFormField("Gas limit is required", false, {
min: [
parseInt(minGasLimit ?? "0"),
`Gas limit can't be lower than ${parseInt(minGasLimit ?? "0")}`,
],
}),
maxPriorityFeePerGas: makeStringNumberFormField(
"Max tip is required",
true
),
maxFeePerGas: makeStringNumberFormField("Max fee is required", false),
})
type GasAdvancedForm = InferType<ReturnType<typeof schemaBuilder>>

// Advanced tab. Allows users to enter manual fee values.
const GasSelectorAdvanced = (props: GasComponentProps) => {
Expand All @@ -193,6 +200,14 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
gasPricesLevels.average.maxPriorityFeePerGas
)

const schema = useMemo(() => {
return schemaBuilder({ minGasLimit: props.minGasLimit })
}, [props.minGasLimit])

const [gasLimitWarning, setGasLimitWarning] = useState("")
const [tipWarning, setTipWarning] = useState("")
const [maxFeeWarning, setMaxFeeWarning] = useState("")

const {
register,
handleSubmit,
Expand Down Expand Up @@ -236,16 +251,20 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
setValue("maxFeePerGas", formatUnits(defaultFees.maxFeePerGas!, "gwei"))
}

const validateGasLimit = (gasLimit?: BigNumber) => {
setGasLimitWarning(
gasLimit?.lt(defaultFees.gasLimit!)
? `Gas limit lower than suggested (${defaultFees.gasLimit})`
: ""
)
}

const validateFees = (fees: TransactionFeeData) => {
clearErrors("maxFeePerGas")

const baseFee = BigNumber.from(baseFeePerGas)

setGasLimitWarning(
fees.gasLimit?.lt(defaultFees.gasLimit!)
? "Gas limit lower than suggested"
: ""
)
validateGasLimit(fees.gasLimit)

setMaxFeeWarning(
fees.maxFeePerGas?.lt(baseFee.add(fees.maxPriorityFeePerGas!))
Expand Down Expand Up @@ -287,7 +306,6 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {

const handleBlur = () => {
const values = getValues()

const fees: TransactionFeeData = {
gasLimit: BigNumber.from(
values.gasLimit === "" ? "0" : values.gasLimit
Expand Down Expand Up @@ -321,10 +339,6 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
setSelectedGas(custom)
})

const [gasLimitWarning, setGasLimitWarning] = useState("")
const [tipWarning, setTipWarning] = useState("")
const [maxFeeWarning, setMaxFeeWarning] = useState("")

return (
<div className="flex flex-col w-full">
<div className="flex flex-col w-full space-y-3 px-3 pb-3">
Expand All @@ -334,7 +348,9 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
</label>
<input
type="text"
{...register("gasLimit")}
{...register("gasLimit", {
pattern: /[0-9]/g,
})}
className={classnames(
Classes.inputBordered,
"w-full",
Expand All @@ -348,6 +364,11 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
autoComplete="off"
onKeyDown={handleKeyDown}
onInput={handleChangeAmountWei((value) => {
//If there was a warning, then check whether we need to clean it or not.
//If there weren't, lets validate that onBlur to avoid showing and clearing error everytime.
if (gasLimitWarning) {
validateGasLimit(BigNumber.from(value ?? "0"))
}
setValue("gasLimit", value, {
shouldValidate: true,
})
Expand Down Expand Up @@ -388,7 +409,9 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
<EndLabel label="GWEI">
<input
type="text"
{...register("maxPriorityFeePerGas")}
{...register("maxPriorityFeePerGas", {
pattern: /[0-9.]/g,
})}
className={classnames(
Classes.inputBordered,
"w-full",
Expand Down Expand Up @@ -445,7 +468,9 @@ const GasSelectorAdvanced = (props: GasComponentProps) => {
<EndLabel label="GWEI">
<input
type="text"
{...register("maxFeePerGas")}
{...register("maxFeePerGas", {
pattern: /[0-9.]/g,
})}
className={classnames(
Classes.inputBordered,
"w-full",
Expand Down Expand Up @@ -538,13 +563,15 @@ const GasPriceComponent: FunctionComponent<{
isParentLoading?: boolean
showEstimationError?: boolean
displayOnlyMaxValue?: boolean
minGasLimit?: string
}> = ({
defaultGas,
setGas,
isParentLoading,
disabled,
showEstimationError,
displayOnlyMaxValue = false,
minGasLimit,
}) => {
//Popup variables
const ref = useRef(null)
Expand Down Expand Up @@ -894,6 +921,7 @@ const GasPriceComponent: FunctionComponent<{
setShowEstimationWarning(false)
setActive(false)
}}
minGasLimit={minGasLimit}
getGasOption={getGasOption}
/>
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/components/transactions/GasPriceSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ interface GasTabProps {
selectedGasPrice: TransactionSpeedOption
defaultGasLimit: BigNumber
defaultGasPrice: BigNumber
minGasLimit?: number
setUserChanged: (userChanged: boolean) => void
handlePriceSelection: (price: TransactionSpeedOption) => void
getSpeedOption: (
Expand Down Expand Up @@ -498,7 +499,7 @@ export const GasPriceSelector = (props: GasPriceSelectorProps) => {
const [userChanged, setUserChanged] = useState<boolean>(false)

// Tabs variables
// if network is configured to not show gas levels or received gas does not match with average, default is advanced tab.
// if network is configured to noxt show gas levels or received gas does not match with average, default is advanced tab.
const [tab, setTab] = useState(
tabs[!showGasLevels || !defaultLevel ? 1 : 0]
)
Expand Down
10 changes: 6 additions & 4 deletions packages/ui/src/routes/bridge/BridgeConfirmPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import AssetAmountDisplay from "../../components/assets/AssetAmountDisplay"
import ClickableText from "../../components/button/ClickableText"
import Divider from "../../components/Divider"
import GasPriceComponent from "../../components/transactions/GasPriceComponent"
import HardwareDeviceNotLinkedDialog from "../../components/dialog/HardwareDeviceNotLinkedDialog"
import LoadingDialog from "../../components/dialog/LoadingDialog"
import NetworkDisplay from "../../components/network/NetworkDisplay"
import PopupFooter from "../../components/popup/PopupFooter"
import PopupHeader from "../../components/popup/PopupHeader"
Expand Down Expand Up @@ -40,7 +37,6 @@ import { isHardwareWallet } from "../../util/account"
import { useBlankState } from "../../context/background/backgroundHooks"
import { useGasPriceData } from "../../context/hooks/useGasPriceData"
import { useHasSufficientBalance } from "../../context/hooks/useHasSufficientBalance"
import { useInProgressAllowanceTransaction } from "../../context/hooks/useInProgressAllowanceTransaction"
import { useInProgressInternalTransaction } from "../../context/hooks/useInProgressInternalTransaction"
import { useUserSettings } from "../../context/hooks/useUserSettings"
import { useLocationRecovery } from "../../util/hooks/useLocationRecovery"
Expand Down Expand Up @@ -354,6 +350,7 @@ const BridgeConfirmPage: FunctionComponent<{}> = () => {
maxFeePerGas: isEIP1559Compatible
? selectedFees.maxFeePerGas
: undefined,
gasLimit: selectedGasLimit,
}

await executeBridge(txParams)
Expand Down Expand Up @@ -661,6 +658,11 @@ const BridgeConfirmPage: FunctionComponent<{}> = () => {
}}
isParentLoading={isGasLoading}
disabled={isGasLoading}
minGasLimit={
transactionRequest
? transactionRequest.gasLimit
: undefined
}
displayOnlyMaxValue
/>
) : (
Expand Down
63 changes: 42 additions & 21 deletions packages/ui/src/util/form.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as yup from "yup"

type ValidationInput<T> = [T, string]

type StringNumberValidator = {
min?: ValidationInput<number>
}
/**
* This function create a yup schema field when you need a number (most of the time gas fees) in a form.
* If you need to add more verification you can do it like so:
Expand All @@ -11,9 +16,10 @@ import * as yup from "yup"
*/
export const makeStringNumberFormField = (
requiredMessage: string,
isGteThan0: boolean = true
) =>
yup
isGteThan0: boolean = true,
{ min }: StringNumberValidator = {}
) => {
let schema = yup
.string()
.required(requiredMessage)
.test("is-correct", "Please enter a number.", (value) => {
Expand All @@ -30,6 +36,21 @@ export const makeStringNumberFormField = (
return isGteThan0 ? parseFloat(value) >= 0 : parseFloat(value) > 0
})

if (min) {
const [minValue, message] = min
schema = schema.test("is-min", message, (value) => {
if (
typeof value !== "string" &&
typeof minValue !== "undefined" &&
value
) {
return false
}
return parseFloat(value ?? "0") >= minValue
})
}
return schema
}
/**
* This function check if the input is a valid number
* @param e Keyboard event
Expand All @@ -47,29 +68,29 @@ export const handleKeyDown = (e: React.KeyboardEvent<any>) => {
}
}

const makeHandleChangeAmount = (replace: RegExp) => (
cb: (value: string) => void,
defaultValue: string = ""
) => (event: React.ChangeEvent<any>) => {
let value: string = event.target.value
const makeHandleChangeAmount =
(replace: RegExp) =>
(cb: (value: string) => void, defaultValue: string = "") =>
(event: React.ChangeEvent<any>) => {
let value: string = event.target.value

value = value
.replace(",", ".")
.replace(replace, "")
.replace(/(\..*?)\..*/g, "$1")
value = value
.replace(",", ".")
.replace(replace, "")
.replace(/(\..*?)\..*/g, "$1")

const match = value.match(/[0-9]+[.,]([0-9]+)/)
const match = value.match(/[0-9]+[.,]([0-9]+)/)

if (match && match[1].length > 9) {
value = value.substring(0, value.length - 1)
}
if (match && match[1].length > 9) {
value = value.substring(0, value.length - 1)
}

if (!value || value === ".") {
value = defaultValue
}
if (!value || value === ".") {
value = defaultValue
}

cb(value)
}
cb(value)
}

export const handleChangeAmountGwei = makeHandleChangeAmount(/[^0-9.]/g)

Expand Down

0 comments on commit 3b58d4b

Please sign in to comment.