diff --git a/frontend/src/app/contracts/ContractBorrowerOperations.tsx b/frontend/src/app/contracts/ContractBorrowerOperations.tsx index 68ef7058..841b0cd6 100644 --- a/frontend/src/app/contracts/ContractBorrowerOperations.tsx +++ b/frontend/src/app/contracts/ContractBorrowerOperations.tsx @@ -1,12 +1,11 @@ -import type { Address } from "@/src/types"; 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 { ADDRESS_ZERO } from "@/src/eth-utils"; -import { parseInputAddress, parseInputPercentage, parseInputValue } from "@/src/form-utils"; +import { parseInputInt, parseInputPercentage, parseInputValue } from "@/src/form-utils"; +import { getTroveId } from "@/src/liquity-utils"; import * as dn from "dnum"; import { useState } from "react"; import { useAccount, useWriteContract } from "wagmi"; @@ -28,33 +27,38 @@ export function ContractBorrowerOperations() { ); } +type FormValue = [fieldValue: string, parsedValue: T]; + function OpenTrove() { const account = useAccount(); const { writeContract } = useWriteContract(); const [formValues, setFormValues] = useState<{ - maxFeePercentage: [string, Dnum]; - boldAmount: [string, Dnum]; - upperHint: [string, Address]; - lowerHint: [string, Address]; - annualInterestRate: [string, Dnum]; - ethValue: [string, Dnum]; + 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: ["", ADDRESS_ZERO], - lowerHint: ["", ADDRESS_ZERO], + upperHint: ["", dn.from(0, 18)], + lowerHint: ["", dn.from(0, 18)], annualInterestRate: ["", dn.from(0, 18)], - ethValue: ["", 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", parseInputAddress] as const, - ["lowerHint", parseInputAddress] as const, + ["upperHint", parseInputValue] as const, + ["lowerHint", parseInputValue] as const, ["annualInterestRate", parseInputPercentage] as const, - ["ethValue", parseInputValue] as const, + ["ethAmount", parseInputValue] as const, ].map(([name, valueParser]) => [name, { onChange: (value: string) => { const parsedValue = valueParser(value); @@ -75,26 +79,28 @@ function OpenTrove() { address: CONTRACT_BORROWER_OPERATIONS, functionName: "openTrove", args: [ + account.address, + formValues.ownerIndex[1], formValues.maxFeePercentage[1][0], + formValues.ethAmount[1][0], formValues.boldAmount[1][0], - formValues.upperHint[1], - formValues.lowerHint[1], + formValues.upperHint[1][0], + formValues.lowerHint[1][0], formValues.annualInterestRate[1][0], ], - value: formValues.ethValue[1][0], }); } }; const onFillExample = () => { - const address = account.address ?? ADDRESS_ZERO; setFormValues({ + ownerIndex: ["0", 0n], maxFeePercentage: ["100", [100n * 10n ** 16n, 18]], boldAmount: ["1800", [1800n * 10n ** 18n, 18]], - upperHint: [address, address], - lowerHint: [address, address], + upperHint: ["0", [0n, 18]], + lowerHint: ["0", [0n, 18]], annualInterestRate: ["5", [5n * 10n ** 16n, 18]], - ethValue: ["20", [20n * 10n ** 18n, 18]], + ethAmount: ["20", [20n * 10n ** 18n, 18]], }); }; @@ -104,9 +110,15 @@ function OpenTrove() { onSubmit={onSubmit} title="Open Trove" > + + + + + + @@ -119,9 +131,6 @@ function OpenTrove() { - - - ); } @@ -129,20 +138,50 @@ 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 onSubmit = () => { + if (account.address) { + writeContract({ + abi: BorrowerOperations, + address: CONTRACT_BORROWER_OPERATIONS, + functionName: "closeTrove", + args: [ + getTroveId(account.address, formValues.ownerIndex[1]), + ], + }); + } + }; + return ( { - if (account.address) { - writeContract({ - abi: BorrowerOperations, - address: CONTRACT_BORROWER_OPERATIONS, - functionName: "closeTrove", - args: [], - }); - } - }} - /> + onSubmit={onSubmit} + > + + + + ); } diff --git a/frontend/src/app/contracts/page.tsx b/frontend/src/app/contracts/page.tsx index 6bc55c48..2d6bd2d3 100644 --- a/frontend/src/app/contracts/page.tsx +++ b/frontend/src/app/contracts/page.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from "react"; import { ADDRESS_ZERO, shortenAddress } from "@/src/eth-utils"; import { + getTroveId, useBoldBalance, useCloseTrove, useLiquity2Info, @@ -54,7 +55,7 @@ function AccountDetails() { const { address } = useAccount(); const balance = useBalance({ address: address ?? ADDRESS_ZERO }); const boldBalance = useBoldBalance(address ?? ADDRESS_ZERO); - const rewards = useRewards(address ?? ADDRESS_ZERO); + const rewards = useRewards(getTroveId(address ?? ADDRESS_ZERO, 0n)); return ( address && data.some(({ status }) => status === "error"), - () =>
error
, + ({ data }) =>
error
, ) .with( { @@ -122,21 +123,22 @@ function AccountDetails() { function TroveDetails() { const { address } = useAccount(); - const troveDetails = useTroveDetails(address ?? ADDRESS_ZERO); + const troveDetails = useTroveDetails(address && getTroveId(address, 0n)); - const closeTrove = useCloseTrove(address ?? ADDRESS_ZERO); + const closeTrove = useCloseTrove(getTroveId(address ?? ADDRESS_ZERO, 0n)); const openTrove = useOpenTrove(address ?? ADDRESS_ZERO, { + ownerIndex: 0n, maxFeePercentage: 100n * 10n ** 16n, // 100% boldAmount: 1800n * 10n ** 18n, // 1800 BOLD - upperHint: address ?? ADDRESS_ZERO, - lowerHint: address ?? ADDRESS_ZERO, + upperHint: 0n, + lowerHint: 0n, interestRate: 5n * 10n ** 16n, // 5% - value: 20n * 10n ** 18n, // 20 ETH + ethAmount: 20n * 10n ** 18n, // 20 ETH }); return ( - TCR ($4k/ETH) + TCR ($200/ETH) } value={dn.format(dn.mul(data.tcr, 100n), 2) + "%"} @@ -326,13 +328,10 @@ function CardRow({ alignItems: "center", gap: 80, height: 32, + whiteSpace: "nowrap", })} > -
+
{name}
{value}
diff --git a/frontend/src/form-utils.ts b/frontend/src/form-utils.ts index 6f9c1446..431c91b8 100644 --- a/frontend/src/form-utils.ts +++ b/frontend/src/form-utils.ts @@ -7,6 +7,12 @@ export function isInputValueFloat(value: string) { return inputValueRegex.test(value); } +const inputIntRegex = /^[0-9]*$/; +export function isInputValueInt(value: string) { + value = value.trim(); + return inputIntRegex.test(value); +} + export function parseInputValue(value: string) { value = value.trim(); @@ -29,6 +35,11 @@ export function parseInputPercentage(value: string) { return dn.div(parsedValue, 100); } +export function parseInputInt(value: string) { + value = value.trim(); + return isInputValueInt(value) ? BigInt(value) : null; +} + export function parseInputAddress(value: string) { value = value.trim(); return isAddress(value) ? value : ADDRESS_ZERO; diff --git a/frontend/src/liquity-utils.ts b/frontend/src/liquity-utils.ts index 2140e360..3e3d5e74 100644 --- a/frontend/src/liquity-utils.ts +++ b/frontend/src/liquity-utils.ts @@ -1,8 +1,9 @@ -import type { Address } from "@/src/types"; +import type { Address, TroveId } from "@/src/types"; import type { Dnum } from "dnum"; import { BoldTokenContract, BorrowerOperationsContract, TroveManagerContract } from "@/src/contracts"; import { match } from "ts-pattern"; +import { encodeAbiParameters, keccak256, parseAbiParameters } from "viem"; import { useReadContract, useReadContracts, useWriteContract } from "wagmi"; type TroveStatus = @@ -21,7 +22,7 @@ type TroveDetails = { }; type LiquityInfo = { - trovesCount: number; + // trovesCount: number; totalCollateral: Dnum; totalDebt: Dnum; tcr: Dnum; @@ -45,43 +46,40 @@ function troveStatusFromNumber(value: number): TroveStatus { }); } -export function useTroveDetails(borrower: Address) { +export function useTroveDetails(troveId: TroveId = 0n) { const read = useReadContracts({ allowFailure: false, contracts: [ { ...TroveManagerContract, functionName: "getTroveStatus", - args: [borrower], + args: [troveId], }, { ...TroveManagerContract, functionName: "getTroveStake", - args: [borrower], + args: [troveId], }, { ...TroveManagerContract, functionName: "getTroveDebt", - args: [borrower], + args: [troveId], }, { ...TroveManagerContract, functionName: "getTroveColl", - args: [borrower], + args: [troveId], }, { ...TroveManagerContract, functionName: "getTroveAnnualInterestRate", - args: [borrower], + args: [troveId], }, ], }); if (!read.data || read.status !== "success") { - return { - ...read, - data: undefined, - }; + return { ...read, data: undefined }; } const troveStatusNumber = Number(read.data[0]); @@ -94,26 +92,25 @@ export function useTroveDetails(borrower: Address) { interestRate: [read.data[4] ?? 0n, 18], }; - return { - ...read, - data, - }; + return { ...read, data }; } -export function useOpenTrove(address: Address, { +export function useOpenTrove(owner: Address, { + ownerIndex, maxFeePercentage, boldAmount, upperHint, lowerHint, interestRate, - value, + ethAmount, }: { + ownerIndex: bigint; maxFeePercentage: bigint; // 0 to 1 * 10^18 boldAmount: bigint; // amount * 10^18 - upperHint: Address; - lowerHint: Address; + upperHint: bigint; + lowerHint: bigint; interestRate: bigint; // 0 to 1 * 10^18 - value: bigint; // amount * 10^18 + ethAmount: bigint; // amount * 10^18 }) { const { writeContract } = useWriteContract(); return () => ( @@ -121,26 +118,26 @@ export function useOpenTrove(address: Address, { ...BorrowerOperationsContract, functionName: "openTrove", args: [ + owner, + ownerIndex, maxFeePercentage, + ethAmount, boldAmount, upperHint, lowerHint, interestRate, ], - account: address, - value: value, }) ); } -export function useCloseTrove(address: Address) { +export function useCloseTrove(troveId: TroveId) { const { writeContract } = useWriteContract(); return () => ( writeContract({ ...BorrowerOperationsContract, functionName: "closeTrove", - args: [], - account: address, + args: [troveId], }) ); } @@ -153,19 +150,19 @@ export function useBoldBalance(address: Address) { }); } -export function useRewards(address: Address) { +export function useRewards(troveId: TroveId) { const read = useReadContracts({ allowFailure: false, contracts: [ { ...TroveManagerContract, functionName: "getPendingETHReward", - args: [address], + args: [troveId], }, { ...TroveManagerContract, functionName: "getPendingBoldDebtReward", - args: [address], + args: [troveId], }, ], }); @@ -192,11 +189,11 @@ export function useLiquity2Info() { const read = useReadContracts({ allowFailure: false, contracts: [ - { - ...TroveManagerContract, - functionName: "getTroveOwnersCount", - args: [], - }, + // { + // ...TroveManagerContract, + // functionName: "getTroveOwnersCount", + // args: [], + // }, { ...TroveManagerContract, functionName: "getEntireSystemColl", @@ -210,7 +207,7 @@ export function useLiquity2Info() { { ...TroveManagerContract, functionName: "getTCR", - args: [4_000n * 10n ** 18n], + args: [200n * 10n ** 18n], }, { ...TroveManagerContract, @@ -228,15 +225,19 @@ export function useLiquity2Info() { } const data: LiquityInfo = { - trovesCount: Number(read.data[0]), - totalCollateral: [read.data[1], 18], - totalDebt: [read.data[2], 18], - tcr: [read.data[3], 18], - redemptionRate: [read.data[4], 18], + // trovesCount: Number(read.data[0]), + totalCollateral: [read.data[0], 18], + totalDebt: [read.data[1], 18], + tcr: [read.data[2], 18], + redemptionRate: [read.data[3], 18], }; - return { - ...read, - data, - }; + return { ...read, data }; +} + +export function getTroveId(owner: Address, ownerIndex: bigint) { + return BigInt(keccak256(encodeAbiParameters( + parseAbiParameters("address, uint256"), + [owner, ownerIndex], + ))); } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index f64ddbb9..15bf5d6f 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1 +1,3 @@ export type Address = `0x${string}`; + +export type TroveId = bigint;