From 47eceff322761241b2438aa034634b894d422f73 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Sat, 13 Apr 2024 15:19:29 +0100 Subject: [PATCH] ABI update: ContractBorrowerOperations --- frontend/src/app/contracts/ContractAction.tsx | 59 ++- .../contracts/ContractBorrowerOperations.tsx | 423 +++++++++++------- frontend/src/form-utils.ts | 72 +++ 3 files changed, 400 insertions(+), 154 deletions(-) diff --git a/frontend/src/app/contracts/ContractAction.tsx b/frontend/src/app/contracts/ContractAction.tsx index 39057fb8..4f9235e0 100644 --- a/frontend/src/app/contracts/ContractAction.tsx +++ b/frontend/src/app/contracts/ContractAction.tsx @@ -9,11 +9,16 @@ export function ContractAction({ onFillExample, onSubmit, title, + error, }: { children?: ReactNode; onFillExample?: () => void; onSubmit?: () => void; title: string; + error: { + name: string; + message: string; + } | null; }) { return (
{ + onSubmit={(e) => { e.preventDefault(); onSubmit?.(); }} @@ -69,6 +74,58 @@ export function ContractAction({ >
); diff --git a/frontend/src/app/contracts/ContractBorrowerOperations.tsx b/frontend/src/app/contracts/ContractBorrowerOperations.tsx index 841b0cd6..47ebc018 100644 --- a/frontend/src/app/contracts/ContractBorrowerOperations.tsx +++ b/frontend/src/app/contracts/ContractBorrowerOperations.tsx @@ -1,13 +1,10 @@ -import type { Dnum } from "dnum"; - import { BorrowerOperations } from "@/src/abi/BorrowerOperations"; import { FormField } from "@/src/comps/FormField/FormField"; import { TextInput } from "@/src/comps/Input/TextInput"; import { CONTRACT_BORROWER_OPERATIONS } from "@/src/env"; -import { parseInputInt, parseInputPercentage, parseInputValue } from "@/src/form-utils"; +import { formValue, parseInputInt, parseInputPercentage, parseInputValue, useForm } from "@/src/form-utils"; import { getTroveId } from "@/src/liquity-utils"; import * as dn from "dnum"; -import { useState } from "react"; import { useAccount, useWriteContract } from "wagmi"; import { Contract } from "./Contract"; import { ContractAction } from "./ContractAction"; @@ -27,50 +24,19 @@ export function ContractBorrowerOperations() { ); } -type FormValue = [fieldValue: string, parsedValue: T]; - function OpenTrove() { const account = useAccount(); - const { writeContract } = useWriteContract(); - - const [formValues, setFormValues] = useState<{ - ownerIndex: FormValue; - maxFeePercentage: FormValue; - boldAmount: FormValue; - upperHint: FormValue; - lowerHint: FormValue; - annualInterestRate: FormValue; - ethAmount: FormValue; - }>(() => ({ - ownerIndex: ["", 0n], - maxFeePercentage: ["", dn.from(0, 18)], - boldAmount: ["", dn.from(0, 18)], - upperHint: ["", dn.from(0, 18)], - lowerHint: ["", dn.from(0, 18)], - annualInterestRate: ["", dn.from(0, 18)], - ethAmount: ["", dn.from(0, 18)], - })); - - const formProps = Object.fromEntries([ - ["ownerIndex", parseInputInt] as const, - ["maxFeePercentage", parseInputValue] as const, - ["boldAmount", parseInputValue] as const, - ["upperHint", parseInputValue] as const, - ["lowerHint", parseInputValue] as const, - ["annualInterestRate", parseInputPercentage] as const, - ["ethAmount", parseInputValue] as const, - ].map(([name, valueParser]) => [name, { - onChange: (value: string) => { - const parsedValue = valueParser(value); - if (parsedValue !== null) { - setFormValues((values) => ({ - ...values, - [name]: [value, parsedValue], - })); - } - }, - value: formValues[name][0], - }])); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values, setFormValues } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + maxFeePercentage: formValue(dn.from(0, 18), parseInputValue), + ethAmount: formValue(dn.from(0, 18), parseInputValue), + boldAmount: formValue(dn.from(0, 18), parseInputValue), + upperHint: formValue(dn.from(0, 18), parseInputValue), + lowerHint: formValue(dn.from(0, 18), parseInputValue), + annualInterestRate: formValue(dn.from(0, 18), parseInputPercentage), + }), reset); const onSubmit = () => { if (account.address) { @@ -80,56 +46,56 @@ function OpenTrove() { functionName: "openTrove", args: [ account.address, - formValues.ownerIndex[1], - formValues.maxFeePercentage[1][0], - formValues.ethAmount[1][0], - formValues.boldAmount[1][0], - formValues.upperHint[1][0], - formValues.lowerHint[1][0], - formValues.annualInterestRate[1][0], + values.ownerIndex, + values.maxFeePercentage[0], + values.ethAmount[0], + values.boldAmount[0], + values.upperHint[0], + values.lowerHint[0], + values.annualInterestRate[0], ], }); } }; - const onFillExample = () => { - setFormValues({ - ownerIndex: ["0", 0n], - maxFeePercentage: ["100", [100n * 10n ** 16n, 18]], - boldAmount: ["1800", [1800n * 10n ** 18n, 18]], - upperHint: ["0", [0n, 18]], - lowerHint: ["0", [0n, 18]], - annualInterestRate: ["5", [5n * 10n ** 16n, 18]], - ethAmount: ["20", [20n * 10n ** 18n, 18]], - }); - }; + // const onFillExample = () => { + // setFormValues({ + // ownerIndex: ["0", 0n], + // maxFeePercentage: ["100", [100n * 10n ** 16n, 18]], + // ethAmount: ["20", [20n * 10n ** 18n, 18]], + // boldAmount: ["1800", [1800n * 10n ** 18n, 18]], + // upperHint: ["0", [0n, 18]], + // lowerHint: ["0", [0n, 18]], + // annualInterestRate: ["5", [5n * 10n ** 16n, 18]], + // }); + // }; return ( - + - + - + - + - + - + - + ); @@ -137,28 +103,11 @@ function OpenTrove() { function CloseTrove() { const account = useAccount(); - const { writeContract } = useWriteContract(); - - const [formValues, setFormValues] = useState<{ - ownerIndex: FormValue; - }>(() => ({ - ownerIndex: ["", 0n], - })); - - const formProps = Object.fromEntries([ - ["ownerIndex", parseInputInt] as const, - ].map(([name, valueParser]) => [name, { - onChange: (value: string) => { - const parsedValue = valueParser(value); - if (parsedValue !== null) { - setFormValues((values) => ({ - ...values, - [name]: [value, parsedValue], - })); - } - }, - value: formValues[name][0], - }])); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + }), reset); const onSubmit = () => { if (account.address) { @@ -167,7 +116,7 @@ function CloseTrove() { address: CONTRACT_BORROWER_OPERATIONS, functionName: "closeTrove", args: [ - getTroveId(account.address, formValues.ownerIndex[1]), + getTroveId(account.address, values.ownerIndex), ], }); } @@ -175,112 +124,280 @@ function CloseTrove() { return ( - + ); } -function AdjustTroveInterestRate() { +function RepayBold() { + const account = useAccount(); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + boldAmount: formValue(dn.from(0, 18), parseInputValue), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "repayBold", + args: [ + getTroveId(account.address, values.ownerIndex), + values.boldAmount[0], + ], + }); + } + }; return ( - - - - - - + + + - - + + ); } -function AdjustTrove() { +function AddCollateral() { + const account = useAccount(); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + ethAmount: formValue(dn.from(0, 18), parseInputValue), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "addColl", + args: [ + getTroveId(account.address, values.ownerIndex), + values.ethAmount[0], + ], + }); + } + }; + return ( - - - - - - - - - + + + - - + + ); } -function RepayBold() { +function WithdrawCollateral() { const account = useAccount(); - const { writeContract } = useWriteContract(); - const [[boldAmountInput, boldAmount], setBoldAmount] = useState<[string, Dnum]>(["", dn.from(0, 18)]); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + ethAmount: formValue(dn.from(0, 18), parseInputValue), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "withdrawColl", + args: [ + getTroveId(account.address, values.ownerIndex), + values.ethAmount[0], + ], + }); + } + }; + return ( { - if (account.address) { - writeContract({ - abi: BorrowerOperations, - address: CONTRACT_BORROWER_OPERATIONS, - functionName: "repayBold", - args: [boldAmount[0]], - }); - } - }} + error={error} + onSubmit={onSubmit} + title="Withdraw Collateral" > - - { - const parsedValue = parseInputValue(value); - if (parsedValue !== null) { - setBoldAmount([value, parsedValue]); - } - }} - value={boldAmountInput} - /> + + + + + ); } -function AddCollateral() { +function AdjustTroveInterestRate() { + const account = useAccount(); + const { writeContract, error, reset } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + newAnnualInterestRate: formValue(dn.from(0, 18), parseInputPercentage), + upperHint: formValue(dn.from(0, 18), parseInputValue), + lowerHint: formValue(dn.from(0, 18), parseInputValue), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "adjustTroveInterestRate", + args: [ + getTroveId(account.address, values.ownerIndex), + values.newAnnualInterestRate[0], + values.upperHint[0], + values.lowerHint[0], + ], + }); + } + }; + return ( - - - + + + + + + + + + + + + ); } -function WithdrawCollateral() { +function AdjustTrove() { + const account = useAccount(); + const { error, reset, writeContract } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + maxFeePercentage: formValue(dn.from(0, 18), parseInputValue), + collChange: formValue(dn.from(0, 18), parseInputValue), + isCollIncrease: formValue(false, (value) => value === "true"), + boldChange: formValue(dn.from(0, 18), parseInputValue), + isDebtIncrease: formValue(false, (value) => value === "true"), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "adjustTrove", + args: [ + getTroveId(account.address, values.ownerIndex), + values.maxFeePercentage[0], + values.collChange[0], + values.isCollIncrease, + values.boldChange[0], + values.isDebtIncrease, + ], + }); + } + }; + return ( - - - + + + + + + + + + + + + + + + + + + ); } function WithdrawBold() { + const account = useAccount(); + const { error, reset, writeContract } = useWriteContract(); + + const { fieldsProps, values } = useForm(() => ({ + ownerIndex: formValue(0n, parseInputInt), + maxFeePercentage: formValue(dn.from(0, 18), parseInputValue), + boldAmount: formValue(dn.from(0, 18), parseInputValue), + }), reset); + + const onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "withdrawBold", + args: [ + getTroveId(account.address, values.ownerIndex), + values.maxFeePercentage[0], + values.boldAmount[0], + ], + }); + } + }; + return ( - + + + + - + - + ); diff --git a/frontend/src/form-utils.ts b/frontend/src/form-utils.ts index 431c91b8..93460620 100644 --- a/frontend/src/form-utils.ts +++ b/frontend/src/form-utils.ts @@ -1,5 +1,6 @@ import { ADDRESS_ZERO, isAddress } from "@/src/eth-utils"; import * as dn from "dnum"; +import { useState } from "react"; const inputValueRegex = /^[0-9]*\.?[0-9]*?$/; export function isInputValueFloat(value: string) { @@ -40,7 +41,78 @@ export function parseInputInt(value: string) { return isInputValueInt(value) ? BigInt(value) : null; } +export function parseInputBoolean(value: string) { + value = value.trim(); + return value === "true" || value === "1"; +} + export function parseInputAddress(value: string) { value = value.trim(); return isAddress(value) ? value : ADDRESS_ZERO; } + +export type FormValue = [fieldValue: string, parsedValue: T, parser: (value: string) => T]; +export function formValue(parsedValueDefault: T, parser: (value: string) => T): FormValue { + return ["", parsedValueDefault, parser]; +} + +export function useForm
>>( + initial: () => Form, + onUpdate: () => void, // use this to reset the error state +) { + const [form, setForm] = useState(initial); + + const fieldsProps: Record void; + value: string; + }> = {}; + + for (const [name, formValue] of Object.entries(form)) { + const parser = formValue[2]; + fieldsProps[name] = { + onChange: (value: string) => { + onUpdate(); + const parsedValue = parser(value); + if (parsedValue !== null) { + setForm((values) => ({ + ...values, + [name]: [value, parsedValue, parser], + })); + } + }, + value: form[name][0], + }; + } + + const parsedValues = Object.fromEntries( + Object + .entries(form) + .map(([name, [_, parsedValue]]) => [name, parsedValue]), + ) as { + [K in keyof Form]: NonNullable; + }; + + const setFormValues = (values: Record) => { + setForm((form) => { + const newForm: Record> = { ...form }; + for (const [name, value] of Object.entries(values)) { + const formValue = newForm[name]; + if (formValue) { + const [, , parser] = formValue; + newForm[name] = [ + String(value), + parser(String(value)), + parser, + ]; + } + } + return { ...form, newForm }; + }); + }; + + return { + fieldsProps, + values: parsedValues, + setFormValues, + } as const; +}