import { useReadContracts } from "wagmi" import { useCallback, useMemo } from "react" import { useQueryClient } from "@tanstack/react-query" import { formatUnits } from "viem" import { mainnetBorrowContracts, testnetBorrowContracts, } from "../lib/contracts" import { usePassportContext } from "./usePassportContext" import { CHAIN_ID } from "../constants" import { bigIntMax, normalizePrecision } from "../utils/numbers" import { useWalletAccount } from "./useWalletAccount" import { getAsset } from "../utils/assets" import { convertToUsd } from "../utils/currency" import { useCollateralPrice } from "./useCollateralPrice" const DEBT_AND_COLL_PRECISION = 18 // Wagmi handles typesafety with ABI const assertions. TypeScript doesn't // support importing JSON as const yet so types cannot be inferred from the // imported contract. As a workaround there is minimal ABI definition that can // be asserted types from. // Ref: https://wagmi.sh/core/typescript#const-assert-abis-typed-data const TROVE_MANAGER_ABI = [ { inputs: [ { internalType: "address", name: "_borrower", type: "address", }, ], name: "getEntireDebtAndColl", outputs: [ { internalType: "uint256", name: "coll", type: "uint256", }, { internalType: "uint256", name: "principal", type: "uint256", }, { internalType: "uint256", name: "interest", type: "uint256", }, { internalType: "uint256", name: "pendingCollateral", type: "uint256", }, { internalType: "uint256", name: "pendingPrincipal", type: "uint256", }, { internalType: "uint256", name: "pendingInterest", type: "uint256", }, ], stateMutability: "view", type: "function", }, ] as const const BORROWER_OPERATIONS_ABI = [ { inputs: [], name: "MUSD_GAS_COMPENSATION", outputs: [ { internalType: "uint256", name: "", type: "uint256", }, ], stateMutability: "view", type: "function", }, ] as const const BORROW_SCOPE_KEY = "borrowData" /** * Query hook for getting borrow data. Returns collateral and trove debt for the * connected account, based on it's evm address. * @param queryOptions Query options passed to the underlying `useQuery` hook. */ export function useBorrowData(queryOptions = {}) { const { environment = "mainnet", borrowDataRefetchInterval } = usePassportContext() const walletAccount = useWalletAccount() const { data: collateralPrice } = useCollateralPrice() const contractAddress = useMemo(() => { if (environment === "mainnet") { return { troveManager: mainnetBorrowContracts.TroveManager.address, borrowerOperations: mainnetBorrowContracts.BorrowerOperations.address, } } return { troveManager: testnetBorrowContracts.TroveManager.address, borrowerOperations: testnetBorrowContracts.BorrowerOperations.address, } }, [environment]) const chainId = CHAIN_ID[environment] return useReadContracts({ scopeKey: BORROW_SCOPE_KEY, contracts: [ { abi: TROVE_MANAGER_ABI, address: contractAddress.troveManager, functionName: "getEntireDebtAndColl", args: walletAccount.accountAddress ? [walletAccount.accountAddress] : ["0x" as `0x${string}}`], chainId, }, { abi: BORROWER_OPERATIONS_ABI, address: contractAddress.borrowerOperations, functionName: "MUSD_GAS_COMPENSATION", chainId, }, ], query: { enabled: !!walletAccount.accountAddress && !!collateralPrice, retry: 1, select: (data) => { if (!data || data.some((item) => !item.result)) throw new Error("No borrow data available") if (!collateralPrice) throw new Error("Collateral price not available") const [rawCollateral, principal, interest] = data[0].result! const gasCompensation = data[1].result! const btcDetails = getAsset("BTC") const collateral = { value: rawCollateral, decimals: btcDetails.decimals, symbol: btcDetails.symbol, formatted: formatUnits(rawCollateral, DEBT_AND_COLL_PRECISION), usd: convertToUsd( rawCollateral, DEBT_AND_COLL_PRECISION, collateralPrice, DEBT_AND_COLL_PRECISION, ), } const debtInUsdValue = bigIntMax( normalizePrecision( principal + interest - gasCompensation, DEBT_AND_COLL_PRECISION, ), 0n, // Ensure that debt is not negative ) const formattedDebtInUsd = formatUnits( debtInUsdValue, DEBT_AND_COLL_PRECISION, ) const debtInUsd = { value: debtInUsdValue, formatted: formattedDebtInUsd, } return { collateral, debtInUsd, } }, staleTime: borrowDataRefetchInterval, refetchInterval: borrowDataRefetchInterval, ...queryOptions, }, }) } /** * Hook for for invalidating current user's borrow data. Can be used to * invalidate borrow data manually, which forces the data to be re-fetched. * @returns Function `invalidateBorrowData` for invalidating the borrow data */ export function useInvalidateBorrowData() { const queryClient = useQueryClient() const invalidateBorrowData = useCallback( () => queryClient.invalidateQueries({ queryKey: ["readContracts", { scopeKey: BORROW_SCOPE_KEY }], }), [queryClient], ) return { invalidateBorrowData } } /** * Hook for for resetting current user's borrow data. Can be used to reset * borrow data manually, which forces the data to be re-fetched. * @returns Function `resetBorrowData` for resetting the borrow data */ export function useResetBorrowData() { const queryClient = useQueryClient() const resetBorrowData = useCallback( () => queryClient.resetQueries({ queryKey: ["readContracts", { scopeKey: BORROW_SCOPE_KEY }], }), [queryClient], ) return { resetBorrowData } }