Kevo Docs

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.

typescript
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>
  )
}
The EVM wallet address is the same across all chains — it's derived from the same keypair. Only the RPC endpoint changes per chain.

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.).

typescript
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
PropTypeDescription
activeChainIdnumber | nullCurrently active EVM chain ID. Null if no chain is configured.
setChain(chainId: number) => voidSwitch the active chain. Triggers re-renders of all chain-aware hooks.
chainsRecord<number, string>All configured chains from KevoConfig.chains.
rpcUrlstring | nullThe 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.

typescript
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.

typescript
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  }))  // Polygon
RPC resolution priority: explicit rpcUrl 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.

typescript
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.).

typescript
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.