-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Ether deposit validation on change and display balance #294
Changes from all commits
11bd9ec
0fc9c5f
2dd5541
cf0f4ba
c9c0c7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,14 @@ import { | |
Autocomplete, | ||
Button, | ||
Collapse, | ||
Flex, | ||
Group, | ||
Loader, | ||
Stack, | ||
Text, | ||
Textarea, | ||
TextInput, | ||
Textarea, | ||
UnstyledButton, | ||
} from "@mantine/core"; | ||
import { useForm } from "@mantine/form"; | ||
import { useDisclosure } from "@mantine/hooks"; | ||
|
@@ -25,18 +27,18 @@ import { | |
} from "react-icons/tb"; | ||
import { | ||
BaseError, | ||
getAddress, | ||
Hex, | ||
getAddress, | ||
isAddress, | ||
isHex, | ||
parseUnits, | ||
zeroAddress, | ||
} from "viem"; | ||
import { useAccount, useWaitForTransactionReceipt } from "wagmi"; | ||
import { TransactionProgress } from "./TransactionProgress"; | ||
import useUndeployedApplication from "./hooks/useUndeployedApplication"; | ||
import { TransactionFormSuccessData } from "./DepositFormTypes"; | ||
import { useFormattedBalance } from "./hooks/useFormattedBalance"; | ||
import { TransactionFormSuccessData } from "../DepositFormTypes"; | ||
import { TransactionProgress } from "../TransactionProgress"; | ||
import { useAccountBalance } from "../hooks/useAccountBalance"; | ||
import useUndeployedApplication from "../hooks/useUndeployedApplication"; | ||
|
||
export interface EtherDepositFormProps { | ||
applications: string[]; | ||
|
@@ -54,22 +56,27 @@ export const EtherDepositForm: FC<EtherDepositFormProps> = (props) => { | |
} = props; | ||
const [advanced, { toggle: toggleAdvanced }] = useDisclosure(false); | ||
const { chain } = useAccount(); | ||
const balance = useFormattedBalance(); | ||
const accountBalance = useAccountBalance(); | ||
|
||
const form = useForm({ | ||
validateInputOnBlur: true, | ||
validateInputOnChange: true, | ||
initialValues: { | ||
accountBalance: accountBalance, | ||
application: "", | ||
amount: "", | ||
execLayerData: "0x", | ||
}, | ||
validate: { | ||
application: (value) => | ||
value !== "" && isAddress(value) ? null : "Invalid application", | ||
amount: (value) => { | ||
amount: (value, values) => { | ||
if (value !== "" && Number(value) > 0) { | ||
if (Number(value) > Number(balance)) { | ||
return `The amount ${value} exceeds your current balance of ${balance} ETH`; | ||
const val = parseUnits( | ||
value, | ||
values.accountBalance.decimals, | ||
); | ||
if (val > values.accountBalance.value) { | ||
return `The amount ${value} exceeds your current balance of ${values.accountBalance.formatted} ETH`; | ||
} | ||
return null; | ||
} else { | ||
|
@@ -79,22 +86,25 @@ export const EtherDepositForm: FC<EtherDepositFormProps> = (props) => { | |
execLayerData: (value) => | ||
isHex(value) ? null : "Invalid hex string", | ||
}, | ||
transformValues: (values) => ({ | ||
address: isAddress(values.application) | ||
? getAddress(values.application) | ||
: zeroAddress, | ||
amount: | ||
values.amount !== "" | ||
? parseUnits( | ||
values.amount, | ||
chain?.nativeCurrency.decimals ?? 18, | ||
) | ||
: undefined, | ||
execLayerData: values.execLayerData | ||
? (values.execLayerData as Hex) | ||
: "0x", | ||
}), | ||
transformValues: (values) => { | ||
return { | ||
address: isAddress(values.application) | ||
? getAddress(values.application) | ||
: zeroAddress, | ||
amount: | ||
values.amount !== "" | ||
? parseUnits( | ||
values.amount, | ||
chain?.nativeCurrency.decimals ?? 18, | ||
) | ||
: undefined, | ||
execLayerData: values.execLayerData | ||
? (values.execLayerData as Hex) | ||
: "0x", | ||
}; | ||
}, | ||
}); | ||
|
||
const { address, amount, execLayerData } = form.getTransformedValues(); | ||
const prepare = useSimulateEtherPortalDepositEther({ | ||
args: [address, execLayerData], | ||
|
@@ -118,16 +128,27 @@ export const EtherDepositForm: FC<EtherDepositFormProps> = (props) => { | |
form.reset(); | ||
execute.reset(); | ||
onSearchApplications(""); | ||
accountBalance.refetch(); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [wait, onSearchApplications, onSuccess]); | ||
}, [wait, onSearchApplications, onSuccess, accountBalance]); | ||
|
||
useEffect(() => { | ||
form.setValues({ accountBalance: accountBalance }); | ||
|
||
if (form.isDirty("amount")) { | ||
form.validateField("amount"); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [accountBalance]); | ||
|
||
return ( | ||
<form data-testid="ether-deposit-form"> | ||
<Stack> | ||
<Autocomplete | ||
label="Application" | ||
description="The application smart contract address" | ||
data-testid="application-input" | ||
placeholder="0x" | ||
data={applications} | ||
withAsterisk | ||
|
@@ -157,21 +178,44 @@ export const EtherDepositForm: FC<EtherDepositFormProps> = (props) => { | |
</Alert> | ||
)} | ||
|
||
<TextInput | ||
type="number" | ||
step="1" | ||
min={0} | ||
label="Amount" | ||
description="Amount of ether to deposit" | ||
placeholder="0" | ||
rightSectionWidth={60} | ||
rightSection={<Text>ETH</Text>} | ||
withAsterisk | ||
{...form.getInputProps("amount")} | ||
/> | ||
<Stack gap="xs"> | ||
<TextInput | ||
type="number" | ||
step="1" | ||
min={0} | ||
label="Amount" | ||
description="Amount of ether to deposit" | ||
data-testid="amount-input" | ||
placeholder="0" | ||
rightSectionWidth={60} | ||
rightSection={<Text>ETH</Text>} | ||
withAsterisk | ||
{...form.getInputProps("amount")} | ||
/> | ||
|
||
<Flex c={"dark.2"} gap="3"> | ||
<Text fz="xs">Balance: {accountBalance.formatted}</Text> | ||
{accountBalance.value > 0 && ( | ||
<UnstyledButton | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we switch to using the standard There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey! I started with the |
||
fz={"xs"} | ||
c={"cyan"} | ||
onClick={() => { | ||
form.setFieldValue( | ||
"amount", | ||
accountBalance.formatted, | ||
); | ||
}} | ||
data-testid="max-button" | ||
> | ||
Max | ||
</UnstyledButton> | ||
)} | ||
</Flex> | ||
</Stack> | ||
|
||
<Collapse in={advanced}> | ||
<Textarea | ||
data-testid="eth-extra-data-input" | ||
label="Extra data" | ||
description="Extra execution layer data handled by the application" | ||
{...form.getInputProps("execLayerData")} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { isNotNil, propOr } from "ramda"; | ||
import { useMemo } from "react"; | ||
import { formatUnits } from "viem"; | ||
import { useAccount, useBalance } from "wagmi"; | ||
|
||
type DataType = | ||
| { | ||
decimals: number; | ||
formatted: string; | ||
symbol: string; | ||
value: bigint; | ||
} | ||
| undefined; | ||
|
||
/** | ||
* Check the ETH balance of the connected account. | ||
* returns the value, decimals, symbol, formatted value and fn to refresh the information. | ||
* @returns | ||
*/ | ||
export const useAccountBalance = () => { | ||
const { address } = useAccount(); | ||
const { data, refetch } = useBalance({ | ||
address, | ||
}); | ||
|
||
const result = useMemo(() => { | ||
const value = propOr<bigint, DataType, bigint>(0n, "value", data); | ||
const decimals = propOr<number, DataType, number>(18, "decimals", data); | ||
const symbol = propOr<string, DataType, string>("ETH", "symbol", data); | ||
const formatted = isNotNil(data) ? formatUnits(value, decimals) : "0"; | ||
|
||
return { | ||
value, | ||
decimals, | ||
symbol, | ||
formatted, | ||
refetch, | ||
}; | ||
}, [data, refetch]); | ||
|
||
return result; | ||
}; |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that we won't be needing
useFormattedBalance
hook. If this is true, please delete it and the related unit test suite.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed on c9c0c7f