Appearance
Interacting with Smart Contracts
Learn how to interact with smart contracts on Celo using the MiniPay wallet.
Reading Contract State
Read data from smart contracts using useReadContract:
tsx
import { useReadContract } from "wagmi";
import { erc20Abi } from "viem";
function TokenBalance({ tokenAddress }: { tokenAddress: `0x${string}` }) {
const { address } = useAccount();
const { data: balance, isLoading } = useReadContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [address!],
query: {
enabled: !!address,
},
});
if (isLoading) return <div>Loading...</div>;
return <div>Balance: {balance?.toString()}</div>;
}Reading Multiple Contract Values (Batching)
Batch multiple reads in a single call using useReadContracts:
tsx
import { useReadContracts } from "wagmi";
import { erc20Abi, formatUnits } from "viem";
import type { Hex } from "viem";
function TokenInfo({ tokenAddress }: { tokenAddress: `0x${string}` }) {
const { address } = useAccount();
const { data: results, isLoading } = useReadContracts({
contracts: [
{
address: tokenAddress,
abi: erc20Abi,
functionName: "balanceOf",
args: [address as Hex],
},
{
address: tokenAddress,
abi: erc20Abi,
functionName: "decimals",
},
{
address: tokenAddress,
abi: erc20Abi,
functionName: "symbol",
},
],
query: {
enabled: !!address,
},
});
if (isLoading) return <div>Loading...</div>;
const [balance, decimals, symbol] = results || [];
const formatted = balance && decimals ? formatUnits(balance, decimals) : "0";
return (
<div>
<p>
{formatted} {symbol}
</p>
</div>
);
}Writing to Contracts
Write to contracts using useWriteContract:
tsx
import { useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { encodeFunctionData, erc20Abi, parseUnits } from "viem";
function TransferTokens({
tokenAddress,
to,
amount,
}: {
tokenAddress: `0x${string}`;
to: `0x${string}`;
amount: string;
}) {
const { writeContract, data: hash, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
hash,
});
const handleTransfer = () => {
writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: "transfer",
args: [to, parseUnits(amount, 18)],
});
};
return (
<div>
<button onClick={handleTransfer} disabled={isPending || isConfirming}>
{isPending
? "Preparing..."
: isConfirming
? "Confirming..."
: "Transfer"}
</button>
{isSuccess && <p>Transfer successful!</p>}
</div>
);
}Custom Contract ABIs
Define and use custom contract ABIs:
tsx
// contracts/my-contract.ts
export const MY_CONTRACT_ABI = [
{
inputs: [{ name: "value", type: "uint256" }],
name: "setValue",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "getValue",
outputs: [{ name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;
// In your component
import { useReadContract, useWriteContract } from "wagmi";
import { MY_CONTRACT_ABI } from "./contracts/my-contract";
const CONTRACT_ADDRESS = "0x..." as `0x${string}`;
function MyContract() {
const { data: value } = useReadContract({
address: CONTRACT_ADDRESS,
abi: MY_CONTRACT_ABI,
functionName: "getValue",
});
const { writeContract } = useWriteContract();
const handleSetValue = () => {
writeContract({
address: CONTRACT_ADDRESS,
abi: MY_CONTRACT_ABI,
functionName: "setValue",
args: [BigInt(100)],
});
};
return (
<div>
<p>Value: {value?.toString()}</p>
<button onClick={handleSetValue}>Set Value</button>
</div>
);
}Contract Address Management
Manage contract addresses per network:
tsx
import { useChainId } from "wagmi";
import { celo, celoSepolia } from "wagmi/chains";
import type { Address } from "viem";
const CONTRACT_ADDRESSES: Record<number, Address> = {
[celo.id]: "0x..." as Address, // Mainnet
[celoSepolia.id]: "0x..." as Address, // Testnet
};
function useContractAddress() {
const chainId = useChainId();
return CONTRACT_ADDRESSES[chainId];
}
// Usage
function MyComponent() {
const contractAddress = useContractAddress();
if (!contractAddress) {
return <div>Contract not deployed on this network</div>;
}
// Use contractAddress with your contract calls
}Handling Contract Events
Listen for contract events:
tsx
import { useEffect } from "react";
import { usePublicClient, useWatchContractEvent } from "wagmi";
import { MY_CONTRACT_ABI } from "./contracts/my-contract";
function useContractEvents(contractAddress: `0x${string}`) {
const publicClient = usePublicClient();
useWatchContractEvent({
address: contractAddress,
abi: MY_CONTRACT_ABI,
eventName: "ValueChanged",
onLogs(logs) {
console.log("Value changed:", logs);
// Handle event
},
});
}Error Handling
Handle contract call errors:
tsx
import { useReadContract, useWriteContract } from "wagmi";
function ContractWithErrorHandling() {
const {
data,
error: readError,
isLoading,
} = useReadContract({
address: "0x...",
abi: MY_CONTRACT_ABI,
functionName: "getValue",
});
const { writeContract, error: writeError, isPending } = useWriteContract();
if (readError) {
return <div>Error reading contract: {readError.message}</div>;
}
const handleWrite = () => {
try {
writeContract({
address: "0x...",
abi: MY_CONTRACT_ABI,
functionName: "setValue",
args: [BigInt(100)],
});
} catch (error) {
console.error("Write error:", error);
}
};
return (
<div>
{writeError && <div>Error: {writeError.message}</div>}
<button onClick={handleWrite} disabled={isPending}>
Set Value
</button>
</div>
);
}Gas Estimation
Estimate gas before writing to contracts:
tsx
import { useEstimateGas } from "wagmi";
function useContractGasEstimate(
contractAddress: `0x${string}`,
functionName: string,
args: unknown[],
) {
const { data: gasEstimate, isLoading } = useEstimateGas({
to: contractAddress,
data: encodeFunctionData({
abi: MY_CONTRACT_ABI,
functionName,
args,
}),
});
return { gasEstimate, isLoading };
}Best Practices
- Always validate addresses: Ensure contract addresses are valid before making calls
- Handle loading states: Show loading indicators while reading contract data
- Error handling: Provide user-friendly error messages for failed contract calls
- Network-specific addresses: Use different contract addresses for mainnet and testnet
- ABI management: Keep ABIs in separate files for better organization
- Type safety: Use TypeScript types for contract addresses and ABIs
- Contract verification: All contract source code must be published and verified on Celoscan (mainnet) or Celo Sepolia Celoscan (testnet). This is required for Mini App listing.
- Sample transactions: When submitting your Mini App, provide links to sample transactions for every user-facing contract method. See Submit your MiniApp for submission requirements.
Next Steps
- Learn about sending transactions
- See transaction status tracking
- Check out best practices for wallet interactions