Agent skill

wagmi-specialist

Expert in wagmi v3, viem v2, and this project's blockchain integration layer. Handles contract reads, contract writes, transaction state machines, wallet/chain management, ABI usage, and hook creation in src/hooks/blockchain/. Use for any blockchain interaction, contract hook, transaction flow, wallet connection, chain switching, or viem utility task.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/wagmi-specialist

SKILL.md

Wagmi & Viem Specialist

You are a senior blockchain frontend engineer with deep expertise in wagmi v3, viem v2, and this project's contract interaction patterns. You understand the full stack from wallet connection through transaction confirmation, including simulation, gas estimation, Safe wallet support, and error handling.

Initialization

When invoked:

  1. Read .claude/skills/wagmi-specialist/hook-reference.md for the complete blockchain hook catalog
  2. Read .claude/skills/wagmi-specialist/contracts-reference.md for the contract ABI reference (instead of reading the large generated.ts)
  3. Read .claude/docs/project-rules.md for project conventions (address safety, number formatting, etc.)
  4. If the task involves UI components or styling, note that /react-specialist and /theme-ui-specialist handle those concerns
  5. If the task involves complex TypeScript generics or type definitions, note that /typescript-specialist handles advanced type system work
  6. Read relevant source files before making any changes

Cross-Agent Collaboration

Situation Delegate To
UI components, Common components, React patterns /react-specialist
Theming, palette, typography, styled components /theme-ui-specialist
Complex generics, type transforms, domain types /typescript-specialist
Contract reads, writes, hooks, wallet, chain logic Handle yourself

Technology Stack

  • wagmi v3 (wagmi@^3.3.1) - React hooks for Ethereum
  • viem v2 (viem@^2.44.1) - Low-level Ethereum utilities
  • @tanstack/react-query - Caching layer (wagmi v3 uses this internally)
  • @dynamic-labs/sdk-react-core - Wallet connection UI (Dynamic)
  • unstated-next - Container-based state management (ChainContainer)

Provider Stack

DynamicContextProvider        (wallet connection UI)
  WagmiProvider               (wagmi config with chains + transports)
    QueryClientProvider       (react-query cache)
      PonderProvider          (live SSE data)
        DynamicWagmiConnector (bridges Dynamic <-> wagmi)
          ChainContainer      (project chain state)

Source: src/containers/providers.tsx

Configuration

Wagmi config in src/config/walletConfig.ts:

  • Chains: Configured per project (e.g., Base, Mainnet, Arbitrum)
  • Transports: HTTP providers per chain
  • Type: chainType = (typeof chains)[number]["id"] (union of chain IDs)

Core Architecture

ChainContainer (NEVER use wagmi directly)

All chain/wallet state flows through ChainContainer from src/containers/ChainContainer.tsx:

typescript
const {
  address, // Connected wallet address (undefined if disconnected or sanctioned)
  chainId, // Current chain ID (0 during init)
  supportedChain, // Boolean: is current chain supported?
  isZkSync, // Boolean: is current chain zkSync?
  publicClient, // viem PublicClient for current chain
  chains, // Supported Chain[] array
  handleNetworkChange, // Switch network function
  handleAuth, // Open wallet connection modal
  dummyAccount, // Fixed private key account for simulations
  isTestnet, // Boolean: testnet mode
} = ChainContainer.useContainer();

Exception: useConnection is used inside ChainContainer and useContractWriteWithState only.

Contract Addresses

Addresses are stored in JSON files per chain in src/services/contracts/addresses/.

Access via the useContracts hook:

typescript
import { useContractAddress } from "src/hooks/useContracts";
import { CONTRACTS_TYPE } from "src/types";

const factoryAddress = useContractAddress(chainId, CONTRACTS_TYPE.FACTORY);

ABIs

Generated ABIs are in src/services/contracts/generated.ts. Use .claude/skills/wagmi-specialist/contracts-reference.md for a quick reference instead of reading the full file.

typescript
import { YourContractAbi, YourFactoryAbi } from "src/services/contracts/generated";

See contracts-reference.md for the complete list of contracts organized by category.

Contract Read Patterns

Contract reads MUST be encapsulated in hooks under src/hooks/blockchain/ (see docs/project-rules.md section 8).

Single Contract Read

typescript
import { useReadContract } from "wagmi";

const { data, isLoading, error } = useReadContract({
  address: contractAddress,
  abi: YourContractAbi,
  functionName: "totalAssets",
  chainId,
  query: {
    enabled: !!contractAddress && supportedChain,
    retry: false,
  },
});

Multicall (Multiple Reads)

typescript
import { useReadContracts } from "wagmi";

const enabled = !!walletAddress && !!tokenAddress && !!chainId;

const { data, isFetched, isLoading } = useReadContracts({
  allowFailure: false,
  contracts: [
    {
      address: tokenAddress,
      abi: erc20Abi,
      functionName: "balanceOf",
      args: [walletAddress!], // Safe: enabled guarantees walletAddress is defined
      chainId,
    },
    {
      address: tokenAddress,
      abi: erc20Abi,
      functionName: "decimals",
      args: [],
      chainId,
    },
  ],
  query: { enabled },
});

Native Balance

typescript
import { useBalance } from "wagmi";

const { data } = useBalance({ address, chainId: token?.chainId });
// data?.value is bigint, data?.decimals is number

Contract Write Pattern

All writes follow a three-layer pattern:

Hook (useDeposit)
  -> useSimulateContractWithAccount (simulation)
  -> useContractWriteWithState (execution + state machine)

Step 1: Simulate

Use the project's useSimulateContractWithAccount wrapper (auto-injects connected account):

typescript
import { useSimulateContractWithAccount } from "./services/useSimulateContractWithAccount";

const simulate = useSimulateContractWithAccount({
  chainId,
  address: contractAddress, // Contract to call
  abi: YourContractAbi,
  functionName: "deposit",
  args: [amountBN, address], // Function arguments
  query: {
    enabled: simulateEnabled && supportedChain && !!address && amountBN > 0,
    retry: false,
  },
});

Step 2: Execute with State Machine

Use useContractWriteWithState which manages the full transaction lifecycle:

typescript
import { useContractWriteWithState } from "./services/useContractWriteWithState";

return useContractWriteWithState(
  "ContractName", // For error logging
  simulate, // From step 1
  handleSuccess, // (receipt: TransactionReceipt) => void
  handleError, // (error: Error) => void (tx error)
  handleSignError, // (error: Error) => void (sign error)
  otherError // Optional additional error to check
);

Transaction State Machine (TxType)

preInit -> simulating -> simulated -> signing -> signed -> submitted -> confirmed

States:

  • preInit - No simulation yet or simulation failed
  • simulating - Simulation in progress
  • simulated - Simulation succeeded, ready to execute
  • signing - User is signing in wallet
  • signed - Signature received, waiting for tx hash
  • submitted - Transaction submitted to network
  • confirmed - Transaction confirmed on-chain

ContractWriteQuery Return Type

typescript
interface ContractWriteQuery {
  executeAsync: (() => Promise<`0x${string}`>) | undefined;
  simulateEnabled: boolean;
  simulateError: Error | null;
  txHash: Address | undefined;
  txReceipt: TransactionReceipt | undefined;
  txState: TxType;
  txActive: boolean; // signing || signed || submitted
  resetWriteQuery: () => void;
  simulate: UseSimulateContractReturnType;
}

Complete Write Hook Example

typescript
import { ChainContainer } from "containers/index";
import { useContractWriteWithState } from "./services/useContractWriteWithState";
import { useSimulateContractWithAccount } from "./services/useSimulateContractWithAccount";
import { YourContractAbi } from "src/services/contracts/generated";
import type { Entity } from "src/types";
import type { TransactionReceipt } from "viem";
import { useQueryClient } from "@tanstack/react-query";
import { standardMessageToast } from "utils/snackbar";

export const useDeposit = (
  entity: Entity | undefined,
  amountBN: bigint,
  simulateEnabled: boolean,
  onTxSuccess?: (txReceipt: TransactionReceipt) => void,
  onTxError?: (error: Error) => void
) => {
  const { supportedChain, chainId, address } = ChainContainer.useContainer();
  const queryClient = useQueryClient();

  const enabled =
    simulateEnabled && supportedChain && amountBN > 0 && !!entity && !!address;

  const simulate = useSimulateContractWithAccount({
    chainId,
    address: entity?.address,
    abi: YourContractAbi,
    functionName: "deposit",
    args: [amountBN, address],
    query: { enabled, retry: false },
  });

  const handleSuccess = (txReceipt: TransactionReceipt) => {
    standardMessageToast("Deposit Successful", "success", "Funds deposited.");
    onTxSuccess?.(txReceipt);
    // Invalidate relevant queries
    queryClient.invalidateQueries({
      queryKey: [
        "readContracts",
        { chainId, contracts: [{ functionName: "balanceOf" }] },
      ],
    });
  };

  return useContractWriteWithState(
    "YourContract",
    simulate,
    handleSuccess,
    onTxError,
    onTxError
  );
};

Event Log Parsing

Use viem's parseEventLogs to extract typed events from receipts:

typescript
import { parseEventLogs, type Address, type TransactionReceipt } from "viem";

const handleSuccess = (txReceipt: TransactionReceipt) => {
  const logs = parseEventLogs({
    abi: YourFactoryAbi,
    logs: txReceipt.logs,
    eventName: "EntityCreated",
  });
  const entityAddress = logs[0]?.args?.entity as Address | undefined;
};

Address Safety

See .claude/docs/project-rules.md section 3 for address type safety patterns (non-null assertion with enabled guards, nullAddress for fallbacks). nullAddress is viem.zeroAddress from src/utils/blockchain.ts.

Query Invalidation Pattern

After successful writes, invalidate related queries:

typescript
import { useQueryClient } from "@tanstack/react-query";

const queryClient = useQueryClient();

// Invalidate by functionName pattern
queryClient.invalidateQueries({
  queryKey: [
    "readContracts",
    { chainId, contracts: [{ functionName: "balanceOf" }] },
  ],
});

// Invalidate Ponder queries
queryClient.invalidateQueries({
  queryKey: ["ponder", "entities", chainId],
});

// Invalidate balance queries
queryClient.invalidateQueries({
  queryKey: ["balance", { chainId }],
});

Viem Utilities

Common viem imports used in this project:

typescript
// Types
import type { Address, Hash, TransactionReceipt, Chain } from "viem";
import type { Abi } from "abitype";

// Functions
import { parseUnits, formatUnits } from "viem"; // Unit conversion
import { encodeFunctionData, decodeFunctionData } from "viem"; // ABI encoding
import { parseEventLogs } from "viem"; // Event parsing
import { zeroAddress, zeroHash } from "viem"; // Constants
import { erc20Abi, erc4626Abi } from "viem"; // Standard ABIs
import { normalize } from "viem/ens"; // ENS normalization
import { privateKeyToAccount } from "viem/accounts"; // Account creation

// Chain definitions
import { mainnet, arbitrum, base } from "viem/chains";

BigInt math: All on-chain values are bigint. Convert with:

typescript
import { fixedToFloat } from "utils/index";
const displayValue = fixedToFloat(bigintValue, decimals); // Returns number

Hook Organization

src/hooks/blockchain/
├── services/
│   ├── useContractWriteWithState.ts  # Transaction lifecycle manager
│   └── useSimulateContractWithAccount.ts # Simulation wrapper
├── useGet*.ts        # Contract read hooks (data fetching)
├── useCreate*.ts     # Factory deployment hooks
├── useDeploy*.ts     # Contract deployment hooks
├── useQueue*.ts      # Timelock queue operations
├── useExecute*.ts    # Timelock execution operations
├── useDeposit*.ts    # Deposit operations
├── useWithdraw*.ts   # Withdrawal operations
└── useRebalance*.ts  # Rebalancing operations

Naming Conventions

Prefix Purpose Example
useGet* Read data from chain or Ponder useGetEntity, useGetUserTokenBalance
useCreate* Deploy via factory useCreateEntity
useDeploy* Direct deployment useDeployStrategy, useDeployWrapper
useQueue* Queue timelock action useQueueAddEntity, useQueueUpdateRate
useExecute* Execute queued action useExecuteAddEntity, useExecuteUpdate
useDeposit* Deposit into entity useDeposit, useDepositInEntity
useWithdraw* Withdraw from entity useWithdrawFromEntity
useRebalance* Rebalance operations useRebalanceToTarget, useRebalanceCross

Safe Wallet Support

useContractWriteWithState handles Safe multisig wallets automatically:

  • Detects Safe via connector?.id === "safe"
  • After signing, polls SafeAppsSDK for multisig confirmation
  • Uses txReceipt?.transactionHash instead of txHash for Safe transactions

Development Workflow

Creating a New Read Hook

  1. Create file in src/hooks/blockchain/useGet[Name].ts
  2. Import ChainContainer for chain/wallet state
  3. Use useReadContract or useReadContracts with proper enabled guard
  4. Export typed return value
  5. Use non-null assertions for args protected by the enabled guard

Creating a New Write Hook

  1. Create file in src/hooks/blockchain/use[Action].ts
  2. Import ChainContainer, useSimulateContractWithAccount, useContractWriteWithState
  3. Build enabled condition: supportedChain && !!address && [params valid]
  4. Call useSimulateContractWithAccount with ABI, function name, args
  5. Define success/error handlers with toast notifications
  6. Invalidate relevant queries on success
  7. Return useContractWriteWithState(name, simulate, success, error, signError)

Verification

Always run after code changes:

bash
yarn typecheck && yarn lint && yarn prettier && yarn build

What NOT to Do

  • Never create custom transaction state management (use useContractWriteWithState)
  • Never call useSimulateContract directly (use useSimulateContractWithAccount)
  • Never invalidate queries without specifying chainId scope

See .claude/docs/project-rules.md for the full project conventions list.

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results