skills/frontend-web3-bap578/SKILL.md
Use this skill when integrating BAP-578 smart contract reads or writes into a Next.js or React frontend, including wallet connection, chain setup, ABI wiring, hooks, minting flows, and agent-management UI.
npx skillsauth add chatandbuild/chatchat-skills Frontend Web3 for BAP-578Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill to integrate BAP-578 smart contract reads and writes into a Next.js/React frontend. This skill covers everything from wallet connection through to building production-quality agent management UIs.
The UI renders the agent's on-chain identity by reading getAgentState and getAgentMetadata. Display the token ID, owner address, persona (parsed from JSON), experience text, and logic address. Show active/inactive status with a visual indicator (green dot for active, grey for inactive). If a logic contract is bound, display the address with an indicator that behavior is externally defined.
The frontend reads structured metadata from the contract and optionally fetches extended memory from vaultURI. To verify vault integrity, the frontend should:
vaultURI.keccak256 of the raw content using viem's keccak256 utility.vaultHash.Display on-chain event history by querying past events filtered by token ID: AgentCreated, AgentFunded, AgentWithdraw, AgentStatusChanged, MetadataUpdated.
The UI exposes these agent capabilities as interactive forms and buttons:
fundAgentwithdrawFromAgentsetAgentStatussetLogicAddressThe frontend only signs transactions through the user's own wallet (MetaMask, WalletConnect, etc.). There is no backend mutation path. Every write goes directly to the on-chain contract. The UI should:
npm install wagmi viem @tanstack/react-query @rainbow-me/rainbowkit
Create config/web3.ts:
import { http } from "wagmi";
import { bsc, bscTestnet } from "wagmi/chains";
import { getDefaultConfig } from "@rainbow-me/rainbowkit";
export const wagmiConfig = getDefaultConfig({
appName: "NFA",
projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || "",
chains: [bsc, bscTestnet],
transports: {
[bsc.id]: http(process.env.NEXT_PUBLIC_BSC_RPC_URL),
[bscTestnet.id]: http(process.env.NEXT_PUBLIC_BSC_TESTNET_RPC_URL),
},
ssr: true,
});
Required env vars in .env.local:
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
NEXT_PUBLIC_BSC_RPC_URL=your_bsc_rpc
NEXT_PUBLIC_BSC_TESTNET_RPC_URL=your_bsc_testnet_rpc
NEXT_PUBLIC_BAP578_ADDRESS=0xYourDeployedAddress
Create components/Web3Provider.tsx:
"use client";
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { wagmiConfig } from "@/config/web3";
import "@rainbow-me/rainbowkit/styles.css";
const queryClient = new QueryClient();
export function Web3Provider({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>{children}</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}
Add <Web3Provider> to your root layout wrapping {children}.
Create config/bap578.ts:
export const BAP578_ADDRESS = process.env.NEXT_PUBLIC_BAP578_ADDRESS as `0x${string}`;
export const BAP578_ABI = [
// Minting
{
name: "createAgent",
type: "function",
stateMutability: "payable",
inputs: [
{ name: "to", type: "address" },
{ name: "logicAddress", type: "address" },
{ name: "metadataURI", type: "string" },
{
name: "metadata",
type: "tuple",
components: [
{ name: "persona", type: "string" },
{ name: "experience", type: "string" },
{ name: "voiceHash", type: "string" },
{ name: "animationURI", type: "string" },
{ name: "vaultURI", type: "string" },
{ name: "vaultHash", type: "bytes32" },
],
},
],
outputs: [{ name: "tokenId", type: "uint256" }],
},
// Funding
{
name: "fundAgent",
type: "function",
stateMutability: "payable",
inputs: [{ name: "tokenId", type: "uint256" }],
outputs: [],
},
// Withdrawal
{
name: "withdrawFromAgent",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "tokenId", type: "uint256" },
{ name: "amount", type: "uint256" },
],
outputs: [],
},
// Status
{
name: "setAgentStatus",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "tokenId", type: "uint256" },
{ name: "active", type: "bool" },
],
outputs: [],
},
// Logic binding
{
name: "setLogicAddress",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "tokenId", type: "uint256" },
{ name: "logicAddress", type: "address" },
],
outputs: [],
},
// Metadata update
{
name: "updateAgentMetadata",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "tokenId", type: "uint256" },
{ name: "newURI", type: "string" },
{
name: "newMetadata",
type: "tuple",
components: [
{ name: "persona", type: "string" },
{ name: "experience", type: "string" },
{ name: "voiceHash", type: "string" },
{ name: "animationURI", type: "string" },
{ name: "vaultURI", type: "string" },
{ name: "vaultHash", type: "bytes32" },
],
},
],
outputs: [],
},
// Views
{
name: "getAgentState",
type: "function",
stateMutability: "view",
inputs: [{ name: "tokenId", type: "uint256" }],
outputs: [
{
name: "",
type: "tuple",
components: [
{ name: "balance", type: "uint256" },
{ name: "active", type: "bool" },
{ name: "logicAddress", type: "address" },
{ name: "createdAt", type: "uint256" },
{ name: "owner", type: "address" },
],
},
],
},
{
name: "getAgentMetadata",
type: "function",
stateMutability: "view",
inputs: [{ name: "tokenId", type: "uint256" }],
outputs: [
{
name: "",
type: "tuple",
components: [
{ name: "persona", type: "string" },
{ name: "experience", type: "string" },
{ name: "voiceHash", type: "string" },
{ name: "animationURI", type: "string" },
{ name: "vaultURI", type: "string" },
{ name: "vaultHash", type: "bytes32" },
],
},
],
},
{
name: "tokensOfOwner",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ name: "", type: "uint256[]" }],
},
{
name: "getTotalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "getFreeMints",
type: "function",
stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [{ name: "", type: "uint256" }],
},
{
name: "isFreeMint",
type: "function",
stateMutability: "view",
inputs: [{ name: "tokenId", type: "uint256" }],
outputs: [{ name: "", type: "bool" }],
},
] as const;
Create hooks/useBap578.ts:
import { useReadContract, useWriteContract, useAccount } from "wagmi";
import { parseEther, zeroAddress, zeroHash } from "viem";
import { BAP578_ADDRESS, BAP578_ABI } from "@/config/bap578";
export function useAgentState(tokenId: bigint) {
return useReadContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "getAgentState",
args: [tokenId],
});
}
export function useAgentMetadata(tokenId: bigint) {
return useReadContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "getAgentMetadata",
args: [tokenId],
});
}
export function useOwnedTokens() {
const { address } = useAccount();
return useReadContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "tokensOfOwner",
args: address ? [address] : undefined,
query: { enabled: !!address },
});
}
export function useFreeMints() {
const { address } = useAccount();
return useReadContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "getFreeMints",
args: address ? [address] : undefined,
query: { enabled: !!address },
});
}
export function useMintAgent() {
const { writeContract, ...rest } = useWriteContract();
const mint = (params: {
to: `0x${string}`;
persona: string;
experience: string;
metadataURI: string;
value?: bigint;
}) => {
writeContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "createAgent",
args: [
params.to,
zeroAddress,
params.metadataURI,
{
persona: params.persona,
experience: params.experience,
voiceHash: "",
animationURI: "",
vaultURI: "",
vaultHash: zeroHash,
},
],
value: params.value ?? 0n,
});
};
return { mint, ...rest };
}
export function useFundAgent() {
const { writeContract, ...rest } = useWriteContract();
const fund = (tokenId: bigint, amountBnb: string) => {
writeContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "fundAgent",
args: [tokenId],
value: parseEther(amountBnb),
});
};
return { fund, ...rest };
}
export function useWithdrawFromAgent() {
const { writeContract, ...rest } = useWriteContract();
const withdraw = (tokenId: bigint, amountBnb: string) => {
writeContract({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
functionName: "withdrawFromAgent",
args: [tokenId, parseEther(amountBnb)],
});
};
return { withdraw, ...rest };
}
Recommended component set:
WalletButton — uses <ConnectButton /> from RainbowKitMintAgentForm — collects persona, experience, URI; calls useMintAgentAgentDashboard — lists owned tokens via useOwnedTokens, renders cardsAgentIdentityCard — displays one agent's four-question profileFundAgentForm — BNB amount input, calls useFundAgentWithdrawAgentForm — amount input, calls useWithdrawFromAgentAgentStatusToggle — switch that calls setAgentStatusVaultVerifier — fetches vault URI, hashes content, compares to on-chain hashimport { keccak256, toHex } from "viem";
async function verifyVault(vaultURI: string, onChainHash: `0x${string}`) {
const res = await fetch(vaultURI);
const content = await res.text();
const hash = keccak256(toHex(content));
return hash === onChainHash;
}
Display a green "Verified" badge if true, or a red "Unverified — data may have been tampered with" warning if false. If vaultHash is the zero hash, display "No vault data registered".
Use viem's getContractEvents to fetch historical events for a token:
import { publicClient } from "@/config/web3";
import { BAP578_ADDRESS, BAP578_ABI } from "@/config/bap578";
async function getAgentHistory(tokenId: bigint) {
const events = await publicClient.getContractEvents({
address: BAP578_ADDRESS,
abi: BAP578_ABI,
fromBlock: 0n,
toBlock: "latest",
});
return events.filter((e) => {
const args = e.args as Record<string, unknown>;
return args.tokenId === tokenId;
});
}
Render these as a timeline component showing creation, funding, withdrawals, status changes, and metadata updates with timestamps and transaction hashes.
to = connected address when free mints remain.vaultHash before displaying.When asked for frontend help, respond with:
The BAP-578 frontend should work on mobile devices where most Web3 users interact:
Support deep links to specific agents for sharing:
https://app.example.com/agent/17
https://app.example.com/owner/0xABC...
Consider PWA support for mobile users who want an app-like experience:
bap578bap578-scannerbap578-metadata-designtools
Use only when the user explicitly asks to stage, commit, push, and open a GitHub pull request in one flow using the GitHub CLI (`gh`).
development
Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like "the xlsx in my downloads") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved.
development
Use this skill when turning messy workout information into clear logs, comparing user-provided sessions, surfacing trends or likely PRs, and suggesting realistic next-session steps.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.