Skip to main content

Create Hooks

Create custom React hooks for fetching data and executing transactions.

Create Hooks Folder

mkdir src/hooks

1. useUsdcBalance Hook

Create src/hooks/use-usdc-balance.ts:
import { useCurrentAccount, useCurrentClient } from "@mysten/dapp-kit-react";
import { useQuery } from "@tanstack/react-query";
import { USDC_TYPE } from "@/lib/contracts";

export function useUsdcBalance() {
  const account = useCurrentAccount();
  const client = useCurrentClient();

  return useQuery({
    queryKey: ["usdc-balance", account?.address],
    queryFn: async () => {
      if (!account?.address) return { balance: 0n };
      const { objects } = await client.listCoins({
        owner: account.address,
        coinType: USDC_TYPE,
      });

      const balance = objects.reduce((sum, coin) => {
        const bal = (coin as { balance?: string | bigint }).balance;
        return sum + BigInt(String(bal ?? 0));
      }, 0n);

      return { balance };
    },
    enabled: !!account?.address,
    staleTime: 0,
    refetchInterval: 3000,
  });
}

2. useMintUsdc Hook

Create src/hooks/use-mint-usdc.ts:
import { useState } from "react";
import { useCurrentAccount, useDAppKit } from "@mysten/dapp-kit-react";
import { useQueryClient } from "@tanstack/react-query";
import { Transaction } from "@mysten/sui/transactions";
import { CONTRACTS, USDC_MINT_AMOUNT, parseUSDC } from "@/lib/contracts";

export function useMintUsdc() {
  const account = useCurrentAccount();
  const { signAndExecuteTransaction } = useDAppKit();
  const queryClient = useQueryClient();
  const [isPending, setIsPending] = useState(false);

  const mint = async () => {
    if (!account?.address) return;

    setIsPending(true);
    try {
      const tx = new Transaction();
      tx.moveCall({
        target: `${CONTRACTS.PACKAGE_ID}::usdc::mint_to`,
        arguments: [
          tx.object(CONTRACTS.USDC_TREASURY_CAP),
          tx.pure.u64(parseUSDC(USDC_MINT_AMOUNT)),
          tx.pure.address(account.address),
        ],
      });

      await signAndExecuteTransaction({ transaction: tx });
      queryClient.invalidateQueries({ queryKey: ["usdc-balance"] });
    } catch (error) {
      console.error("Mint USDC failed:", error);
    } finally {
      setIsPending(false);
    }
  };

  return { mint, isPending };
}

3. useOwnedNfts Hook

Create src/hooks/use-owned-nfts.ts:
import { useCurrentAccount, useCurrentClient } from "@mysten/dapp-kit-react";
import { useQuery } from "@tanstack/react-query";
import { NFT_TYPE } from "@/lib/contracts";

export interface PSILNFT {
  id: string;
  name: string;
  description: string;
  url: string;
  edition: number;
}

export function useOwnedNfts() {
  const account = useCurrentAccount();
  const client = useCurrentClient();

  return useQuery({
    queryKey: ["owned-nfts", account?.address],
    queryFn: async (): Promise<PSILNFT[]> => {
      if (!account?.address) return [];

      const { objects } = await client.listOwnedObjects({
        owner: account.address,
        type: NFT_TYPE,
        include: { json: true },
      });

      return objects
        .map((obj) => {
          const json = obj.json as Record<string, unknown> | null;
          if (!json) return null;
          return {
            id: obj.objectId || "",
            name: String(json.name || ""),
            description: String(json.description || ""),
            url: String(json.url || ""),
            edition: Number(json.edition || 0),
          };
        })
        .filter((nft): nft is PSILNFT => nft !== null);
    },
    enabled: !!account?.address,
    staleTime: 5000,
  });
}

4. useMintNft Hook

Create src/hooks/use-mint-nft.ts:
import { useState } from "react";
import { useCurrentAccount, useDAppKit } from "@mysten/dapp-kit-react";
import { useQueryClient } from "@tanstack/react-query";
import { Transaction } from "@mysten/sui/transactions";
import { CONTRACTS, NFT_MINT_PRICE, USDC_TYPE } from "@/lib/contracts";

export function useMintNft() {
  const account = useCurrentAccount();
  const { getClient, signAndExecuteTransaction } = useDAppKit();
  const queryClient = useQueryClient();
  const [isPending, setIsPending] = useState(false);

  const mint = async () => {
    if (!account?.address) return;

    setIsPending(true);
    try {
      const client = getClient();
      const { objects: coins } = await client.listCoins({
        owner: account.address,
        coinType: USDC_TYPE,
      });

      if (!coins.length) return;

      const tx = new Transaction();
      const [primaryCoin, ...otherCoins] = coins;

      // Merge all USDC coins if needed
      if (otherCoins.length > 0) {
        tx.mergeCoins(
          tx.object(primaryCoin.objectId),
          otherCoins.map((c: { objectId: string }) => tx.object(c.objectId))
        );
      }

      // Split exact payment amount
      const [paymentCoin] = tx.splitCoins(tx.object(primaryCoin.objectId), [
        tx.pure.u64(NFT_MINT_PRICE),
      ]);

      // Call mint function
      tx.moveCall({
        target: `${CONTRACTS.PACKAGE_ID}::psil_nft::mint`,
        arguments: [tx.object(CONTRACTS.NFT_TREASURY), paymentCoin],
      });

      await signAndExecuteTransaction({ transaction: tx });
      queryClient.invalidateQueries({ queryKey: ["usdc-balance"] });
      queryClient.invalidateQueries({ queryKey: ["owned-nfts"] });
    } catch (error) {
      console.error("Mint NFT failed:", error);
    } finally {
      setIsPending(false);
    }
  };

  return { mint, isPending };
}

Hook Summary

HookPurpose
useUsdcBalanceFetch USDC balance (auto-refreshes)
useMintUsdcMint USDC tokens
useOwnedNftsFetch owned NFTs
useMintNftMint NFT with USDC payment

Next Steps

Build Components

Create the UI components