Create Hooks
Create custom React hooks for fetching data and executing transactions.Create Hooks Folder
Copy
mkdir src/hooks
1. useUsdcBalance Hook
Createsrc/hooks/use-usdc-balance.ts:
Copy
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
Createsrc/hooks/use-mint-usdc.ts:
Copy
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
Createsrc/hooks/use-owned-nfts.ts:
Copy
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
Createsrc/hooks/use-mint-nft.ts:
Copy
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
| Hook | Purpose |
|---|---|
useUsdcBalance | Fetch USDC balance (auto-refreshes) |
useMintUsdc | Mint USDC tokens |
useOwnedNfts | Fetch owned NFTs |
useMintNft | Mint NFT with USDC payment |
Next Steps
Build Components
Create the UI components