> ## Documentation Index
> Fetch the complete documentation index at: https://seilabs-docs-evm-cookbook.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction Lifecycle

> Send transactions, wait for receipts, and decode event logs on Sei

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' ? 'Sei Mainnet' : network === 'testnet' ? 'Sei 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 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="not-prose w-full rounded-lg border overflow-hidden my-4" style={{
    borderColor: HAIRLINE
  }}>
			<div className="flex items-center justify-between gap-3 px-4 py-2.5 border-b" 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-600 dark:text-neutral-400">{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="text-xs uppercase tracking-wide text-neutral-600 dark:text-neutral-400">Request</span>
			</div>
			<pre className="m-0 px-4 py-3 text-sm overflow-x-auto text-neutral-700 dark:text-neutral-300" 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="text-xs uppercase tracking-wide text-neutral-600 dark:text-neutral-400">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="m-0 px-4 py-3 text-sm overflow-x-auto text-neutral-700 dark:text-neutral-300" style={codeStyle}>
						{resultString}
					</pre>
					{decoded ? <div className="px-4 pb-3 text-xs text-neutral-600 dark:text-neutral-400" 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="text-xs uppercase tracking-wide text-neutral-600 dark:text-neutral-400">Error</span>
					</div>
					<pre className="m-0 px-4 py-3 text-sm overflow-x-auto text-red-600 dark:text-red-400" style={codeStyle}>
						{errorMsg}
					</pre>
				</div> : null}
		</div>;
};

# Transaction lifecycle

This page covers the full round trip of a transaction: building and sending it, waiting for the receipt, and reading its event logs.

## Try it live

Before you send anything, read the live chain state that your transaction will interact with. These calls run from your browser against Sei Mainnet. You do not need to install anything or use a key.

<RunSnippet method="eth_gasPrice" network="mainnet" title="eth_gasPrice" description="Current gas price in wei. The widget decodes the hex quantity to decimal below the response." />

<RunSnippet method="eth_blockNumber" network="mainnet" title="eth_blockNumber" description="Latest committed block on Sei Mainnet. Your transaction lands a block or two after whatever this returns." />

## Sending a transaction

<CodeGroup>
  ```ts viem theme={null}
  import { createWalletClient, http, parseEther } from 'viem';
  import { privateKeyToAccount } from 'viem/accounts';
  import { sei } from 'viem/chains';

  const account = privateKeyToAccount('0xYourPrivateKey');
  const walletClient = createWalletClient({ account, chain: sei, transport: http() });

  const hash = await walletClient.sendTransaction({
    to: '0xRecipient',
    value: parseEther('1'),
  });
  ```

  ```ts ethers theme={null}
  import { ethers } from 'ethers';

  const provider = new ethers.JsonRpcProvider('https://evm-rpc.sei-apis.com');
  const wallet = new ethers.Wallet('0xYourPrivateKey', provider);

  const tx = await wallet.sendTransaction({
    to: '0xRecipient',
    value: ethers.parseEther('1'),
  });
  ```
</CodeGroup>

## Waiting for the receipt

<CodeGroup>
  ```ts viem theme={null}
  import { createPublicClient, http } from 'viem';

  const client = createPublicClient({ chain: sei, transport: http() });

  const receipt = await client.waitForTransactionReceipt({ hash });

  console.log('Status:', receipt.status);           // 'success' | 'reverted'
  console.log('Block:', receipt.blockNumber);
  console.log('Gas used:', receipt.gasUsed);
  ```

  ```ts ethers theme={null}
  const receipt = await tx.wait();
  // or wait for a specific number of confirmations:
  // const receipt = await tx.wait(1);

  console.log('Status:', receipt.status); // 1 = success, 0 = reverted
  console.log('Block:', receipt.blockNumber);
  console.log('Gas used:', receipt.gasUsed);
  ```
</CodeGroup>

<Info>Sei has instant finality, so the receipt is final as soon as it arrives. One confirmation is sufficient. See [Finality](/evm/evm-parity/finality).</Info>

## Decoding event logs from a receipt

After a contract interaction, decode the events that the transaction emitted:

<CodeGroup>
  ```ts viem theme={null}
  import { parseAbi, decodeEventLog } from 'viem';

  const ERC20_ABI = parseAbi([
    'function transfer(address to, uint256 amount) returns (bool)',
    'event Transfer(address indexed from, address indexed to, uint256 value)',
  ]);

  const hash = await walletClient.writeContract({
    address: TOKEN,
    abi: ERC20_ABI,
    functionName: 'transfer',
    args: ['0xRecipient', 1_000_000n],
  });

  const receipt = await client.waitForTransactionReceipt({ hash });

  // viem returns typed logs automatically when you pass the ABI to getTransactionReceipt
  const typedReceipt = await client.getTransactionReceipt({ hash });

  // Or decode manually from raw logs
  for (const log of receipt.logs) {
    try {
      const event = decodeEventLog({ abi: ERC20_ABI, ...log });
      console.log('Event:', event.eventName, event.args);
    } catch {
      // Log from a different contract or unknown ABI
    }
  }
  ```

  ```ts ethers theme={null}
  import { ethers } from 'ethers';

  const ERC20_ABI = [
    'function transfer(address to, uint256 amount) returns (bool)',
    'event Transfer(address indexed from, address indexed to, uint256 value)',
  ];

  const contract = new ethers.Contract(TOKEN, ERC20_ABI, wallet);
  const tx = await contract.transfer('0xRecipient', 1_000_000n);
  const receipt = await tx.wait();

  // ethers parses logs automatically when using a Contract instance
  for (const log of receipt.logs) {
    try {
      const parsed = contract.interface.parseLog(log);
      if (parsed) {
        console.log('Event:', parsed.name, parsed.args);
        // e.g. Event: Transfer [ '0xFrom', '0xTo', 1000000n ]
      }
    } catch {
      // Log from a different contract
    }
  }
  ```
</CodeGroup>

## Checking for revert

A receipt with `status: 'reverted'` (viem) or `status: 0` (ethers) means that the transaction was included but the execution failed. The transaction still consumed gas.

```ts viem theme={null}
const receipt = await client.waitForTransactionReceipt({ hash });

if (receipt.status === 'reverted') {
  // Transaction was mined but execution failed.
  // Simulate the call to get the revert reason.
  console.error('Transaction reverted at block', receipt.blockNumber);
}
```

## Fetching a past transaction

```ts viem theme={null}
const tx = await client.getTransaction({ hash: '0xTxHash' });
console.log('From:', tx.from);
console.log('To:', tx.to);
console.log('Value:', tx.value);
console.log('Input data:', tx.input);
```

```ts ethers theme={null}
const tx = await provider.getTransaction('0xTxHash');
const receipt = await provider.getTransactionReceipt('0xTxHash');
```

## Getting logs for a specific transaction

To fetch only the events that a specific transaction emitted, instead of decoding them from the receipt, use this pattern:

```ts viem theme={null}
const logs = await client.getContractEvents({
  address: TOKEN,
  abi: ERC20_ABI,
  eventName: 'Transfer',
  blockHash: receipt.blockHash,
});

const txLogs = logs.filter((log) => log.transactionHash === hash);
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.