Solana

Solana Beta is live. Try BoltRPC Solana endpoints free - start your trial now.

How to Connect to Ethereum with web3.py: RPC Configuration Guide

Connect web3.py to a custom RPC endpoint. Covers HTTP and WebSocket providers, contract reads, event filtering, async support, and production configuration patterns.

BoltRPC
BoltRPC Team
6 min read
How to Connect to Ethereum with web3.py: RPC Configuration Guide

How to Connect to Ethereum with web3.py: RPC Configuration Guide

web3.py is the standard Python library for Ethereum. If you are building data pipelines, analytics scripts, trading bots, or backend services in Python, web3.py is how you connect to the blockchain.

By default, tutorials point you at public endpoints that get rate-limited immediately under any real workload. This guide shows how to configure web3.py properly with a dedicated RPC endpoint.


Install web3.py

pip install web3

Requires Python 3.7+. For async support, install the extras:

pip install "web3[asyncio]"

HTTP Provider

The HTTP provider handles all standard JSON-RPC calls: reading blocks, balances, contract state.

from web3 import Web3

w3 = Web3(Web3.HTTPProvider(
    'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
))

# Verify connection
print(w3.is_connected())  # True

# Latest block
block = w3.eth.get_block('latest')
print(f"Latest block: {block.number}")

# ETH balance
balance = w3.eth.get_balance('0xYourAddress')
print(f"Balance: {w3.from_wei(balance, 'ether')} ETH")

WebSocket Provider

For event subscriptions and real-time data, use the WebSocket provider:

from web3 import Web3

w3 = Web3(Web3.WebsocketProvider(
    'wss://eu.endpoints.matrixed.link/ws/ethereum?auth=YOUR_KEY'
))

print(w3.is_connected())  # True

WebSocket is required for eth_subscribe calls (new blocks, pending transactions, contract logs).


Async HTTP Provider

For production scripts that need concurrent requests:

import asyncio
from web3 import AsyncWeb3

async def main():
    w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(
        'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
    ))

    block = await w3.eth.get_block('latest')
    print(f"Latest block: {block.number}")

    # Concurrent requests
    block_num, gas_price, chain_id = await asyncio.gather(
        w3.eth.block_number,
        w3.eth.gas_price,
        w3.eth.chain_id,
    )
    print(f"Block: {block_num}, Gas: {gas_price}, Chain: {chain_id}")

asyncio.run(main())

Request Session Configuration

For production use, configure connection pooling and timeouts:

from web3 import Web3
from web3.middleware import ExtraDataToPOAMiddleware
from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# Configure retry + connection pool
session = Session()
adapter = HTTPAdapter(
    pool_connections=10,
    pool_maxsize=20,
    max_retries=Retry(
        total=3,
        backoff_factor=0.5,
        status_forcelist=[429, 500, 502, 503, 504],
    )
)
session.mount('https://', adapter)

w3 = Web3(Web3.HTTPProvider(
    'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY',
    request_kwargs={
        'timeout': 30,
        'session': session,
    }
))

For POA chains (Polygon, BNB Chain), inject the POA middleware:

# Required for Polygon, BNB Chain, and other POA networks
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)

Read Contract Data

from web3 import Web3

w3 = Web3(Web3.HTTPProvider(
    'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
))

ERC20_ABI = [
    {
        "inputs": [{"name": "account", "type": "address"}],
        "name": "balanceOf",
        "outputs": [{"name": "", "type": "uint256"}],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "inputs": [],
        "name": "decimals",
        "outputs": [{"name": "", "type": "uint8"}],
        "stateMutability": "view",
        "type": "function",
    },
    {
        "inputs": [],
        "name": "symbol",
        "outputs": [{"name": "", "type": "string"}],
        "stateMutability": "view",
        "type": "function",
    },
]

USDC_ADDRESS = Web3.to_checksum_address('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')
contract = w3.eth.contract(address=USDC_ADDRESS, abi=ERC20_ABI)

# Read contract state
symbol = contract.functions.symbol().call()
decimals = contract.functions.decimals().call()
balance = contract.functions.balanceOf('0xYourAddress').call()

print(f"{symbol}: {balance / 10**decimals}")

Send a Transaction

from web3 import Web3

w3 = Web3(Web3.HTTPProvider(
    'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
))

private_key = 'YOUR_PRIVATE_KEY'
account = w3.eth.account.from_key(private_key)

# Build transaction
nonce = w3.eth.get_transaction_count(account.address)
fee_data = w3.eth.fee_history(1, 'latest', [50])
base_fee = fee_data['baseFeePerGas'][-1]
priority_fee = w3.to_wei(1, 'gwei')

tx = {
    'chainId': 1,
    'from': account.address,
    'to': '0xRecipientAddress',
    'value': w3.to_wei(0.001, 'ether'),
    'gas': 21000,
    'maxFeePerGas': base_fee * 2 + priority_fee,
    'maxPriorityFeePerGas': priority_fee,
    'nonce': nonce,
    'type': 2,  # EIP-1559
}

# Sign and send
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Confirmed in block {receipt.blockNumber}")

Filter Events with eth_getLogs

from web3 import Web3

w3 = Web3(Web3.HTTPProvider(
    'https://eu.endpoints.matrixed.link/rpc/ethereum?auth=YOUR_KEY'
))

TRANSFER_TOPIC = Web3.keccak(text='Transfer(address,address,uint256)').hex()
USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'

current_block = w3.eth.block_number

# Get USDC transfers in the last 1,000 blocks
logs = w3.eth.get_logs({
    'address': Web3.to_checksum_address(USDC_ADDRESS),
    'topics': [TRANSFER_TOPIC],
    'fromBlock': current_block - 1000,
    'toBlock': 'latest',
})

print(f"Found {len(logs)} USDC Transfer events")
for log in logs[:5]:
    print(f"  TX: {log.transactionHash.hex()}, Block: {log.blockNumber}")

For large block ranges, chunk requests to avoid provider limits:

def get_logs_chunked(w3, filter_params, chunk_size=2000):
    all_logs = []
    from_block = filter_params['fromBlock']
    to_block = filter_params['toBlock']

    current = from_block
    while current <= to_block:
        end = min(current + chunk_size - 1, to_block)
        try:
            logs = w3.eth.get_logs({**filter_params, 'fromBlock': current, 'toBlock': end})
            all_logs.extend(logs)
            current = end + 1
        except Exception as e:
            if 'block range' in str(e).lower() or 'too many' in str(e).lower():
                chunk_size = chunk_size // 2
                if chunk_size < 10:
                    raise
                continue
            raise
    return all_logs

WebSocket Subscriptions

import asyncio
from web3 import AsyncWeb3

async def watch_new_blocks():
    w3 = AsyncWeb3(AsyncWeb3.AsyncWebsocketProvider(
        'wss://eu.endpoints.matrixed.link/ws/ethereum?auth=YOUR_KEY'
    ))

    subscription_id = await w3.eth.subscribe('newHeads')
    print(f"Subscribed to new blocks: {subscription_id}")

    async for response in w3.socket.process_subscriptions():
        block = response['result']
        print(f"New block: {block['number']} | Hash: {block['hash'].hex()}")

asyncio.run(watch_new_blocks())

Subscribe to contract logs in real time:

async def watch_usdc_transfers():
    w3 = AsyncWeb3(AsyncWeb3.AsyncWebsocketProvider(
        'wss://eu.endpoints.matrixed.link/ws/ethereum?auth=YOUR_KEY'
    ))

    TRANSFER_TOPIC = '0x' + Web3.keccak(text='Transfer(address,address,uint256)').hex()

    subscription_id = await w3.eth.subscribe('logs', {
        'address': USDC_ADDRESS,
        'topics': [TRANSFER_TOPIC],
    })

    async for response in w3.socket.process_subscriptions():
        log = response['result']
        print(f"USDC Transfer — TX: {log['transactionHash'].hex()}")

Multi-Chain Configuration

from web3 import Web3

RPC_BASE = 'https://eu.endpoints.matrixed.link/rpc'
AUTH = 'YOUR_KEY'

chains = {
    'ethereum': Web3(Web3.HTTPProvider(f'{RPC_BASE}/ethereum?auth={AUTH}')),
    'polygon':  Web3(Web3.HTTPProvider(f'{RPC_BASE}/polygon?auth={AUTH}')),
    'arbitrum': Web3(Web3.HTTPProvider(f'{RPC_BASE}/arbitrum?auth={AUTH}')),
    'base':     Web3(Web3.HTTPProvider(f'{RPC_BASE}/base?auth={AUTH}')),
}

# Apply POA middleware to POA chains
from web3.middleware import ExtraDataToPOAMiddleware
chains['polygon'].middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)

# Query any chain
for name, w3 in chains.items():
    block = w3.eth.block_number
    print(f"{name}: block {block}")

Environment Variables

import os
from web3 import Web3

# Load from environment, never hardcode
BOLTRPC_KEY = os.environ['BOLTRPC_KEY']

w3 = Web3(Web3.HTTPProvider(
    f'https://eu.endpoints.matrixed.link/rpc/ethereum?auth={BOLTRPC_KEY}'
))

With python-dotenv:

pip install python-dotenv
from dotenv import load_dotenv
import os
from web3 import Web3

load_dotenv()

w3 = Web3(Web3.HTTPProvider(
    f'https://eu.endpoints.matrixed.link/rpc/ethereum?auth={os.environ["BOLTRPC_KEY"]}'
))

FAQ

Does web3.py work with all EVM chains?

Yes. web3.py connects to any EVM-compatible JSON-RPC endpoint: Ethereum, Polygon, Arbitrum, Base, Optimism, BNB Chain, Avalanche, and others. For POA chains (Polygon, BNB Chain), inject ExtraDataToPOAMiddleware as shown above.

What is the difference between Web3.HTTPProvider and Web3.WebsocketProvider?

HTTP is request-response: you send a query and get a response. WebSocket is persistent and bidirectional: you can subscribe to events and receive data as it arrives. Use HTTP for reading data; use WebSocket when you need real-time subscriptions to new blocks or contract events.

How do I handle rate limit errors in web3.py?

Configure HTTPAdapter with Retry as shown above. Set status_forcelist=[429, 500, 502, 503, 504] and backoff_factor=0.5 for exponential backoff. On a dedicated RPC plan with flat-rate pricing, rate limits are far less likely than on public endpoints.

Is web3.py thread-safe?

The HTTPProvider can be used across threads if you configure connection pooling. The WebsocketProvider is not thread-safe. Use asyncio instead. For multi-threaded scripts, create one Web3 instance per thread or use a connection pool.

Can I use web3.py with Django or FastAPI?

Yes. For Django (synchronous), use Web3(Web3.HTTPProvider(...)) directly. For FastAPI (async), use AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(...)) and await calls from async route handlers. Avoid blocking web3.py calls inside async handlers. They will block the event loop.


BoltRPC provides HTTP and WebSocket endpoints for Ethereum, Polygon, Arbitrum, Base and 17 other networks, all accessible with the same web3.py configuration pattern.

Start your free 2-week trial: trial.boltrpc.io

Related: How to Connect Ethereum with ethers.js | Optimizing RPC Calls for DeFi

Frequently asked questions

Ready to build with high-performance RPC?

Start your free trial today. No credit card required. Access 20+ networks instantly.

Disclaimer: The content in this article is for informational purposes only and does not constitute financial, legal, or technical advice. Code examples and configurations are provided as-is. Always verify information with official documentation and test thoroughly in your own environment before deploying to production.

Continue reading