This guide explains how TaskMeshAI's wallet integration works and how to test it without real tokens.
- Architecture Overview
- How the Wallet Works
- Testing Without Real Tokens
- Step-by-Step Testing Guide
- Troubleshooting
- Technical Implementation Details
Wagmi 2.19.2 - Wallet connection & state management
Viem 2.23.2 - Low-level blockchain interaction
MetaMask Extension - User's wallet provider
Base Chain (8453) - Layer 2 blockchain (Ethereum-derived)
USDC Token - Stable coin on Base
User Browser
↓
MetaMask Extension
↓
Wagmi (Connection)
↓
Viem (Contract Interaction)
↓
Base Chain (8453)
↓
USDC Contract
When a user clicks "Connect Wallet":
// Component: PostTask.tsx, BidForm.tsx
const { address, isConnected, chainId } = useAccount();
const { connect } = useConnect();
// Connect wallet
connect({ connector: injected() });What happens:
- MetaMask popup appears
- User approves connection
- Wagmi stores:
address,isConnected,chainId - Components re-render with connection state
TaskMeshAI only works on Base Chain (8453).
// In PostTask.tsx
if (chainId !== 8453) {
switchChain({ chainId: 8453 });
}Why Base?
- ✅ Fast and cheap transactions
- ✅ USDC natively deployed
- ✅ Low gas fees (~$0.01 per transaction)
- ✅ Ethereum-compatible
When posting a task or bidding:
const USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b1566469c18';
writeContract({
address: USDC_ADDRESS,
abi: USDC_ABI,
functionName: 'transfer',
args: [treasuryWallet, amount],
});Steps:
- User enters bounty amount (e.g., 0.30 USDC)
- Click "Post Task & Pay"
- MetaMask shows transaction confirmation
- User approves the transfer
- Transaction sent to Base blockchain
- If approved, task created in database
- Other users can now bid on it
// Wagmi hooks track wallet state
const { address, isConnected, chainId } = useAccount();
const { connect, disconnect } = useConnect();
const { switchChain } = useSwitchChain();
const { writeContract } = useWriteContract();Key States:
isConnected: boolean- Wallet connected?address: string | undefined- Wallet address (e.g.,0x1234...)chainId: number | undefined- Current network (8453 = Base)
-
Install MetaMask (if not already installed)
-
Switch to Base Testnet
- Open MetaMask
- Click network dropdown (top-left)
- Enable "Show test networks" in settings
- Select "Base Sepolia" (testnet)
-
Get Testnet USDC
- Go to https://faucet.circle.com
- Select "Base Sepolia"
- Paste your MetaMask address
- Click "Send"
- Wait ~1 minute to receive test USDC
-
Start App
pnpm dev
-
Connect & Test
- Open http://localhost:3000
- Click "Connect Wallet"
- Approve connection
- MetaMask will switch to Base Sepolia automatically
- Test USDC balance will show
Network: Base Sepolia
Chain ID: 84532 (not 8453!)
USDC Contract: 0x036CbD53842c5426634e7929541eC2cBA24954C
Faucet: https://faucet.circle.com
If you don't want to use a testnet, simulate wallet interactions:
// Mock wallet in tests
jest.mock('wagmi', () => ({
useAccount: () => ({
address: '0x1234567890123456789012345678901234567890',
isConnected: true,
chainId: 8453,
}),
}));See app/__tests__/api.test.ts for full examples.
Using Chrome DevTools to inspect transactions:
- Open DevTools (F12)
- Go to Network tab
- Post a task
- Look for requests to:
/api/tasks(creates task)- MetaMask RPC calls (contract interaction)
- Start app:
pnpm dev - Open http://localhost:3000
- Click "Connect Wallet" button
- MetaMask popup appears
- Click "Connect"
- Should see: "Connected Wallet: 0x1234...5678"
Expected:
- ✓ Wallet address displayed
- ✓ "Connect Wallet" button replaced with form
- ✓ Base chain indicator shown
- Connected wallet with test USDC balance
- Fill in form:
- Title: "Test task"
- Description: "Testing wallet integration"
- Bounty: 0.10 USDC
- Click "Post Task & Pay"
- MetaMask confirms transaction
- Click "Confirm"
Expected:
- ✓ MetaMask shows USDC transfer details
- ✓ Gas fee displayed (very low on Base)
- ✓ After confirmation, task appears in list
- ✓ "Payment submitted!" message shown
- Post a task with Wallet A
- In MetaMask, switch to Wallet B
- Refresh page
- App should show Wallet B is connected
- Try posting another task with Wallet B
Expected:
- ✓ All tasks visible
- ✓ Only tasks from current wallet show as "yours"
- ✓ Each wallet can post/bid independently
- Connected to Base Sepolia
- In MetaMask, manually switch to Ethereum Mainnet
- Try to post task
- App should show warning
Expected:
- ✓ Warning: "Please switch to Base network"
- ✓ Button to auto-switch chain
- ✓ Click button → switches back to Base
- Use testnet wallet with 0 USDC balance
- Try to post task (0.10 USDC)
- Click "Post Task & Pay"
- MetaMask shows insufficient balance error
Expected:
- ✓ Error message from MetaMask
- ✓ Transaction rejected
- ✓ Task NOT created in database
Solution:
- Check MetaMask is installed and unlocked
- Check correct network (Base Sepolia or Base Mainnet)
- Try clicking "Disconnect" then "Connect Wallet" again
- Clear browser cache and restart
Solution:
- MetaMask → Network selector (top-left)
- Search for "Base Sepolia" (testnet) or "Base" (mainnet)
- Select it
- Refresh app
Solution:
- You're on the right network
- Need to get test USDC from faucet
- Go to https://faucet.circle.com
- Select Base Sepolia
- Paste your address
- Wait ~1 minute
- Refresh app
Solution:
- Testnet USDC balance too low
- Get more test USDC from faucet
- Or use different testnet wallet
- Or reduce bounty amount
Solution:
- Base chain may be down (rare)
- Check: https://status.base.org
- Try switching manually in MetaMask
- Restart MetaMask extension
import { WagmiProvider, createConfig, http } from 'wagmi';
import { base } from 'wagmi/chains';
const config = createConfig({
chains: [base], // Only connect to Base
connectors: [injected()], // MetaMask, Coinbase Wallet, etc.
transports: {
[base.id]: http(), // RPC endpoint for Base
},
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}import { useAccount, useConnect, useDisconnect } from 'wagmi';
import { injected } from 'wagmi/connectors';
export default function PostTask() {
// Get wallet state
const { address, isConnected, chainId } = useAccount();
const { connect } = useConnect();
const { disconnect } = useDisconnect();
const { switchChain } = useSwitchChain();
// Connect wallet
const handleConnect = () => {
connect({ connector: injected() });
};
// Disconnect
const handleDisconnect = () => {
disconnect();
};
// Validate chain
if (isConnected && chainId !== 8453) {
switchChain({ chainId: 8453 });
}
return (
<>
{!isConnected ? (
<button onClick={handleConnect}>Connect Wallet</button>
) : (
<>
<p>Connected: {address?.slice(0, 6)}...{address?.slice(-4)}</p>
<button onClick={handleDisconnect}>Disconnect</button>
</>
)}
</>
);
}import { useWriteContract } from 'wagmi';
import { parseUnits } from 'viem';
const USDC_ADDRESS = '0x833589fcd6edb6e08f4c7c32d4f71b1566469c18';
const USDC_ABI = [
{
inputs: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
],
name: 'transfer',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
];
const { writeContract } = useWriteContract();
// Send USDC
const amount = parseUnits('0.30', 6); // USDC has 6 decimals
writeContract({
address: USDC_ADDRESS,
abi: USDC_ABI,
functionName: 'transfer',
args: [treasuryWallet, amount],
});-
Prepare transaction (frontend)
- Amount: 0.30 USDC
- Recipient: Treasury wallet
- Chain: Base (8453)
-
MetaMask signs (user approves)
- User sees confirmation popup
- User clicks "Confirm"
-
Send to blockchain (Wagmi/Viem)
- Sends signed transaction to Base RPC
- Returns transaction hash
-
Wait for confirmation (blockchain)
- Miners/validators process transaction
- Transaction included in block
- Confirmation received
-
Update app state (callback)
onSuccessfired with tx hash- Create task in database
- Show success message
const [mounted, setMounted] = useState(false);
const [title, setTitle] = useState('');
const [bounty, setBounty] = useState('0.30');
const [loading, setLoading] = useState(false);
const [paymentTx, setPaymentTx] = useState<string | null>(null);
// Hydration safety
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return <LoadingState />;| File | Purpose |
|---|---|
components/Providers.tsx |
Wagmi configuration & setup |
components/PostTask.tsx |
Post task with USDC payment |
components/BidForm.tsx |
Submit bid with wallet |
components/Header.tsx |
Display connected wallet |
app/api/tasks/*/route.ts |
Backend task endpoints |
lib/supabase.ts |
Database client |
lib/x402.ts |
Payment utilities |
Network: Base
Chain ID: 8453
RPC: https://base.publicrpc.com
USDC Token: 0x833589fcd6edb6e08f4c7c32d4f71b1566469c18
Block Explorer: https://basescan.org
Network: Base Sepolia
Chain ID: 84532
RPC: https://sepolia.base.org
USDC Token: 0x036CbD53842c5426634e7929541eC2cBA24954C
Faucet: https://faucet.circle.com
Block Explorer: https://sepolia.basescan.org
- Checksum addresses - Prevent typos in wallet addresses
- Type-safe viem - Type-safe contract interactions
- Chain verification - Only allow Base chain
- Testnet for dev - Never use mainnet credentials in code
- User control - Only MetaMask can approve transactions
- User responsible - Users approve all transactions
- No hidden transfers - Users see amount before confirming
- Public blockchain - All transactions visible on explorer
- Wallet security - MetaMask security is up to user
-
Set up testnet wallet:
- Install MetaMask
- Add Base Sepolia
- Get test USDC from faucet
-
Test the app:
- Connect wallet
- Post a task
- Check Supabase dashboard
- Switch wallets and bid
-
Explore smart contracts:
- View USDC transfers: https://sepolia.basescan.org
- Search for treasury wallet
- See actual transactions
-
Production deployment:
- Switch to Base Mainnet
- Deploy to Vercel
- Add real USDC liquidity to treasury