Skip to main content

EVM-compatible blockchain data reader with intelligent RPC provider management, automatic failover, and smart caching

Project description

ChainReader

CI codecov Python 3.9+ License: MIT

EVM-Compatible Blockchain Data Reader with Intelligent RPC Provider Management

ChainReader is a Python library for reliably fetching on-chain data from EVM-compatible blockchains with automatic failover, smart caching, and provider health tracking.

Features

Phase 1 (Current) ✅

  • Multi-Provider Management: Configure multiple RPC endpoints with automatic failover
  • Intelligent Caching: In-memory caching with smart TTL for different data types
  • Provider Health Tracking: Automatic detection and recovery of failed providers
  • Retry Logic: Exponential backoff with configurable retry attempts
  • EVM Compatible: Works with Ethereum, Polygon, BSC, Arbitrum, and any EVM chain

Installation

pip install -e .

For development:

pip install -e ".[dev]"

Quick Start

import asyncio
from chainreader import ChainReader

async def main():
    # Initialize with multiple providers
    reader = ChainReader(
        chain_id=137,  # Polygon mainnet
        providers=[
            {'name': 'infura', 'url': 'https://polygon-mainnet.infura.io/v3/YOUR_KEY'},
            {'name': 'alchemy', 'url': 'https://polygon-mainnet.g.alchemy.com/v2/YOUR_KEY'},
            {'name': 'public', 'url': 'https://polygon-rpc.com'}
        ]
    )

    async with reader:
        # Get account balance
        balance = await reader.get_balance('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb')
        print(f"Balance: {balance / 10**18} MATIC")

        # Get latest block
        block = await reader.get_block('latest')
        print(f"Block number: {block['number']}")

        # Call contract method
        result = await reader.call_contract(
            address='0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174',  # USDC on Polygon
            abi=usdc_abi,
            method='balanceOf',
            args=['0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb']
        )
        print(f"USDC Balance: {result / 10**6}")

if __name__ == "__main__":
    asyncio.run(main())

Usage

Initialize ChainReader

from chainreader import ChainReader

reader = ChainReader(
    chain_id=137,  # Network chain ID
    providers=[
        {'name': 'provider1', 'url': 'https://rpc1.example.com', 'priority': 1},
        {'name': 'provider2', 'url': 'https://rpc2.example.com', 'priority': 2},
    ],

    # Cache configuration
    cache_ttl_blocks=12,      # TTL for recent data (seconds)
    cache_ttl_latest=5,       # TTL for 'latest' queries (seconds)
    max_cache_size=10000,     # Maximum cache entries

    # Retry configuration
    max_retries=3,            # Max retry attempts
    retry_backoff_factor=2.0, # Exponential backoff factor

    # Provider management
    failover_threshold=3,     # Failures before marking unhealthy
    health_check_cooldown=300,# Seconds before re-enabling provider

    # Request settings
    request_timeout=30,       # RPC request timeout (seconds)
    log_level='INFO'          # Logging level
)

Available Methods

Get Balance

balance = await reader.get_balance(
    address='0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
    block='latest'  # or block number
)

Get Block

block = await reader.get_block('latest')
# or
block = await reader.get_block(12345678)
# or
block = await reader.get_block('0xabcdef...')

Get Transaction

tx = await reader.get_transaction('0x1234...')

Get Transaction Receipt

receipt = await reader.get_transaction_receipt('0x1234...')

Call Contract Method

result = await reader.call_contract(
    address='0x...',
    abi=[...],
    method='balanceOf',
    args=['0x...'],
    block='latest'
)

Get Event Logs

logs = await reader.get_logs(
    address='0x...',
    topics=['0x...'],  # Event signature
    from_block=1000000,
    to_block=1001000
)

Get Current Block Number

block_number = await reader.get_block_number()

Monitoring

Provider Statistics

stats = reader.get_provider_stats()
# Returns:
# {
#     'provider1': {
#         'is_healthy': True,
#         'success_count': 42,
#         'failure_count': 1,
#         'success_rate': 0.976,
#         'average_latency': 0.234,
#         ...
#     },
#     ...
# }

Cache Statistics

stats = reader.get_cache_stats()
# Returns:
# {
#     'hits': 150,
#     'misses': 50,
#     'hit_rate': 0.75,
#     'size': 234
# }

Caching Strategy

ChainReader automatically applies intelligent caching based on data type:

  • Permanent: Transaction receipts, historical blocks (>12 blocks old), historical contract calls
  • Short TTL (5s): Latest block queries, current state
  • Medium TTL (12s): Recent blocks, recent contract calls
  • No cache: Pending transactions, current mempool

Provider Failover

ChainReader automatically handles provider failures:

  1. Health Tracking: Each provider's success/failure rate is monitored
  2. Automatic Failover: Failed requests automatically retry with the next provider
  3. Priority-based Selection: Providers are tried in priority order (lower number = higher priority)
  4. Auto Recovery: Failed providers are re-enabled after a cooldown period

Examples

See the examples directory for complete examples:

Development

Setup

# Clone repository
git clone https://github.com/TickTockBent/chainreader.git
cd chainreader

# Install with dev dependencies
pip install -e ".[dev]"

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=chainreader --cov-report=html

# Run specific test file
pytest tests/test_provider_manager.py

Code Quality

# Format code
black chainreader tests

# Lint code
ruff check chainreader tests

# Type checking
mypy chainreader

Supported Chains

ChainReader works with any EVM-compatible blockchain:

  • Ethereum (chain_id: 1)
  • Polygon (chain_id: 137)
  • BSC (chain_id: 56)
  • Arbitrum (chain_id: 42161)
  • Optimism (chain_id: 10)
  • Avalanche C-Chain (chain_id: 43114)
  • And many more...

Error Handling

ChainReader provides specific exceptions for different error cases:

from chainreader import (
    ChainReaderError,           # Base exception
    AllProvidersFailedError,    # All providers failed
    ProviderError,              # Single provider error
    RateLimitError,             # Rate limit exceeded
    InvalidAddressError,        # Invalid address format
    ContractCallError,          # Contract call failed
)

try:
    balance = await reader.get_balance('0x...')
except InvalidAddressError:
    print("Invalid address format")
except AllProvidersFailedError:
    print("All RPC providers are down")
except ChainReaderError as e:
    print(f"ChainReader error: {e}")

Roadmap

Phase 2 (Planned)

  • SQLite cache backend for persistent caching
  • Redis cache backend for shared caching
  • Advanced cache warming and prefetching

Phase 3 (Planned)

  • Request batching for eth_call
  • Request deduplication
  • Adaptive rate limiting
  • Priority queue for requests

Phase 4 (Planned)

  • Comprehensive testing (>80% coverage)
  • Performance benchmarks
  • Production-ready documentation

Phase 5 (Planned)

  • FastAPI service layer
  • WebSocket event subscriptions
  • Admin dashboard
  • Docker deployment

Contributing

Contributions are welcome! Areas where we'd love help:

  • Additional chain support and testing
  • New cache backends
  • Performance optimizations
  • Documentation improvements
  • Bug fixes

License

MIT License - Free for commercial and personal use.

Acknowledgments

Built for use in:


Note: This is a Phase 1 release. Features like SQLite/Redis caching, request batching, and rate limiting are planned for future releases.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chainreader-0.1.0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chainreader-0.1.0-py3-none-any.whl (19.7 kB view details)

Uploaded Python 3

File details

Details for the file chainreader-0.1.0.tar.gz.

File metadata

  • Download URL: chainreader-0.1.0.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for chainreader-0.1.0.tar.gz
Algorithm Hash digest
SHA256 cde5d35681e49676949b0539063e379ece517d5b40a1f2851e9ca379e2fbe971
MD5 c45a49e72ef35c53f1ebf7314dc24608
BLAKE2b-256 5ea064a3d4a12b1c75108aad593c6261752d6ec1f2ccca75996e56244f73e715

See more details on using hashes here.

File details

Details for the file chainreader-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: chainreader-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for chainreader-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 632793b64f24402c15a3f5a9f6cdea8b380601368d625ff28102fe00992f6cb9
MD5 364c21b68f3ba68651212b80d49ed2d8
BLAKE2b-256 d4f518fb54c14823324c7724b32e9c92d78f0103b2be77242faa927809a2ba8a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page