diff --git a/apps/app/src/app/(private-routes)/dashboard/degens/page.tsx b/apps/app/src/app/(private-routes)/dashboard/degens/page.tsx index c3e94a0e..452b8b84 100644 --- a/apps/app/src/app/(private-routes)/dashboard/degens/page.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/degens/page.tsx @@ -75,15 +75,15 @@ const DashboardDegensPage = (): JSX.Element => { const { loading: loadingAllRentals, data } = useFetch(`${DEGEN_BASE_API_URL}/cache/rentals/rentables.json`); - const { degensBalance, loadingDegens } = useNFTsBalances(); + const { degensBalances, loadingDegens } = useNFTsBalances(); const loading = loadingAllRentals || loadingDegens; const populatedDegens: Degen[] = useMemo(() => { - if (!degensBalance.length || !data) return []; + if (!degensBalances.length || !data) return []; // TODO: remove temp fix for 7th tribes // return degens.map((degen) => data[degen.id]); - return degensBalance.map(degen => + return degensBalances.map(degen => Number(degen.id) <= 9900 ? (data[Number(degen.id)] as Degen) : ({ @@ -108,7 +108,7 @@ const DashboardDegensPage = (): JSX.Element => { } as Degen), ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [degensBalance.length, !!data]); + }, [degensBalances.length, !!data]); const theme = useTheme(); const isScreenLg = useMediaQuery(theme.breakpoints.between('lg', 'xl')); @@ -320,7 +320,7 @@ const DashboardDegensPage = (): JSX.Element => { [...Array(8)].map(renderSkeletonItem) ) : dataForCurrentPage.length ? ( dataForCurrentPage.map(renderDegen) - ) : !degensBalance?.length ? ( + ) : !degensBalances?.length ? ( { [ currentPage, dataForCurrentPage, - degensBalance?.length, + degensBalances?.length, filteredData.length, isConnected, isDrawerOpen, diff --git a/apps/app/src/app/(private-routes)/dashboard/gamer-profile/page.tsx b/apps/app/src/app/(private-routes)/dashboard/gamer-profile/page.tsx index 0a8bd20d..1c8b4a41 100644 --- a/apps/app/src/app/(private-routes)/dashboard/gamer-profile/page.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/gamer-profile/page.tsx @@ -38,22 +38,22 @@ const GamerProfile = (): JSX.Element => { const { avatarsAndFee } = useProfileAvatarFee(); const { data } = useFetch(`${DEGEN_BASE_API_URL}/cache/rentals/rentables.json`); - const { comicsBalance, degenCount, degensBalance, itemsBalance } = useNFTsBalances(); + const { comicsBalances, degenCount, degensBalances, itemsBalances } = useNFTsBalances(); const filteredDegens: Degen[] = useMemo(() => { - if (degensBalance.length && data) { - const mapDegens = degensBalance.map(degen => data[Number(degen.id)]) as Degen[]; + if (degensBalances.length && data) { + const mapDegens = degensBalances.map(degen => data[Number(degen.id)]) as Degen[]; return mapDegens; } return []; - }, [degensBalance, data]); + }, [degensBalances, data]); const filteredComics = useMemo( - () => comicsBalance.filter(comic => comic.balance && comic.balance > 0), - [comicsBalance], + () => comicsBalances.filter(comic => comic.balance && comic.balance > 0), + [comicsBalances], ); - const filteredItems = useMemo(() => itemsBalance.filter(item => item.balance && item.balance > 0), [itemsBalance]); + const filteredItems = useMemo(() => itemsBalances.filter(item => item.balance && item.balance > 0), [itemsBalances]); const renderEmptyProfile = () => { return ( diff --git a/apps/app/src/app/(private-routes)/dashboard/items/burner/_components/comics-grid.tsx b/apps/app/src/app/(private-routes)/dashboard/items/burner/_components/comics-grid.tsx index 1a6d3c92..cfc892c7 100644 --- a/apps/app/src/app/(private-routes)/dashboard/items/burner/_components/comics-grid.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/items/burner/_components/comics-grid.tsx @@ -82,7 +82,7 @@ export default function ComicsGrid({ setSelectedComics: React.Dispatch>; refreshKey: number; }) { - const { comicsBalance, loadingComics } = useNFTsBalances(); + const { comicsBalances, loadingComics } = useNFTsBalances(); const keyCount = useMemo(() => (burnCount.some(v => v === 0) ? 0 : Math.min(...burnCount)), [burnCount]); const itemCount = useMemo(() => sum(burnCount) - keyCount * 6, [burnCount, keyCount]); @@ -98,7 +98,7 @@ export default function ComicsGrid({ if (removed) { newBurnCount[comic.id - 1] = 0; } else { - const comicCount = comicsBalance.find(c => c.id === comic.id)?.balance || 0; + const comicCount = comicsBalances.find(c => c.id === comic.id)?.balance || 0; newBurnCount[comic.id - 1] = comicCount; } setBurnCount(newBurnCount); @@ -122,7 +122,7 @@ export default function ComicsGrid({ ...gridStyles, }} > - {comicsBalance.map(comic => ( + {comicsBalances.map(comic => ( c.id === comic.id)?.balance || 0, + max: comicsBalances.find(c => c.id === comic.id)?.balance || 0, style: { textAlign: 'center', padding: 2.5, diff --git a/apps/app/src/app/(private-routes)/dashboard/items/burner/page.tsx b/apps/app/src/app/(private-routes)/dashboard/items/burner/page.tsx index b1c5d883..5f5f4c1b 100644 --- a/apps/app/src/app/(private-routes)/dashboard/items/burner/page.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/items/burner/page.tsx @@ -22,7 +22,7 @@ import ItemsGrid from './_components/items-grid'; const ComicsBurner = () => { const router = useRouter(); - const { itemsBalance } = useNFTsBalances(); + const { itemsBalances } = useNFTsBalances(); const { address, tx, writeContracts } = useNetworkContext(); const [isApprovedForAll, setIsApprovedForAll] = useState(false); const [helpDialogOpen, setHelpDialogOpen] = useState(false); @@ -34,10 +34,10 @@ const ComicsBurner = () => { const burnDisabled = burning || selectedComics.length < 1 || burnCount.every(c => !c); useEffect(() => { - if (itemsBalance.length) { - setItemsCounts(itemsBalance.map(it => it.balance || 0)); + if (itemsBalances.length) { + setItemsCounts(itemsBalances.map(it => it.balance || 0)); } - }, [itemsBalance]); + }, [itemsBalances]); useEffect(() => { const getAllowance = async () => { diff --git a/apps/app/src/app/(private-routes)/dashboard/items/page.tsx b/apps/app/src/app/(private-routes)/dashboard/items/page.tsx index b5dd07a5..92e3673c 100644 --- a/apps/app/src/app/(private-routes)/dashboard/items/page.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/items/page.tsx @@ -26,7 +26,7 @@ const DashboardComicsPage = (): JSX.Element => { const [selectedComic, setSelectedComic] = useState(null); const [selectedItem, setSelectedItem] = useState(null); const [selectedSubIndex, setSelectedSubIndex] = useState(-1); - const { comicsBalance, loadingComics, itemsBalance, loadingItems } = useNFTsBalances(); + const { comicsBalances, loadingComics, itemsBalances, loadingItems } = useNFTsBalances(); const router = useRouter(); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down('md')); @@ -68,14 +68,14 @@ const DashboardComicsPage = (): JSX.Element => { const handleLaunchBurner = () => router.push('items/burner'); const renderComics = useMemo(() => { - if (comicsBalance.length === 0 && loadingComics) { + if (comicsBalances.length === 0 && loadingComics) { return [...Array(6)].map(() => ( )); - } else if (comicsBalance.length > 0) { - return comicsBalance.map(comic => ( + } else if (comicsBalances.length > 0) { + return comicsBalances.map(comic => ( { )); } return null; - }, [comicsBalance, loadingComics, selectedComic]); + }, [comicsBalances, loadingComics, selectedComic]); const renderItems = useMemo(() => { - if (itemsBalance.length === 0 && loadingItems) { + if (itemsBalances.length === 0 && loadingItems) { return [...Array(6)].map(() => ( )); - } else if (itemsBalance.length > 0) { - return itemsBalance + } else if (itemsBalances.length > 0) { + return itemsBalances .filter(item => !selectedItem?.balance || selectedItem?.balance <= 1 || item.id !== selectedItem?.id) .map(item => ( @@ -110,7 +110,7 @@ const DashboardComicsPage = (): JSX.Element => { } return null; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [itemsBalance, loadingItems, selectedItem]); + }, [itemsBalances, loadingItems, selectedItem]); const renderSubItems = useMemo(() => { if (!selectedItem?.balance || selectedItem?.balance <= 1) return null; @@ -159,12 +159,12 @@ const DashboardComicsPage = (): JSX.Element => { onClick={removeComicSelection} > {renderComics} - {comicsBalance.length > 0 && ( + {comicsBalances.length > 0 && ( {}} - isNew={!comicsBalance.some(comic => comic.balance && comic.balance > 0)} + isNew={!comicsBalances.some(comic => comic.balance && comic.balance > 0)} /> @@ -212,10 +212,10 @@ const DashboardComicsPage = (): JSX.Element => { )} {renderItems} - {itemsBalance.length > 0 && ( + {itemsBalances.length > 0 && ( - {}} isNew={!itemsBalance.some(it => it.balance && it.balance > 0)} /> + {}} isNew={!itemsBalances.some(it => it.balance && it.balance > 0)} /> )} diff --git a/apps/app/src/app/(private-routes)/dashboard/overview/MyComics.tsx b/apps/app/src/app/(private-routes)/dashboard/overview/MyComics.tsx index cfb5239c..ed4f495a 100644 --- a/apps/app/src/app/(private-routes)/dashboard/overview/MyComics.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/overview/MyComics.tsx @@ -17,10 +17,10 @@ import { COMICS_PURCHASE_URL } from '@/constants/url'; const MyComics = (): JSX.Element => { const [selectedComic, setSelectedComic] = useState(null); const router = useRouter(); - const { comicsBalance, loadingComics } = useNFTsBalances(); + const { comicsBalances, loadingComics } = useNFTsBalances(); const filteredComics = useMemo( - () => comicsBalance.filter(comic => comic.balance && comic.balance > 0), - [comicsBalance], + () => comicsBalances.filter(comic => comic.balance && comic.balance > 0), + [comicsBalances], ); const handleViewComic = (comic: Comic) => { diff --git a/apps/app/src/app/(private-routes)/dashboard/overview/MyDegens.tsx b/apps/app/src/app/(private-routes)/dashboard/overview/MyDegens.tsx index a94ad357..56fa3d69 100644 --- a/apps/app/src/app/(private-routes)/dashboard/overview/MyDegens.tsx +++ b/apps/app/src/app/(private-routes)/dashboard/overview/MyDegens.tsx @@ -56,16 +56,16 @@ const MyDegens = (): JSX.Element => { } }, [favsData, setFavDegens]); - const { loadingDegens, degensBalance } = useNFTsBalances(); + const { loadingDegens, degensBalances } = useNFTsBalances(); const { data: degensData } = useFetch(`${DEGEN_BASE_API_URL}/cache/rentals/rentables.json`); const filteredDegens = useMemo(() => { - if (degensBalance.length && degensData) { - return degensBalance.map(degen => degensData[Number(degen.id)]).filter(Boolean); + if (degensBalances.length && degensData) { + return degensBalances.map(degen => degensData[Number(degen.id)]).filter(Boolean); } return []; - }, [degensBalance, degensData]) as Degen[]; + }, [degensBalances, degensData]) as Degen[]; const settings = { slidesToShow: 3, @@ -147,7 +147,7 @@ const MyDegens = (): JSX.Element => { return ( <> 0 && degensBalance.length > 0} + isSlider={filteredDegens.length > 0 && degensBalances.length > 0} firstSection title="My DEGENs" variant="h3" @@ -165,7 +165,7 @@ const MyDegens = (): JSX.Element => { )) - ) : filteredDegens.length && degensBalance.length ? ( + ) : filteredDegens.length && degensBalances.length ? ( filteredDegens.map(degen => ( { const router = useRouter(); - const { itemsBalance, loadingItems } = useNFTsBalances(); - const filteredItems = useMemo(() => itemsBalance.filter(item => item.balance && item.balance > 0), [itemsBalance]); + const { itemsBalances, loadingItems } = useNFTsBalances(); + const filteredItems = useMemo(() => itemsBalances.filter(item => item.balance && item.balance > 0), [itemsBalances]); const settings = { slidesToShow: 4, diff --git a/apps/app/src/components/dialog/DegenDialog/EquipDegenContentDialog.tsx b/apps/app/src/components/dialog/DegenDialog/EquipDegenContentDialog.tsx index 0f6dfbff..2eefcc3e 100644 --- a/apps/app/src/components/dialog/DegenDialog/EquipDegenContentDialog.tsx +++ b/apps/app/src/components/dialog/DegenDialog/EquipDegenContentDialog.tsx @@ -85,10 +85,10 @@ const initEquipped: boolean[] = new Array(6).fill(false); const EquipDegenContentDialog = ({ degen, name }: EquipDegenContentDialogProps) => { const dispatch = useDispatch(); - const { comicsBalance, loadingComics } = useNFTsBalances(); + const { comicsBalances, loadingComics } = useNFTsBalances(); const filteredComics = useMemo( - () => comicsBalance.filter(comic => comic.balance && comic.balance > 0), - [comicsBalance], + () => comicsBalances.filter(comic => comic.balance && comic.balance > 0), + [comicsBalances], ); const [animationType, setAnimationType] = useState('pose'); const [equipped, setEquipped] = useState(initEquipped); diff --git a/apps/app/src/constants/contracts/index.ts b/apps/app/src/constants/contracts/index.ts index b509c7b0..d8281629 100644 --- a/apps/app/src/constants/contracts/index.ts +++ b/apps/app/src/constants/contracts/index.ts @@ -1,4 +1,4 @@ -import type { InterfaceAbi } from 'ethers6'; +import type { Abi } from 'viem'; import type { Network } from '@/types/web3'; import { SEPOLIA_ID, MAINNET_ID } from '../networks'; import DEPLOYMENTS from './deployments'; @@ -7,7 +7,7 @@ export const getDeployedContract = (chainId: Network['chainId'], contractName: s const deployments = DEPLOYMENTS[chainId] as { [contractName: string]: { address: `0x${string}`; - abi: InterfaceAbi; + abi: Abi; }; }; return deployments[contractName]; @@ -18,7 +18,7 @@ export const getContractAddress = (chainId: Network['chainId'], contractName: st }; export const getContractABI = (chainId: Network['chainId'], contractName: string) => { - return getDeployedContract(chainId, contractName)?.abi as InterfaceAbi; + return getDeployedContract(chainId, contractName)?.abi as Abi; }; // Ethereum contracts diff --git a/apps/app/src/contexts/NFTsBalanceContext.tsx b/apps/app/src/contexts/NFTsBalanceContext.tsx index 5efcc786..3d3e4784 100644 --- a/apps/app/src/contexts/NFTsBalanceContext.tsx +++ b/apps/app/src/contexts/NFTsBalanceContext.tsx @@ -7,34 +7,37 @@ import type { Comic, Item } from '@/types/marketplace'; import { useOwnerSearch } from '@/hooks/useGraphQL'; import useAuth from '@/hooks/useAuth'; -import useComicsBalance from '@/hooks/balances/useComicsBalance'; -import useIMXContext from '@/hooks/useIMXContext'; -import useItemsBalance from '@/hooks/balances/useItemsBalance'; +import useComicsBalances from '@/hooks/balances/useComicsBalances'; +import useItemsBalances from '@/hooks/balances/useItemsBalances'; interface NFTsBalanceContext { - comicsBalance: Comic[]; + comicsBalances: Comic[]; degenCount: number; - degensBalance: Character[]; + degensBalances: Character[]; degenTokenIndices: number[]; isDegenOwner: boolean; - itemsBalance: Item[]; + itemsBalances: Item[]; loadingComics: boolean; loadingDegens: boolean; loadingItems: boolean; - refreshDegenBalance: () => void; + refreshComicsBalances: () => void; + refreshDegenBalances: () => void; + refreshItemsBalances: () => void; } const CONTEXT_INITIAL_STATE: NFTsBalanceContext = { - comicsBalance: [], + comicsBalances: [], degenCount: 0, - degensBalance: [], + degensBalances: [], degenTokenIndices: [], isDegenOwner: false, - itemsBalance: [], + itemsBalances: [], loadingComics: true, loadingDegens: false, loadingItems: true, - refreshDegenBalance: () => {}, + refreshComicsBalances: () => {}, + refreshDegenBalances: () => {}, + refreshItemsBalances: () => {}, }; const NFTsBalanceContext = createContext(CONTEXT_INITIAL_STATE); @@ -42,23 +45,22 @@ const NFTsBalanceContext = createContext(CONTEXT_INITIAL_STA export const NFTsBalanceProvider = ({ children }: PropsWithChildren): JSX.Element => { const firstRenderRef = useRef(true); const { isLoggedIn } = useAuth(); - const { address, imxContracts } = useIMXContext(); // Load user DEGEN balances from Subgraph - const { isFetching, data: owner, refetch: refreshDegenBalance } = useOwnerSearch(); + const { isFetching, data: owner, refetch: refreshDegenBalances } = useOwnerSearch(); const { characterCount: degenCount = 0 } = owner || {}; const isDegenOwner = degenCount > 0; - const degensBalance = useMemo(() => { + const degensBalances = useMemo(() => { const characterList = owner?.characters ? [...owner.characters] : []; return characterList.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10)); }, [owner]); - const degenTokenIndices = useMemo(() => degensBalance.map(d => parseInt(d.id, 10)), [degensBalance]); + const degenTokenIndices = useMemo(() => degensBalances.map(d => parseInt(d.id, 10)), [degensBalances]); // Load user Immutable zkEVM NFT balances - const { comicsBalance, loading: loadingComics } = useComicsBalance(imxContracts, address); - const { itemsBalance, loading: loadingItems } = useItemsBalance(imxContracts, address); + const { balances: comicsBalances, loading: loadingComics, refetch: refreshComicsBalances } = useComicsBalances(); + const { balances: itemsBalances, loading: loadingItems, refetch: refreshItemsBalances } = useItemsBalances(); // Refetch on login state change, avoiding initial render useEffect(() => { @@ -67,23 +69,27 @@ export const NFTsBalanceProvider = ({ children }: PropsWithChildren): JSX.Elemen return; } if (!isLoggedIn) return; - refreshDegenBalance(); + refreshComicsBalances(); + refreshDegenBalances(); + refreshItemsBalances(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoggedIn]); return ( {children} diff --git a/apps/app/src/hooks/balances/useClaimableNFTL.ts b/apps/app/src/hooks/balances/useClaimableNFTL.ts index c25a307c..6bf5aec5 100644 --- a/apps/app/src/hooks/balances/useClaimableNFTL.ts +++ b/apps/app/src/hooks/balances/useClaimableNFTL.ts @@ -32,6 +32,7 @@ export default function useClaimableNFTL(degenTokenIndices: number[]): NFTLClaim const { data, error, isLoading, refetch } = useReadContract({ address: NFTL_CONTRACT?.address as `0x${string}`, abi: NFTL_CONTRACT?.abi as Abi, + chainId: TARGET_NETWORK.chainId, functionName: 'accumulatedMultiCheck', args: [degenTokenIndices], query: { diff --git a/apps/app/src/hooks/balances/useComicsBalance.ts b/apps/app/src/hooks/balances/useComicsBalance.ts deleted file mode 100644 index 535938be..00000000 --- a/apps/app/src/hooks/balances/useComicsBalance.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import type { AddressLike } from 'ethers6'; -import type { Contracts } from '@/types/web3'; -import type { Comic } from '@/types/marketplace'; -import { MARKETPLACE_CONTRACT } from '@/constants/contracts'; -import { COMICS } from '@/constants/marketplace'; - -/* - ~ What it does? ~ - - Gets your Comics NFTs balance from Immutable zkEVM - - ~ How can I use? ~ - - const { comicsBalance } = useComicsBalance(imxContracts, address); -*/ - -export default function useComicsBalance( - imxContracts: Contracts, - address?: `0x${string}`, - refreshKey = 0, -): { - comicsBalance: Comic[]; - loading: boolean; -} { - const [loading, setLoading] = useState(true); - const [comicsBalance, setComicsBal] = useState([]); - const marketplaceContract = useMemo(() => imxContracts[MARKETPLACE_CONTRACT], [imxContracts]); - - useEffect(() => { - async function checkUserComics() { - const ownerArr = [address, address, address, address, address, address] as AddressLike[]; - const comicIds = [1, 2, 3, 4, 5, 6]; - const comicsData = await marketplaceContract.balanceOfBatch(ownerArr, comicIds); - - if (comicsData.some((c: bigint) => c > 0)) { - setComicsBal( - comicsData.map((c: bigint, i: number) => ({ - ...(COMICS[i] as Comic), - balance: Number(c), - })), - ); - } - setLoading(false); - } - - if (address && marketplaceContract) { - // eslint-disable-next-line no-void - void checkUserComics(); - } - }, [address, marketplaceContract, refreshKey]); - - return { comicsBalance, loading }; -} diff --git a/apps/app/src/hooks/balances/useComicsBalances.ts b/apps/app/src/hooks/balances/useComicsBalances.ts new file mode 100644 index 00000000..ac6b2817 --- /dev/null +++ b/apps/app/src/hooks/balances/useComicsBalances.ts @@ -0,0 +1,78 @@ +'use client'; + +import { useMemo } from 'react'; +import { useAccount, useReadContract, type Config } from 'wagmi'; +import type { Abi } from 'viem'; +import type { AddressLike, BigNumberish } from 'ethers6'; +import type { Comic } from '@/types/marketplace'; + +import { getDeployedContract, MARKETPLACE_CONTRACT } from '@/constants/contracts'; +import { COMICS } from '@/constants/marketplace'; +import useAuth from '@/hooks/useAuth'; +import useIMXContext from '@/hooks/useIMXContext'; + +/* + ~ What it does? ~ + + Gets your Comics NFTs balances from Immutable zkEVM + + ~ How can I use? ~ + + const { balances, error, loading, refetch } = useComicsBalances(); +*/ + +const COMICS_IDS = [1, 2, 3, 4, 5, 6]; + +type ComicsBalancesState = { + balances: Comic[]; + error: Error | null; + loading: boolean; + refetch: () => void; +}; + +type BalanceOfBatch = { + args: [AddressLike[], BigNumberish[]]; + result: bigint[]; +}; + +type UseReadContractParams = { + abi: Abi; + functionName: 'balanceOfBatch'; + args: BalanceOfBatch['args']; + config: Config; + result: BalanceOfBatch['result']; +}; + +export default function useComicsBalances(): ComicsBalancesState { + const { isLoggedIn } = useAuth(); + const { address, isConnected } = useAccount(); + const { imxChainId } = useIMXContext(); + + const marketplaceContract = getDeployedContract(imxChainId, MARKETPLACE_CONTRACT); + const ownerArr = useMemo(() => Array(COMICS_IDS.length).fill(address) as AddressLike[], [address]); + + const { data, error, isLoading, refetch } = useReadContract< + UseReadContractParams['abi'], + UseReadContractParams['functionName'], + UseReadContractParams['args'], + UseReadContractParams['config'], + UseReadContractParams['result'] + >({ + address: marketplaceContract?.address, + abi: marketplaceContract?.abi, + chainId: imxChainId, + functionName: 'balanceOfBatch', + args: [ownerArr, COMICS_IDS], + query: { + staleTime: 10_000, + enabled: isConnected && isLoggedIn, + }, + }); + + const balances = useMemo( + () => (data ? data.map((c: bigint, i: number) => ({ ...(COMICS[i] as Comic), balance: Number(c) })) : []), + [data], + ); + + return { balances, error, loading: isLoading, refetch }; +} diff --git a/apps/app/src/hooks/balances/useItemsBalance.ts b/apps/app/src/hooks/balances/useItemsBalance.ts deleted file mode 100644 index 390ac045..00000000 --- a/apps/app/src/hooks/balances/useItemsBalance.ts +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import type { AddressLike } from 'ethers6'; -import type { Contracts } from '@/types/web3'; -import type { Item } from '@/types/marketplace'; -import { MARKETPLACE_CONTRACT } from '@/constants/contracts'; -import { ITEMS } from '@/constants/marketplace'; - -/* - ~ What it does? ~ - - Gets your Items NFTs balance from Immutable zkEVM - - ~ How can I use? ~ - - const { itemsBalance } = useItemsBalance(imxContracts, address); -*/ - -export default function useItemsBalance( - imxContracts: Contracts, - address?: `0x${string}`, - refreshKey = 0, -): { - itemsBalance: Item[]; - loading: boolean; -} { - const [loading, setLoading] = useState(true); - const [itemsBalance, setItemsBal] = useState([]); - const marketplaceContract = useMemo(() => imxContracts[MARKETPLACE_CONTRACT], [imxContracts]); - - useEffect(() => { - async function checkUserItems() { - const ownerArr = [address, address, address, address, address, address, address] as AddressLike[]; - const itemIds = [101, 102, 103, 104, 105, 106, 107]; - const itemsData = await marketplaceContract.balanceOfBatch(ownerArr, itemIds); - - if (itemsData.some((c: bigint) => c > 0)) { - setItemsBal( - itemsData.map((c: bigint, i: number) => ({ - ...(ITEMS[i] as Item), - balance: Number(c), - })), - ); - } - setLoading(false); - } - - if (address && marketplaceContract) { - // eslint-disable-next-line no-void - void checkUserItems(); - } - }, [address, marketplaceContract, refreshKey]); - - return { itemsBalance, loading }; -} diff --git a/apps/app/src/hooks/balances/useItemsBalances.ts b/apps/app/src/hooks/balances/useItemsBalances.ts new file mode 100644 index 00000000..36c827d9 --- /dev/null +++ b/apps/app/src/hooks/balances/useItemsBalances.ts @@ -0,0 +1,78 @@ +'use client'; + +import { useMemo } from 'react'; +import { useAccount, useReadContract, type Config } from 'wagmi'; +import type { Abi } from 'viem'; +import type { AddressLike, BigNumberish } from 'ethers6'; +import type { Item } from '@/types/marketplace'; + +import { getDeployedContract, MARKETPLACE_CONTRACT } from '@/constants/contracts'; +import { ITEMS } from '@/constants/marketplace'; +import useAuth from '@/hooks/useAuth'; +import useIMXContext from '@/hooks/useIMXContext'; + +/* + ~ What it does? ~ + + Gets your Items NFTs balances from Immutable zkEVM + + ~ How can I use? ~ + + const { balances, error, loading, refetch } = useItemsBalance(); +*/ + +const ITEM_IDS = [101, 102, 103, 104, 105, 106, 107]; + +type ItemsBalancesState = { + balances: Item[]; + error: Error | null; + loading: boolean; + refetch: () => void; +}; + +type BalanceOfBatch = { + args: [AddressLike[], BigNumberish[]]; + result: bigint[]; +}; + +type UseReadContractParams = { + abi: Abi; + functionName: 'balanceOfBatch'; + args: BalanceOfBatch['args']; + config: Config; + result: BalanceOfBatch['result']; +}; + +export default function useItemssBalances(): ItemsBalancesState { + const { isLoggedIn } = useAuth(); + const { address, isConnected } = useAccount(); + const { imxChainId } = useIMXContext(); + + const marketplaceContract = getDeployedContract(imxChainId, MARKETPLACE_CONTRACT); + const ownerArr = useMemo(() => Array(ITEM_IDS.length).fill(address) as AddressLike[], [address]); + + const { data, error, isLoading, refetch } = useReadContract< + UseReadContractParams['abi'], + UseReadContractParams['functionName'], + UseReadContractParams['args'], + UseReadContractParams['config'], + UseReadContractParams['result'] + >({ + address: marketplaceContract?.address, + abi: marketplaceContract?.abi, + chainId: imxChainId, + functionName: 'balanceOfBatch', + args: [ownerArr, ITEM_IDS], + query: { + staleTime: 10_000, + enabled: isConnected && isLoggedIn, + }, + }); + + const balances = useMemo( + () => (data ? data.map((c: bigint, i: number) => ({ ...(ITEMS[i] as Item), balance: Number(c) })) : []), + [data], + ); + + return { balances, error, loading: isLoading, refetch }; +} diff --git a/apps/app/src/hooks/useNFTLAllowance.ts b/apps/app/src/hooks/useNFTLAllowance.ts index 07687011..a2c54e84 100644 --- a/apps/app/src/hooks/useNFTLAllowance.ts +++ b/apps/app/src/hooks/useNFTLAllowance.ts @@ -25,6 +25,7 @@ export default function useNFTLAllowance(contractAddress: `0x${string}`): NFTLAl } = useReadContract({ address: NFTL_CONTRACT?.address as `0x${string}`, abi: NFTL_CONTRACT?.abi as Abi, + chainId: TARGET_NETWORK.chainId, functionName: 'allowance', args: [contractAddress], query: {