Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Looking for Contributors! This Skill needs an implementation.Contributing a Skill involves:
- Writing a reference implementation with full functionality
- Adding comprehensive tests
- Writing documentation with usage examples
See the ethers-provider Skill for an example of a complete Skill implementation.Interested? Open an issue or PR at github.com/evmts/voltaire.
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.
Build your own event indexer. Fetch, decode, and store blockchain events locally for fast queries without relying on external indexing services.
Why Build Your Own Indexer?
Third-party indexers (The Graph, Dune, etc.) are great but come with trade-offs:
- Latency — Indexing delay before data is available
- Cost — Query costs at scale
- Dependencies — Service outages affect your app
- Limitations — May not support your specific queries
A local indexer gives you:
- Instant access — Query your own database
- Full control — Custom schemas, any query pattern
- No dependencies — Works offline, no API limits
- Cost effective — One-time sync, unlimited queries
Planned Implementation
Basic Indexer
const indexer = EventIndexer({
provider,
storage: new SQLiteStorage('./events.db'), // or PostgresStorage, etc.
contracts: [
{
address: USDC_ADDRESS,
abi: ERC20_ABI,
events: ['Transfer', 'Approval'],
startBlock: 17000000,
},
{
address: UNISWAP_ROUTER,
abi: ROUTER_ABI,
events: ['Swap'],
startBlock: 17000000,
}
]
});
// Start indexing (runs in background)
await indexer.start();
// Query indexed events
const transfers = await indexer.query({
event: 'Transfer',
where: {
from: myAddress,
blockNumber: { gte: 18000000 }
},
orderBy: 'blockNumber',
limit: 100
});
Incremental Sync
const indexer = EventIndexer({
provider,
storage,
contracts: [...],
sync: {
// Batch size for historical sync
batchSize: 2000,
// Confirmations before considering final
confirmations: 12,
// Poll interval for new blocks
pollInterval: 12000,
// Resume from last indexed block
resume: true,
}
});
// Get sync status
const status = indexer.getStatus();
// { indexed: 18500000, latest: 18500100, syncing: true, progress: 0.99 }
Custom Event Handlers
const indexer = EventIndexer({
provider,
storage,
contracts: [{
address: UNISWAP_POOL,
abi: POOL_ABI,
events: ['Swap'],
// Transform event before storing
transform: (event) => ({
...event,
priceUSD: calculatePrice(event.args.sqrtPriceX96),
volumeUSD: calculateVolume(event.args.amount0, event.args.amount1),
}),
// Filter which events to index
filter: (event) => event.args.amount0 > 0n,
}]
});
Query API
// Simple queries
const events = await indexer.query({
event: 'Transfer',
where: { to: myAddress },
limit: 50
});
// Aggregations
const volume = await indexer.aggregate({
event: 'Swap',
field: 'volumeUSD',
operation: 'sum',
groupBy: 'day',
where: { blockNumber: { gte: startBlock } }
});
// Raw SQL (if using SQL storage)
const result = await indexer.raw(`
SELECT DATE(timestamp) as day, SUM(amount) as volume
FROM transfers
WHERE token = ?
GROUP BY day
`, [USDC_ADDRESS]);
Storage Backends
// SQLite (simple, local)
import { SQLiteStorage } from './storage/sqlite.js';
const storage = new SQLiteStorage('./events.db');
// PostgreSQL (production, scalable)
import { PostgresStorage } from './storage/postgres.js';
const storage = new PostgresStorage('postgres://...');
// In-Memory (testing, temporary)
import { MemoryStorage } from './storage/memory.js';
const storage = new MemoryStorage();
// Custom storage (implement interface)
const storage = {
async write(events) { ... },
async query(filter) { ... },
async getLastBlock() { ... },
};
Use Cases
Token Transfer History
// Index all transfers for a token
const indexer = EventIndexer({
contracts: [{
address: TOKEN_ADDRESS,
abi: ERC20_ABI,
events: ['Transfer'],
startBlock: deploymentBlock,
}]
});
// Query user's transfer history
const history = await indexer.query({
event: 'Transfer',
where: {
$or: [{ from: userAddress }, { to: userAddress }]
},
orderBy: { blockNumber: 'desc' }
});
DEX Analytics
// Index Uniswap swaps
const indexer = EventIndexer({
contracts: [{
address: POOL_ADDRESS,
abi: POOL_ABI,
events: ['Swap'],
transform: (e) => ({
...e,
price: sqrtPriceX96ToPrice(e.args.sqrtPriceX96),
volume: calculateVolume(e.args),
})
}]
});
// OHLCV candles
const candles = await indexer.aggregate({
event: 'Swap',
groupBy: 'hour',
select: {
open: { field: 'price', operation: 'first' },
high: { field: 'price', operation: 'max' },
low: { field: 'price', operation: 'min' },
close: { field: 'price', operation: 'last' },
volume: { field: 'volume', operation: 'sum' },
}
});
NFT Ownership Tracking
// Index NFT transfers to track ownership
const indexer = EventIndexer({
contracts: [{
address: NFT_ADDRESS,
abi: ERC721_ABI,
events: ['Transfer'],
}]
});
// Get current owner of token
const transfers = await indexer.query({
event: 'Transfer',
where: { tokenId: 1234 },
orderBy: { blockNumber: 'desc' },
limit: 1
});
const currentOwner = transfers[0]?.args.to;