| Type | Mainnet | Testnet |
|---|---|---|
| HTTP | https://mainnet.megaeth.com/rpc |
https://carrot.megaeth.com/rpc |
| WebSocket | wss://mainnet.megaeth.com/ws |
wss://carrot.megaeth.com/ws |
Third-party providers: Alchemy, QuickNode (recommended for geo-distributed access)
MegaETH supports synchronous transaction submission — get your receipt in <10ms instead of polling.
Two equivalent methods:
| Method | Origin | Status |
|---|---|---|
realtime_sendRawTransaction |
MegaETH original | Supported |
eth_sendRawTransactionSync |
EIP-7966 standard | Supported (proxied) |
History: MegaETH created realtime_sendRawTransaction first. Later, eth_sendRawTransactionSync was standardized as EIP-7966. MegaETH now proxies both — they are functionally identical. Use whichever you prefer; eth_sendRawTransactionSync is recommended for cross-chain compatibility.
# Both work identically:
curl -X POST https://mainnet.megaeth.com/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_sendRawTransactionSync",
"params": ["0x...signedTx"],
"id": 1
}'
# Or use the original:
curl -X POST https://mainnet.megaeth.com/rpc \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "realtime_sendRawTransaction",
"params": ["0x...signedTx"],
"id": 1
}'Response: Full transaction receipt (same schema as eth_getTransactionReceipt)
Corner cases on retry:
- Tx still in pool: "already known" error
- Tx already executed: "nonce too low" error (future: will return receipt directly)
Paginated log queries for large result sets:
const response = await client.request({
method: 'eth_getLogsWithCursor',
params: [{
address: '0x...',
topics: ['0x...'],
fromBlock: '0x0',
toBlock: 'latest'
}]
});
// Returns { logs: [...], cursor: "..." }
// Use cursor in next request to continue{
"jsonrpc": "2.0",
"method": "eth_subscribe",
"params": ["miniBlocks"],
"id": 1
}Mini-block schema:
{
"payload_id": "0x...",
"block_number": 12345,
"index": 0,
"tx_offset": 0,
"log_offset": 0,
"gas_offset": 0,
"timestamp": 1704067200000,
"gas_used": 21000,
"transactions": [...],
"receipts": [...]
}Mini-blocks are ephemeral — cannot be fetched via RPC after assembly into blocks.
Send eth_chainId every 30 seconds to prevent disconnection:
const ws = new WebSocket('wss://mainnet.megaeth.com/ws');
const keepalive = setInterval(() => {
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'eth_chainId',
params: [],
id: Date.now()
}));
}, 30000);
ws.on('close', () => clearInterval(keepalive));- 50 WebSocket connections per VIP endpoint
- 10 subscriptions per connection (+1 on subscribe, -1 on unsubscribe)
eth_sendRawTransactioneth_sendRawTransactionSynceth_subscribe/eth_unsubscribeeth_chainId(for keepalive)
As of v2.0.14, Multicall is the preferred approach for batching eth_call requests.
With v2.0.14, eth_call is 2-10x faster. The EVM execution time is now a small fraction of total CPU time — most overhead is per-RPC framework cost. Multicall amortizes this overhead across multiple calls.
// ✅ Preferred: Multicall (v2.0.14+)
import { multicall } from 'viem/actions';
const results = await multicall(client, {
contracts: [
{ address: token1, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
{ address: token2, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
{ address: token3, abi: erc20Abi, functionName: 'balanceOf', args: [user] },
]
});Earlier MegaETH guidance recommended JSON-RPC batching over Multicall because:
- Multicall made it difficult to cache
eth_callresults effectively - Certain dApps benefited significantly from this caching
With v2.0.14's performance improvements, the caching mechanism is deprecated and Multicall is now preferred.
- Mixing slow + fast methods — Don't batch
eth_getLogswitheth_call; logs are always slower - Blocking UX on historical queries — Keep
eth_getLogsin background
Public endpoints have rate limits based on:
- CPU time consumption
- Network bandwidth (32 MB/s average, 15x burst for VIP)
These require VIP endpoints for heavy usage:
eth_getLogs(large block ranges)eth_call(complex contracts)debug_*/trace_*(disabled on public)
- Warm up connections — Call
eth_chainIdon app init to pre-establish HTTP connection (avoids DNS/TCP/TLS overhead on first real request) - Use Multicall for eth_call batching — Amortizes per-RPC overhead (v2.0.14+)
- Don't batch slow with fast —
eth_getLogsblocks entire batch response - Use cursors —
eth_getLogsWithCursorinstead of hugeeth_getLogs - Background historical queries — Never block UX waiting for logs
- Multi-block queries: max 20,000 logs per call
- Single-block queries: no limit
- Workaround: Get headers first to count logs, then build queries accordingly
Public endpoint supports eth_call on blocks from past ~15 days. For older data:
- Use Alchemy/QuickNode
- Run your own archive node
- Use indexers (Envio HyperSync recommended)
MegaETH's real-time capabilities shine when you optimize for low latency. Here's what matters:
| Method | HTTP | WebSocket |
|---|---|---|
| eth_chainId | 40ms | 7ms |
| eth_getBalance | 140ms | ~50ms |
| eth_sendRawTransactionSync | 200-450ms | 150-300ms |
WebSocket is 5-6x faster for simple calls due to persistent connection.
// ❌ Slow: HTTP per request (TCP + TLS handshake each time)
const result = await fetch(RPC_URL, { method: 'POST', ... });
// ✅ Fast: Persistent WebSocket connection
const ws = new WebSocket('wss://mainnet.megaeth.com/ws');
ws.send(JSON.stringify({ method: 'eth_sendRawTransactionSync', ... }));Don't sign in the hot path. Prepare transactions ahead of time:
// Pre-sign with sequential nonces
const nonce = await publicClient.getTransactionCount({ address });
const signedTxs = await Promise.all([
wallet.signTransaction({ ...baseTx, nonce: nonce }),
wallet.signTransaction({ ...baseTx, nonce: nonce + 1 }),
wallet.signTransaction({ ...baseTx, nonce: nonce + 2 }),
]);
// Later: fire instantly when needed
const receipt = await rpc('eth_sendRawTransactionSync', [signedTxs[0]]);Don't wait for confirmations between transactions:
// ❌ Slow: Wait for each confirmation
for (const tx of transactions) {
const receipt = await sendAndWait(tx);
}
// ✅ Fast: Pipeline with sequential nonces
let nonce = await getNonce(address);
const receipts = await Promise.all(
transactions.map((tx, i) =>
rpc('eth_sendRawTransactionSync', [
signTx({ ...tx, nonce: nonce + i })
])
)
);If using HTTP, ensure connection reuse:
import https from 'https';
const agent = new https.Agent({
keepAlive: true,
maxSockets: 10
});
// Reuse for all requests
fetch(RPC_URL, { agent, ... });| Setup | Latency |
|---|---|
| Cross-continent | 150-300ms RTT |
| Same region (cloud) | 20-50ms RTT |
| Local node | <5ms RTT |
For ultra-low latency:
- Use Alchemy/QuickNode geo-distributed endpoints
- Run your own node in the same datacenter as your app
// ❌ Slow: 3 sequential calls = 3x latency
const a = await rpc('eth_call', [params1]);
const b = await rpc('eth_call', [params2]);
const c = await rpc('eth_call', [params3]);
// ✅ Fast: Single batch = 1x latency
const [a, b, c] = await fetch(RPC_URL, {
body: JSON.stringify([
{ method: 'eth_call', params: [params1], id: 1 },
{ method: 'eth_call', params: [params2], id: 2 },
{ method: 'eth_call', params: [params3], id: 3 },
])
});With optimal setup (local node + WebSocket + pre-signed):
- Sign: ~1ms (pre-computed)
- Network to local node: ~1ms
- MegaETH processing: ~10ms
- Total: ~12ms
# Estimate gas
cast estimate \
--from 0xYourAddress \
--value 0.001ether \
0xRecipient \
--rpc-url https://mainnet.megaeth.com/rpc
# Get balance at block
cast call --block 12345 0xContract "balanceOf(address)(uint256)" 0xAddress \
--rpc-url https://mainnet.megaeth.com/rpc
# Trace transaction (requires VIP or local node)
cast run <txhash> --rpc-url <vip-endpoint>