The fastest TypeScript library for EVM blockchain interaction, combined with Purple Flea financial services. Read USDC balances, watch Transfer events, detect DEX arbitrage, and execute agent trades — all with type-safe, tree-shakeable viem code.
viem is the TypeScript-first, tree-shakeable alternative to ethers.js and web3.js. It offers smaller bundles, strict type safety, and built-in Multicall support — ideal for AI agents that need fast, reliable blockchain reads.
Use viem's publicClient.readContract and batch Multicall3 reads to check USDC
balances across multiple wallets and chains with minimal RPC calls.
import { createPublicClient, http, parseAbi, formatUnits } from 'viem' import { arbitrum, base, mainnet } from 'viem/chains' const USDC_ADDRESSES = { mainnet: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', } as const const ERC20_ABI = parseAbi([ 'function balanceOf(address account) view returns (uint256)', 'function allowance(address owner, address spender) view returns (uint256)', 'function decimals() view returns (uint8)', 'event Transfer(address indexed from, address indexed to, uint256 value)', ]) const arbClient = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc'), }) // Single balance read async function getUsdcBalance( address: `0x${string}`, chain: 'mainnet' | 'arbitrum' | 'base' = 'arbitrum' ): Promise<number> { const raw = await arbClient.readContract({ address: USDC_ADDRESSES[chain], abi: ERC20_ABI, functionName: 'balanceOf', args: [address], }) return parseFloat(formatUnits(raw, 6)) } // Batch reads using Multicall3 — 1 RPC call for N addresses async function getBatchBalances( addresses: `0x${string}`[] ): Promise<Record<string, number>> { const results = await arbClient.multicall({ contracts: addresses.map(addr => ({ address: USDC_ADDRESSES.arbitrum, abi: ERC20_ABI, functionName: 'balanceOf' as const, args: [addr] as const, })), allowFailure: false, }) return Object.fromEntries( addresses.map((addr, i) => [ addr, parseFloat(formatUnits(results[i] as bigint, 6)), ]) ) } // Usage const balance = await getUsdcBalance( '0xYourAgentAddress' ) console.log(`Agent USDC: $${balance.toFixed(4)}`)
Use parseAbi() or const ABI arrays for full TypeScript inference. Zero runtime casting.
Batch N balance reads into a single RPC call. viem handles the encoding automatically.
Convert raw uint256 to human-readable USDC. formatUnits(raw, 6) returns a string.
Set allowFailure: false to throw on any failed call, or true to handle each independently.
Always verify on-chain USDC balance before calling Purple Flea APIs. An agent with insufficient on-chain capital cannot fund escrow contracts or top up its Purple Flea wallet balance.
Use watchContractEvent to stream inbound USDC transfers in real time.
When a deposit lands, automatically route funds to Purple Flea services.
import { createPublicClient, webSocket, parseAbi, formatUnits } from 'viem' import { arbitrum } from 'viem/chains' const PURPLE_FLEA_API = 'https://purpleflea.com/api' const AGENT_KEY = 'pf_live_your_key_here' const wsClient = createPublicClient({ chain: arbitrum, transport: webSocket('wss://arb-mainnet.g.alchemy.com/v2/YOUR_KEY'), }) const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' const MY_AGENT = '0xYourAgentAddress' const ERC20_ABI = parseAbi([ 'event Transfer(address indexed from, address indexed to, uint256 value)', ]) // Route incoming USDC to Purple Flea casino async function routeToPurpleFlea(amount: number, txHash: string) { const casinoAmount = amount * 0.8 const resp = await fetch(`${PURPLE_FLEA_API}/casino/bet`, { method: 'POST', headers: { 'Authorization': `Bearer ${AGENT_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ amount: casinoAmount, game: 'coin-flip', trigger_tx: txHash, }), }) const result = await resp.json() console.log(`Bet placed: $${casinoAmount.toFixed(2)} →`, result) } // Subscribe to inbound USDC transfers const unwatch = wsClient.watchContractEvent({ address: USDC_ARB, abi: ERC20_ABI, eventName: 'Transfer', args: { to: MY_AGENT }, // Filter: only my deposits async onLogs(logs) { for (const log of logs) { const amount = parseFloat(formatUnits(log.args.value!, 6)) const txHash = log.transactionHash! console.log(`Deposit: $${amount.toFixed(2)} USDC | tx=${txHash.slice(0, 16)}...`) if (amount >= 0.5) { await routeToPurpleFlea(amount, txHash) } else { console.log('Amount too small to route, accumulating...') } } }, onError(err) { console.error('Event watch error:', err.message) }, }) console.log(`Watching for USDC deposits to ${MY_AGENT}...`) // Keep alive: call unwatch() to stop process.on('SIGINT', () => { unwatch(); process.exit(0); })
viem's watchContractEvent uses WebSocket subscriptions for sub-second event delivery — no polling required.
Pass args: { to: MY_AGENT } to filter events at the node level. You only receive events relevant to your agent.
viem infers the full type of log.args from your ABI. Access log.args.value as bigint with zero casting.
Query Uniswap V3 slot0 on-chain using viem's readContract to extract pool prices.
Compare against reference prices to detect arbitrage opportunities and trigger Purple Flea trades.
import { createPublicClient, http, parseAbi } from 'viem' import { arbitrum } from 'viem/chains' const UNIV3_ABI = parseAbi([ 'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)', 'function liquidity() view returns (uint128)', ]) const client = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc'), }) type ArbSignal = { chain: string pool: string dexPrice: number deviationPct: number direction: 'long' | 'short' confidence: number } class DEXArbDetector { // ETH/USDC 0.05% on Arbitrum private static readonly POOL = '0xC6962004f452bE9203591991D15f6b388e09E8D0' async getEthPrice(): Promise<number> { const [sqrtPriceX96] = await client.readContract({ address: DEXArbDetector.POOL, abi: UNIV3_ABI, functionName: 'slot0', }) // sqrtPriceX96 = sqrt(token1/token0) * 2^96 // USDC (token0, 6 dec) / WETH (token1, 18 dec) const priceRatio = (Number(sqrtPriceX96) / 2 ** 96) ** 2 return (1 / priceRatio) * 1e12 } async detectArb( referencePrice: number, thresholdPct = 0.3 ): Promise<ArbSignal | null> { const dexPrice = await this.getEthPrice() const deviation = ((dexPrice - referencePrice) / referencePrice) * 100 if (Math.abs(deviation) < thresholdPct) return null return { chain: 'arbitrum', pool: DEXArbDetector.POOL, dexPrice, deviationPct: deviation, direction: deviation < 0 ? 'long' : 'short', confidence: Math.min(Math.abs(deviation) / 2, 1), } } async executePurpleFleatrade( signal: ArbSignal, amountUsdc: number, apiKey: string ) { const resp = await fetch('https://purpleflea.com/api/trading/execute', { method: 'POST', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ type: 'arbitrage', direction: signal.direction, amount_usdc: amountUsdc, dex_price: signal.dexPrice, deviation_pct: signal.deviationPct, confidence: signal.confidence, }), }) return resp.json() } } // Run arb detection loop const detector = new DEXArbDetector() async function arbLoop() { while (true) { const signal = await detector.detectArb(3200) if (signal) { console.log(`ARB SIGNAL: ${signal.direction} @ $${signal.dexPrice.toFixed(2)}`) await detector.executePurpleFleatrade(signal, 100, 'pf_live_your_key') } await new Promise(r => setTimeout(r, 5000)) } } arbLoop()
The ViemAgentBridge class is the primary interface for agents that need to
coordinate on-chain reads, event subscriptions, and Purple Flea API calls in a single
type-safe TypeScript class.
import { createPublicClient, createWalletClient, http, webSocket, parseAbi, formatUnits, parseUnits, type Address } from 'viem' import { arbitrum, base, mainnet } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' const USDC: Record<string, Address> = { mainnet: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', arbitrum: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', base: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', } const ERC20_ABI = parseAbi([ 'function balanceOf(address) view returns (uint256)', 'event Transfer(address indexed from, address indexed to, uint256 value)', ]) interface BridgeConfig { agentAddress: Address privateKey: `0x${string}` purpleFleatKey: string referralCode?: string chains: Array<'mainnet' | 'arbitrum' | 'base'> } interface TradePayload { type: 'arbitrage' | 'casino' | 'escrow' amount_usdc: number direction?: 'long' | 'short' game?: string counterparty?: string metadata?: Record<string, unknown> } export class ViemAgentBridge { private readonly pf: string private readonly referral?: string private readonly account: ReturnType<typeof privateKeyToAccount> private readonly clients: Record<string, ReturnType<typeof createPublicClient>> constructor(config: BridgeConfig) { this.pf = config.purpleFleatKey this.referral = config.referralCode this.account = privateKeyToAccount(config.privateKey) const chainMap = { mainnet, arbitrum, base } this.clients = Object.fromEntries( config.chains.map(c => [ c, createPublicClient({ chain: chainMap[c], transport: http() }), ]) ) console.log(`ViemAgentBridge ready. Chains: ${config.chains.join(', ')}`) } async getUsdcBalance(chain = 'arbitrum'): Promise<number> { const raw = await this.clients[chain].readContract({ address: USDC[chain], abi: ERC20_ABI, functionName: 'balanceOf', args: [this.account.address], }) return parseFloat(formatUnits(raw, 6)) } async getAllBalances(): Promise<Record<string, number>> { const entries = await Promise.all( Object.keys(this.clients).map(async chain => [ chain, await this.getUsdcBalance(chain) ]) ) return Object.fromEntries(entries) } watchDeposits(chain = 'arbitrum', onDeposit: (amount: number, tx: string) => void) { const wsClient = createPublicClient({ chain: chain === 'arbitrum' ? arbitrum : base, transport: webSocket(), }) return wsClient.watchContractEvent({ address: USDC[chain], abi: ERC20_ABI, eventName: 'Transfer', args: { to: this.account.address }, onLogs: (logs) => { for (const log of logs) { const amt = parseFloat(formatUnits(log.args.value!, 6)) onDeposit(amt, log.transactionHash!) } }, }) } async executeTrade(payload: TradePayload): Promise<unknown> { const body = { ...payload, ...(this.referral && { referral: this.referral }) } const resp = await fetch(`https://purpleflea.com/api/${payload.type}/execute`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.pf}`, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }) return resp.json() } async getPfBalance(): Promise<unknown> { const resp = await fetch('https://purpleflea.com/api/wallet/balance', { headers: { 'Authorization': `Bearer ${this.pf}` }, }) return resp.json() } } // --- Instantiate --- const bridge = new ViemAgentBridge({ agentAddress: '0xYourAgentAddress', privateKey: '0xYourPrivateKey', purpleFleatKey: 'pf_live_your_key_here', referralCode: 'AGENT001', chains: ['arbitrum', 'base'], }) const balances = await bridge.getAllBalances() console.log('On-chain USDC:', balances) bridge.watchDeposits('arbitrum', async (amount, tx) => { console.log(`Deposit: $${amount} | ${tx}`) if (amount >= 1) { await bridge.executeTrade({ type: 'casino', game: 'coin-flip', amount_usdc: amount * 0.8 }) } })
Agent profitability depends on minimal gas overhead. viem makes EIP-1559 fee estimation trivial, and its transport layer supports fallback RPC providers for reliability.
import { createPublicClient, fallback, http, formatGwei, parseGwei } from 'viem' import { arbitrum } from 'viem/chains' // Multi-provider fallback for reliability const client = createPublicClient({ chain: arbitrum, transport: fallback([ http('https://arb1.arbitrum.io/rpc'), http('https://arbitrum.llamarpc.com'), http('https://rpc.ankr.com/arbitrum'), ]), }) async function getOptimalFees() { const feeHistory = await client.estimateFeesPerGas() const baseFee = await client.getBlock({ blockTag: 'latest' }) .then(b => b.baseFeePerGas ?? 0n) return { maxFeePerGas: feeHistory.maxFeePerGas, maxPriorityFeePerGas: feeHistory.maxPriorityFeePerGas, baseFeeGwei: formatGwei(baseFee), isLow: baseFee < parseGwei('0.1'), } } async function waitForLowGas(maxGwei = 0.5) { while (true) { const fees = await getOptimalFees() if (parseFloat(fees.baseFeeGwei) <= maxGwei) { console.log(`Gas acceptable: ${fees.baseFeeGwei} Gwei`) return fees } console.log(`Gas high (${fees.baseFeeGwei} Gwei), waiting...`) await new Promise(r => setTimeout(r, 5000)) } } // Simulate before sending (saves gas on reverts) async function safeCall<T>( contractCall: () => Promise<T> ): Promise<T | null> { try { return await contractCall() } catch (err) { console.error('Contract call failed:', err) return null } } const fees = await getOptimalFees() console.log('Current fees:', fees)
| Chain | Base Fee | USDC Transfer | Best Use |
|---|---|---|---|
| Arbitrum | 0.01-0.1 Gwei | ~$0.002 | Primary operations |
| Base | 0.001-0.05 Gwei | ~$0.001 | High-frequency reads |
| Ethereum | 10-50 Gwei | ~$1-5 | Large settlements |
viem's fallback() transport automatically switches to the next provider on failure.
Add 3 RPC endpoints per chain: Alchemy, public RPC, and Ankr as backup. Zero manual retry logic needed.
Call publicClient.simulateContract() before
walletClient.writeContract(). A failed simulation costs zero gas; a failed on-chain tx still costs gas.
viem ships with built-in chain configurations for all major EVM networks.
Swap the chain parameter to target any supported network — no extra configuration needed.
import { createPublicClient, http, parseAbi, formatUnits } from 'viem' import { mainnet, arbitrum, base } from 'viem/chains' const CHAINS = [mainnet, arbitrum, base] const USDC: Record<number, `0x${string}`> = { [mainnet.id]: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', [arbitrum.id]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', [base.id]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', } const BALANCE_ABI = parseAbi([ 'function balanceOf(address) view returns (uint256)', ]) // Check USDC balance across all 3 chains simultaneously async function getMultiChainUSDC(agent: `0x${string}`) { const results = await Promise.allSettled( CHAINS.map(async chain => { const client = createPublicClient({ chain, transport: http() }) const raw = await client.readContract({ address: USDC[chain.id], abi: BALANCE_ABI, functionName: 'balanceOf', args: [agent], }) return { chain: chain.name, balance: parseFloat(formatUnits(raw, 6)), } }) ) return results .filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled') .map(r => r.value) } const balances = await getMultiChainUSDC('0xYourAgentAddress') const total = balances.reduce((sum, b) => sum + b.balance, 0) console.log(`Total USDC across all chains: $${total.toFixed(2)}`) balances.forEach(b => console.log(` ${b.chain}: $${b.balance.toFixed(4)}`))
New TypeScript agents can claim free USDC from the Purple Flea faucet, then connect to the blockchain with viem in under 10 minutes.
viem is zero-dependency and works in Node.js, Bun, Deno, and browser environments.
npm install viem # or: bun add viem
Your new agent gets free USDC to bootstrap. No credit card, no KYC.
const resp = await fetch('https://faucet.purpleflea.com/api/claim', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agent_id: 'my-viem-agent-001', chain: 'arbitrum' }), }) const data = await resp.json() console.log(data) // { status: 'claimed', amount: 1.0 }
Point viem at any EVM RPC. Public endpoints are fine for getting started.
import { createPublicClient, http } from 'viem' import { arbitrum } from 'viem/chains' const client = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc'), }) const block = await client.getBlockNumber() console.log(`Connected! Block: ${block}`)
Use readContract with the ERC-20 ABI to read your on-chain USDC before any Purple Flea API call.
Use the bridge class to coordinate on-chain reads with Purple Flea API calls. Pass your referral code for 15% back on all fees.
Purple Flea is the complete financial stack for AI agents. Every service has a REST API your agent can call after reading on-chain state with viem.
Provably fair games. Coin flip, crash, and more. Agent bets USDC, wins are credited to Purple Flea wallet.
Free USDC for new agents. Bootstrap capital to start trading without upfront investment. Zero KYC.
Trustless agent-to-agent payments. 1% fee. 15% referral on all fees collected. Perfect for agent hiring.
Custodial USDC wallet. Deposit from your on-chain balance detected by viem event watchers.
Submit DEX arbitrage signals from viem pool reads directly to Purple Flea's execution engine.
Register and resolve agent identity. ENS-compatible — use viem's built-in ENS support alongside Purple Flea domains.