Shared Providers¶
ProviderSet is the key to sharing rate limits and credentials across
multiple clients. When multiple clients share a ProviderSet, identical
(provider, api_key) pairs automatically share one rate limit budget.
Multi-chain with shared rate limits¶
import asyncio
from blockparty import (
AsyncBlockpartyClient,
ProviderSet,
ProviderCredential,
EtherscanTier,
RoutescanTier,
)
providers = ProviderSet([
ProviderCredential(
type="etherscan",
api_key="YOUR_KEY",
tier=EtherscanTier.STANDARD,
),
ProviderCredential(type="routescan", tier=RoutescanTier.ANONYMOUS),
ProviderCredential(type="blockscout"),
])
# All clients share one rate limit budget for ("etherscan", "YOUR_KEY")
clients = {
"base": AsyncBlockpartyClient(chain_id=8453, providers=providers),
"arbitrum": AsyncBlockpartyClient(chain_id=42161, providers=providers),
"optimism": AsyncBlockpartyClient(chain_id=10, providers=providers),
}
results = await asyncio.gather(*[
c.get_internal_transactions(address="0x...")
for c in clients.values()
])
for c in clients.values():
await c.close()
Per-chain API keys¶
providers = ProviderSet([
# Premium key for Base and Optimism only
ProviderCredential(
type="etherscan",
api_key="PREMIUM_KEY",
tier=EtherscanTier.STANDARD,
chain_ids=frozenset({8453, 10}),
),
# Free key for everything else
ProviderCredential(
type="etherscan",
api_key="FREE_KEY",
tier=EtherscanTier.FREE,
),
# Anonymous fallbacks
ProviderCredential(type="routescan"),
ProviderCredential(type="blockscout"),
])
When a client for chain 8453 runs, it tries PREMIUM_KEY first (matches via
chain_ids), then FREE_KEY (matches all chains), then Routescan, then
Blockscout. A client for chain 42161 skips PREMIUM_KEY (not in its
chain_ids) and starts with FREE_KEY.
Provider fallback¶
Every client with multiple providers in its ProviderSet automatically
falls back on transient errors (rate limits, 5xx, auth failures):
# If Etherscan rate-limits or is down, Routescan is tried next
async with AsyncBlockpartyClient(chain_id=8453, providers=providers) as client:
response = await client.get_internal_transactions(address="0x...")
Non-recoverable errors (invalid address, premium-only endpoint) raise immediately without trying other providers.
Every response includes response.provider indicating which explorer
actually served the data. See Frontend URL Builder for how to generate URLs matching
the actual provider.
Handling fallback warnings¶
import warnings
import logging
from blockparty import FallbackWarning
# Route warnings to the logging system
logging.captureWarnings(True)
# Or escalate to exceptions
warnings.filterwarnings("error", category=FallbackWarning)