> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sei.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Wagmi + React

> Connect wallets, read chain state, and write transactions in a React app on Sei using Wagmi

export const RunSnippet = props => {
  const {method = 'eth_blockNumber', params = [], network = 'testnet', endpoint, label, title, description, decode = 'auto'} = props || ({});
  const ENDPOINTS = {
    testnet: 'https://evm-rpc-testnet.sei-apis.com',
    mainnet: 'https://evm-rpc.sei-apis.com'
  };
  const rpcUrl = endpoint || ENDPOINTS[network] || ENDPOINTS.testnet;
  const networkLabel = network === 'mainnet' ? 'pacific-1 · mainnet' : network === 'testnet' ? 'atlantic-2 · testnet' : network;
  const requestBody = {
    jsonrpc: '2.0',
    id: 1,
    method,
    params
  };
  const requestJson = JSON.stringify(requestBody, null, 2);
  const [phase, setPhase] = useState('idle');
  const [result, setResult] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);
  const [elapsed, setElapsed] = useState(null);
  const [copied, setCopied] = useState(false);
  const [btnHover, setBtnHover] = useState(false);
  const groupThousands = s => s.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  const hexToDecimal = value => {
    if (typeof value !== 'string' || !(/^0x[0-9a-fA-F]+$/).test(value)) return null;
    if (value.length > 66) return null;
    try {
      return groupThousands(BigInt(value).toString(10));
    } catch (e) {
      return null;
    }
  };
  const run = async () => {
    setPhase('loading');
    setErrorMsg(null);
    setResult(null);
    setElapsed(null);
    const startedAt = typeof performance !== 'undefined' ? performance.now() : null;
    try {
      const response = await fetch(rpcUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(requestBody)
      });
      const data = await response.json();
      if (startedAt != null && typeof performance !== 'undefined') {
        setElapsed(Math.round(performance.now() - startedAt));
      }
      if (data && data.error) {
        setErrorMsg(data.error.message || 'RPC returned an error');
        setPhase('error');
        return;
      }
      setResult(data ? data.result : undefined);
      setPhase('success');
    } catch (err) {
      setErrorMsg(err && err.message ? err.message : 'Request failed');
      setPhase('error');
    }
  };
  const resultString = result === undefined ? 'undefined' : JSON.stringify(result, null, 2);
  const decoded = decode !== 'off' && typeof result === 'string' ? hexToDecimal(result) : null;
  const copyResult = () => {
    const flashCopied = () => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    };
    if (typeof navigator !== 'undefined' && navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(resultString).then(flashCopied, () => {});
      return;
    }
    if (typeof document !== 'undefined') {
      try {
        const ta = document.createElement('textarea');
        ta.value = resultString;
        ta.style.position = 'fixed';
        ta.style.opacity = '0';
        document.body.appendChild(ta);
        ta.select();
        document.execCommand('copy');
        document.body.removeChild(ta);
        flashCopied();
      } catch (e) {}
    }
  };
  const HAIRLINE = 'rgba(128, 128, 128, 0.25)';
  const surfaceStyle = {
    backgroundColor: 'rgba(128, 128, 128, 0.08)'
  };
  const monoStyle = {
    fontFamily: 'var(--sei-font-mono)'
  };
  const codeStyle = {
    backgroundColor: 'rgba(128, 128, 128, 0.05)',
    fontFamily: 'var(--sei-font-mono)'
  };
  const cardClass = 'not-prose w-full rounded-lg border overflow-hidden my-4';
  const headerClass = 'flex items-center justify-between gap-3 px-4 py-2.5 border-b';
  const labelClass = 'text-xs uppercase tracking-wide text-neutral-500 dark:text-neutral-500';
  const preClass = 'm-0 px-4 py-3 text-sm overflow-x-auto text-neutral-700 dark:text-neutral-300';
  const buttonStyle = {
    backgroundColor: btnHover ? 'var(--sei-maroon-200)' : 'var(--sei-maroon-100)',
    color: '#ffffff',
    fontFamily: 'var(--sei-font-mono)',
    textTransform: 'uppercase',
    letterSpacing: '0.04em',
    fontSize: '10px',
    opacity: phase === 'loading' ? 0.7 : 1,
    cursor: phase === 'loading' ? 'default' : 'pointer'
  };
  return <div className={cardClass} style={{
    borderColor: HAIRLINE
  }}>
			<div className={headerClass} style={{
    ...surfaceStyle,
    borderBottomColor: HAIRLINE
  }}>
				<div className="flex flex-col min-w-0">
					<span className="text-sm font-medium text-neutral-900 dark:text-white truncate" style={monoStyle}>
						{title || method}
					</span>
					<span className="text-xs text-neutral-500 dark:text-neutral-500">{networkLabel}</span>
				</div>
				<button type="button" onClick={run} disabled={phase === 'loading'} onMouseEnter={() => setBtnHover(true)} onMouseLeave={() => setBtnHover(false)} className="inline-flex items-center gap-1.5 px-3 py-1.5 shrink-0 transition-colors" style={buttonStyle}>
					{phase === 'loading' ? <svg className="animate-spin h-4 w-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
								<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
								<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8v4a4 4 0 0 0-4 4H4z" />
							</svg> : <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
								<path d="M8 5v14l11-7z" />
							</svg>}
					{phase === 'loading' ? 'Running…' : label || 'Run'}
				</button>
			</div>

			{description ? <div className="px-4 pt-3 text-sm text-neutral-600 dark:text-neutral-400">{description}</div> : null}

			<div className="px-4 pt-3 pb-1">
				<span className={labelClass}>Request</span>
			</div>
			<pre className={preClass} style={codeStyle}>
				{requestJson}
			</pre>

			{phase === 'success' ? <div className="border-t" style={{
    borderTopColor: HAIRLINE
  }}>
					<div className="flex items-center justify-between px-4 pt-3 pb-1">
						<span className={labelClass}>Response{elapsed != null ? ` · ${elapsed} ms` : ''}</span>
						<button type="button" onClick={copyResult} className="text-xs text-neutral-500 hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200 transition-colors">
							{copied ? 'Copied' : 'Copy'}
						</button>
					</div>
					<pre className={preClass} style={codeStyle}>
						{resultString}
					</pre>
					{decoded ? <div className="px-4 pb-3 text-xs text-neutral-500 dark:text-neutral-500" style={monoStyle}>
							= {decoded} (decimal)
						</div> : null}
				</div> : null}

			{phase === 'error' ? <div className="border-t" style={{
    borderTopColor: HAIRLINE
  }}>
					<div className="px-4 pt-3 pb-1">
						<span className={labelClass}>Error</span>
					</div>
					<pre className={`${preClass} text-red-600 dark:text-red-400`} style={codeStyle}>
						{errorMsg}
					</pre>
				</div> : null}
		</div>;
};

# Wagmi + React

[Wagmi](https://wagmi.sh) is the standard React library for EVM wallet connections and contract interactions. This page covers the essential patterns for a Sei frontend: configuration, wallet connect/disconnect, reading balances, and writing to contracts.

For Node.js scripts and backend services, see the [viem Quickstart](/evm/evm-parity/examples/viem-quickstart) instead.

## Try it live

The hooks below — `useBlockNumber`, `useBalance` — are thin wrappers over JSON-RPC reads. Here are those same reads issued directly against Sei mainnet, no wallet connection required, so you can see the data your components will render.

<RunSnippet method="eth_blockNumber" network="mainnet" title="eth_blockNumber" description="What useBlockNumber() resolves to — the latest committed block on pacific-1." />

<RunSnippet method="eth_getBalance" params={['0x0000000000000000000000000000000000000000', 'latest']} network="mainnet" title="eth_getBalance" description="What useBalance({ address }) reads under the hood — wei balance at the latest block. Swap in any address." />

## Install

```bash theme={"dark"}
npm install wagmi viem @sei-js/precompiles @tanstack/react-query
```

## Configuration

Configure Wagmi with the Sei chain and your preferred wallet connectors:

```ts theme={"dark"}
// wagmi.config.ts
import { createConfig, http } from 'wagmi';
import { injected, walletConnect } from 'wagmi/connectors';
import { sei, seiTestnet } from 'viem/chains';

export const config = createConfig({
  chains: [sei, seiTestnet],
  connectors: [
    injected(),
    walletConnect({ projectId: 'YOUR_WALLETCONNECT_PROJECT_ID' }),
  ],
  transports: {
    [sei.id]: http(),
    [seiTestnet.id]: http(),
  },
});
```

## Provider Setup

Wrap your app with `WagmiProvider` and `QueryClientProvider`:

```tsx theme={"dark"}
// App.tsx
import { WagmiProvider } from 'wagmi';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from './wagmi.config';

const queryClient = new QueryClient();

export function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <YourApp />
      </QueryClientProvider>
    </WagmiProvider>
  );
}
```

## Connecting a Wallet

```tsx theme={"dark"}
import { useConnect, useDisconnect, useAccount } from 'wagmi';

export function WalletButton() {
  const { connectors, connect } = useConnect();
  const { disconnect } = useDisconnect();
  const { address, isConnected } = useAccount();

  if (isConnected) {
    return (
      <div>
        <span>{address}</span>
        <button onClick={() => disconnect()}>Disconnect</button>
      </div>
    );
  }

  return (
    <div>
      {connectors.map((connector) => (
        <button key={connector.id} onClick={() => connect({ connector })}>
          Connect {connector.name}
        </button>
      ))}
    </div>
  );
}
```

## Reading the Native SEI Balance

```tsx theme={"dark"}
import { useBalance, useAccount } from 'wagmi';
import { formatEther } from 'viem';

export function SeiBalance() {
  const { address } = useAccount();
  const { data: balance } = useBalance({ address });

  if (!balance) return null;

  return (
    <p>Balance: {formatEther(balance.value)} SEI</p>
  );
}
```

## Reading a Contract

```tsx theme={"dark"}
import { useReadContract } from 'wagmi';
import { parseAbi } from 'viem';

const ERC20_ABI = parseAbi([
  'function balanceOf(address owner) view returns (uint256)',
  'function symbol() view returns (string)',
]);

export function TokenBalance({ tokenAddress, owner }: { tokenAddress: `0x${string}`; owner: `0x${string}` }) {
  const { data: balance } = useReadContract({
    address: tokenAddress,
    abi: ERC20_ABI,
    functionName: 'balanceOf',
    args: [owner],
  });

  const { data: symbol } = useReadContract({
    address: tokenAddress,
    abi: ERC20_ABI,
    functionName: 'symbol',
  });

  if (balance === undefined) return null;

  return <p>{balance.toString()} {symbol}</p>;
}
```

## Writing to a Contract

Use `useWriteContract` for transactions that mutate state, and `useWaitForTransactionReceipt` to track confirmation:

```tsx theme={"dark"}
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { parseAbi, parseUnits } from 'viem';

const ERC20_ABI = parseAbi([
  'function transfer(address to, uint256 amount) returns (bool)',
]);

export function TransferButton({ tokenAddress }: { tokenAddress: `0x${string}` }) {
  const { writeContract, data: hash, isPending } = useWriteContract();
  const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash });

  function handleTransfer() {
    writeContract({
      address: tokenAddress,
      abi: ERC20_ABI,
      functionName: 'transfer',
      args: ['0xRecipient', parseUnits('10', 18)],
    });
  }

  return (
    <div>
      <button onClick={handleTransfer} disabled={isPending || isConfirming}>
        {isPending ? 'Confirm in wallet...' : isConfirming ? 'Confirming...' : 'Transfer'}
      </button>
      {isSuccess && <p>Transfer confirmed.</p>}
    </div>
  );
}
```

<Info>Sei has instant finality — `useWaitForTransactionReceipt` resolves as soon as the transaction is included in a block. A single confirmation is final. See [Finality](/evm/evm-parity/finality).</Info>

## Watching the Connected Chain

```tsx theme={"dark"}
import { useChainId, useSwitchChain } from 'wagmi';
import { sei, seiTestnet } from 'viem/chains';

export function NetworkSwitcher() {
  const chainId = useChainId();
  const { switchChain } = useSwitchChain();

  return (
    <div>
      <p>Connected to chain {chainId}</p>
      {chainId !== sei.id && (
        <button onClick={() => switchChain({ chainId: sei.id })}>
          Switch to Sei Mainnet
        </button>
      )}
    </div>
  );
}
```
