Multi-chain EVM
Support multiple EVM networks simultaneously — Ethereum, Base, Polygon, and any other EVM chain — with a single config and zero per-call boilerplate.
Configuration
Pass a chains map to KevoProvider instead of a single evmRpcUrl. Each key is a chain ID, the value is the JSON-RPC endpoint for that chain. Use defaultChainId to set which chain is active on startup.
import { KevoProvider } from '@kevo-ws/sdk/react'
function App() {
return (
<KevoProvider
config={{
publishableKey: 'pk_live_xxxxxxxxxxxx',
defaultChainId: 1,
chains: {
1: 'https://eth-mainnet.infura.io/v3/YOUR_KEY',
8453: 'https://base-mainnet.infura.io/v3/YOUR_KEY',
137: 'https://polygon-mainnet.infura.io/v3/YOUR_KEY',
},
}}
>
<YourApp />
</KevoProvider>
)
}useChain
useChain() gives you the active chain ID, a setter, and the full chains map. Calling setChain(chainId) switches the active chain and triggers a re-render of all chain-dependent hooks (useBalance, useTokenBalance, etc.).
import { useChain } from '@kevo-ws/sdk/react'
const { activeChainId, setChain, chains, rpcUrl } = useChain()
// activeChainId: number | null — currently active chain ID
// setChain: (id: number) => void — switch active chain
// chains: Record<number, string> — full map from config
// rpcUrl: string | null — RPC URL for the active chain| Prop | Type | Description |
|---|---|---|
activeChainId | number | null | Currently active EVM chain ID. Null if no chain is configured. |
setChain | (chainId: number) => void | Switch the active chain. Triggers re-renders of all chain-aware hooks. |
chains | Record<number, string> | All configured chains from KevoConfig.chains. |
rpcUrl | string | null | The RPC URL for the currently active chain. |
Chain-aware hooks
useBalance() and useTokenBalance() automatically fetch from the active chain's RPC. Switching chain with setChain() triggers an immediate re-fetch with no extra code.
import { useChain, useBalance, useTokenBalance } from '@kevo-ws/sdk/react'
function ChainBalance() {
const { activeChainId, setChain, chains } = useChain()
const { balance } = useBalance() // updates automatically on chain switch
const { balance: usdc } = useTokenBalance(USDC_ADDRESS[activeChainId!])
return (
<div>
<select
value={activeChainId ?? ''}
onChange={e => setChain(Number(e.target.value))}
>
{Object.keys(chains).map(id => (
<option key={id} value={id}>Chain {id}</option>
))}
</select>
<p>Native balance: {balance !== null ? (Number(balance) / 1e18).toFixed(6) : '…'}</p>
<p>USDC balance: {usdc !== null ? (Number(usdc) / 1e6).toFixed(2) : '…'}</p>
</div>
)
}Transactions
sendTransaction resolves the RPC from tx.chainId first, so you can submit transactions on any configured chain regardless of which one is currently active. No rpcUrl argument needed.
import { useSignTransaction } from '@kevo-ws/sdk/react'
import { nativeTransfer, erc20Transfer } from '@kevo-ws/sdk/helpers'
const { sendTransaction } = useSignTransaction()
// Each tx resolves its own RPC from tx.chainId automatically:
await sendTransaction(nativeTransfer({ to: '0x...', value: 1_000_000n, chainId: 1 })) // Ethereum
await sendTransaction(nativeTransfer({ to: '0x...', value: 1_000_000n, chainId: 8453 })) // Base
await sendTransaction(nativeTransfer({ to: '0x...', value: 1_000_000n, chainId: 137 })) // PolygonrpcUrl arg → chains[tx.chainId] → chains[activeChainId] → evmRpcUrl fallback.Real-world example — lending on multiple chains
A lending protocol deployed on both Ethereum and Polygon. Users can switch chains with one button tap and all balances and transactions update automatically.
import { useChain, useBalance, useTokenBalance } from '@kevo-ws/sdk/react'
import { useSignTransaction } from '@kevo-ws/sdk/react'
import { encodeFunctionCall } from '@kevo-ws/sdk/helpers'
// Contract addresses per chain
const LENDING_CONTRACT: Record<number, string> = {
1: '0xEthLendingContract...',
137: '0xPolygonLendingContract...',
}
const USDC_ADDRESS: Record<number, string> = {
1: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
137: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',
}
const CHAIN_LABELS: Record<number, string> = {
1: 'Ethereum',
137: 'Polygon',
}
function LendingPage() {
const { activeChainId, setChain, chains } = useChain()
const { balance: nativeBalance } = useBalance()
const { balance: usdcBalance } = useTokenBalance(
activeChainId ? USDC_ADDRESS[activeChainId] : ''
)
const { sendTransaction } = useSignTransaction()
const supply = async (amount: bigint) => {
const tx = encodeFunctionCall({
to: LENDING_CONTRACT[activeChainId!],
functionSignature: 'supply(address,uint256)',
params: [
{ type: 'address', value: USDC_ADDRESS[activeChainId!] },
{ type: 'uint256', value: amount },
],
chainId: activeChainId!,
})
const txHash = await sendTransaction(tx) // RPC resolved from tx.chainId
console.log('Supplied:', txHash)
}
return (
<div>
{/* Chain switcher */}
<div>
{Object.keys(chains).map(id => (
<button
key={id}
onClick={() => setChain(Number(id))}
style={{ fontWeight: Number(id) === activeChainId ? 'bold' : 'normal' }}
>
{CHAIN_LABELS[Number(id)] ?? `Chain ${id}`}
</button>
))}
</div>
{/* Balances — update automatically when chain changes */}
<p>Native: {nativeBalance !== null ? (Number(nativeBalance) / 1e18).toFixed(6) : '…'}</p>
<p>USDC: {usdcBalance !== null ? (Number(usdcBalance) / 1e6).toFixed(2) : '…'}</p>
<button onClick={() => supply(100_000_000n)}>Supply 100 USDC</button>
</div>
)
}Switching at runtime
You can also call client.setActiveChain(chainId) directly on the KevoClient instance for non-React code (scripts, event handlers outside the React tree, etc.).
const { client } = useKevo()
// Equivalent to useChain().setChain(8453)
client.setActiveChain(8453)setActiveChain throws if the given chain ID is not in KevoConfig.chains and no evmRpcUrl fallback is configured. Always check chains first.