UNPKG

@starknet-react/core

Version:

Starknet React is a collection of React hooks for Starknet.

224 lines (199 loc) 4.75 kB
import type { Address, Chain } from "@starknet-react/chains"; import { useMemo } from "react"; import { type BlockNumber, BlockTag, type CallOptions, num, shortString, } from "starknet"; import { formatUnits } from "viem"; import { type UseQueryProps, type UseQueryResult, useQuery } from "../query"; import { type StarknetTypedContract, useContract } from "./use-contract"; import { useInvalidateOnBlock } from "./use-invalidate-on-block"; import { useNetwork } from "./use-network"; const DEFAULT_FETCH_INTERVAL = 5_000; export type Balance = { decimals: number; symbol: string; formatted: string; value: bigint; }; export type UseBalanceProps = UseQueryProps< Balance, Error, Balance, ReturnType<typeof queryKey> > & { /** The contract's address. Defaults to the native currency. */ token?: Address; /** The address to fetch balance for. */ address?: Address; /** Whether to watch for changes. */ watch?: boolean; /** Block identifier used when performing call. */ blockIdentifier?: BlockNumber; }; export type UseBalanceResult = UseQueryResult<Balance, Error>; type TAbi = typeof balanceABIFragment; type Contract = StarknetTypedContract<TAbi>; /** * Fetch the balance for the provided address and token. * * If no token is provided, the native currency is used. */ export function useBalance({ token: token_, address, refetchInterval: refetchInterval_, watch = false, enabled: enabled_ = true, blockIdentifier = BlockTag.LATEST, ...props }: UseBalanceProps) { const { chain } = useNetwork(); const token = token_ ?? chain.nativeCurrency.address; const { contract } = useContract({ abi: balanceABIFragment, address: token, }); const queryKey_ = useMemo( () => queryKey({ chain, token, address, blockIdentifier }), [chain, token, address, blockIdentifier], ); const enabled = useMemo( () => Boolean(enabled_ && contract && address), [enabled_, contract, address], ); const refetchInterval = refetchInterval_ ?? (blockIdentifier === BlockTag.PENDING && watch ? DEFAULT_FETCH_INTERVAL : undefined); useInvalidateOnBlock({ enabled: Boolean(enabled && watch), queryKey: queryKey_, }); return useQuery({ enabled, refetchInterval, queryKey: queryKey_, queryFn: queryFn({ chain, contract, token, address, blockIdentifier }), ...props, }); } function queryKey({ chain, token, address, blockIdentifier, }: { chain: Chain; token?: string; address?: string; blockIdentifier?: BlockNumber; }) { return [ { entity: "balance", chainId: chain?.name, token, address, blockIdentifier, }, ] as const; } function queryFn({ chain, token, address, contract, blockIdentifier, }: { chain: Chain; token?: string; address?: string; contract?: Contract; blockIdentifier?: BlockNumber; }) { return async () => { if (!address) throw new Error("address is required"); if (!contract) throw new Error("contract is required"); const options: CallOptions = { blockIdentifier, }; const isNativeCurrency = token === chain.nativeCurrency.address; let symbol = chain.nativeCurrency.symbol; if (!isNativeCurrency) { const symbol_ = await contract.symbol(options); symbol = shortString.decodeShortString(num.toHex(symbol_)); } let decimals = chain.nativeCurrency.decimals; if (!isNativeCurrency) { const decimals_ = await contract.decimals(options); decimals = Number(decimals_); } const balanceOf = (await contract.balanceOf(address, options)) as bigint; const formatted = formatUnits(balanceOf, decimals); return { value: balanceOf, decimals: decimals, symbol: symbol, formatted: formatted, }; }; } const balanceABIFragment = [ { name: "core::integer::u256", type: "struct", members: [ { name: "low", type: "core::integer::u128", }, { name: "high", type: "core::integer::u128", }, ], }, { name: "balanceOf", type: "function", inputs: [ { name: "account", type: "core::starknet::contract_address::ContractAddress", }, ], outputs: [ { type: "core::integer::u256", }, ], state_mutability: "view", }, { name: "symbol", type: "function", inputs: [], outputs: [ { type: "core::felt252", }, ], state_mutability: "view", }, { name: "decimals", type: "function", inputs: [], outputs: [ { type: "core::integer::u8", }, ], state_mutability: "view", }, ] as const;