Async cryptocurrency payment detection and wallet tracking for Python
Project description
pycryptoscan
Async cryptocurrency payment detection and wallet tracking for Python.
Unlike heavy full-node SDKs (web3.py, solana-py), CryptoScan does exactly one thing: it watches a wallet address and fires a Python callback when money arrives. Built for payment gateways, Telegram bots, and automated billing systems.
Key Capabilities
- Stablecoin Support: Native parsing for ERC-20 (USDT, USDC on EVM) and SPL tokens (Solana).
- Flexible Matching: Trigger on exact amounts, "at least X", or track ALL incoming transfers.
- Dual Engine: Uses lightweight WebSocket log subscriptions where possible (EVM), falls back to optimized HTTP polling for others (Solana, TRON, Bitcoin).
- Confirmation Control: Set
min_confirmationsto wait for block finality before triggering callbacks. - Enterprise Security: Built-in SSRF protection (blocks private IPs/metadata endpoints) and automated API key masking in logs.
- Resilient: Fully async (
httpx,asyncio) with exponential backoff and auto-reconnects viatenacity.
Installation
pip install pycryptoscan
For WebSocket-based real-time monitoring:
pip install pycryptoscan[realtime]
Quick Start
1. Basic Wallet Tracker (Listen to everything)
If you just want to track a wallet and be notified of any incoming transfer:
import asyncio
from cryptoscan import create_monitor, MatchMode
async def main():
monitor = create_monitor(
network="ethereum",
wallet_address="0xD45F36545b373585a2213427C12AD9af2bEFCE18",
match_mode=MatchMode.ANY
)
@monitor.on_payment
async def handle_payment(event):
info = event.payment_info
print(f"Received: {info.amount} {info.currency} from {info.from_address}")
print(f"TX: {info.transaction_id}")
try:
await monitor.start()
except KeyboardInterrupt:
await monitor.stop()
if __name__ == "__main__":
asyncio.run(main())
2. E-commerce Gateway (USDT/USDC Support)
Wait for a specific stablecoin payment (e.g., user needs to pay at least 50 USDT). The library will automatically scan Transfer logs instead of heavy blocks.
from cryptoscan import create_monitor, MatchMode, TokenConfig
usdt_config = TokenConfig(
contract_address="0xdAC17F958D2ee523a2206206994597C13D831ec7",
symbol="USDT",
decimals=6
)
monitor = create_monitor(
network="ethereum",
wallet_address="0xYOUR_MERCHANT_ADDRESS",
expected_amount="50.0",
match_mode=MatchMode.AT_LEAST,
token_contract=usdt_config,
auto_stop=True
)
@monitor.on_payment
async def release_product(event):
print("Payment confirmed! Releasing digital goods...")
3. Solana & SPL Tokens
CryptoScan supports Solana natively and can monitor SPL token transfers (e.g., USDC on Solana) by parsing preTokenBalances/postTokenBalances.
from cryptoscan import create_monitor, MatchMode, TokenConfig
usdc_solana = TokenConfig(
contract_address="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
symbol="USDC",
decimals=6
)
monitor = create_monitor(
network="solana",
wallet_address="9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
expected_amount="25.0",
match_mode=MatchMode.AT_LEAST,
token_contract=usdc_solana,
auto_stop=True
)
4. Bitcoin with Polling & Confirmations
For chains without WebSocket support (or when you prefer polling), force polling mode and set confirmation thresholds.
monitor = create_monitor(
network="bitcoin",
wallet_address="3DVSCqZdrNJHyu9Le7Sepdh1KgQTNR8reG",
expected_amount="0.00611813",
poll_interval=30.0,
min_confirmations=3,
realtime=False,
auto_stop=True
)
Supported Networks
The library includes pre-configurations for PublicNode endpoints, but you can inject ANY standard RPC.
| Protocol | Networks | Token Support | Method |
|---|---|---|---|
| EVM | Ethereum, BSC, Polygon (+ any EVM chain via custom config) | Native + ERC-20 | WebSockets (eth_subscribe logs) |
| SVM | Solana | Native + SPL | Polling (getSignaturesForAddress) |
| TRON | TRON | Native + TRC-20 | Polling (TRON Grid REST) |
| TON | TON | Native | Polling (TON Center REST) |
| Other | Bitcoin | Native only | Polling (RPC) |
Want to add any EVM-compatible chain? Just pass rpc_url/ws_url or a NetworkConfig — see below.
Custom Network Configuration
Direct RPC Injection
Pass custom endpoints directly to create_monitor() without registering anything:
monitor = create_monitor(
network="scroll",
wallet_address="0x...",
expected_amount="1.0",
rpc_url="https://scroll-rpc.publicnode.com",
ws_url="wss://scroll-rpc.publicnode.com"
)
You can also pass a NetworkConfig object directly:
from cryptoscan import create_monitor, NetworkConfig
my_chain = NetworkConfig(
name="my-chain",
symbol="ETH",
rpc_url="https://my-chain-rpc.example.com",
ws_url="wss://my-chain-rpc.example.com",
chain_type="evm",
decimals=18,
)
monitor = create_monitor(
network=my_chain,
wallet_address="0x...",
expected_amount="1.0",
)
Registering a Network
Register custom networks globally so they can be referenced by name (useful for private chains or testnets):
from cryptoscan import register_network, create_network_config
config = create_network_config(
name="local-testnet",
symbol="ETH",
rpc_url="http://localhost:8545",
chain_type="evm",
decimals=18,
)
register_network(config)
monitor = create_monitor("local-testnet", "0x...", "1.0")
Advanced: Proxies, Timeouts & Error Handling
Proxy and Connection Pooling
Use UserConfig to control connection pooling, timeouts, proxy routing, and WebSocket behavior.
from cryptoscan import create_monitor, UserConfig, ProxyConfig
proxy_settings = ProxyConfig(
https_proxy="http://10.10.1.10:3128",
proxy_auth="user:pass",
)
config = UserConfig(
proxy_config=proxy_settings,
timeout=10.0,
max_retries=5,
retry_delay=2.0,
connector_limit=20,
ssl_verify=True,
)
monitor = create_monitor(
network="solana",
wallet_address="...",
expected_amount="1.5",
user_config=config,
)
You can also pass timeout and max_retries directly to create_monitor() as shortcuts — they override the corresponding UserConfig fields:
monitor = create_monitor(
network="ethereum",
wallet_address="0x...",
expected_amount="1.0",
timeout=15.0,
max_retries=5,
)
Error Handling
The library provides domain-specific exceptions for fine-grained error handling. Strategies catch errors, emit ErrorEvent, and continue retrying — your callbacks are for logging and external integrations.
from cryptoscan import (
create_monitor, NetworkError, CSConnectionError,
CSTimeoutError, PaymentNotFoundError, RPCError,
)
monitor = create_monitor(...)
@monitor.on_error
async def on_error(event):
error = event.error
if isinstance(error, NetworkError):
print(f"Connection instability (auto-retrying): {error}")
elif isinstance(error, RPCError):
print(f"RPC error (code={error.code}): {error}")
else:
print(f"Unexpected error: {error}")
Full exception hierarchy:
CryptoScanError
├── NetworkError
│ ├── CSConnectionError
│ ├── CSTimeoutError
│ └── BlockFetchError
├── PaymentNotFoundError
├── ValidationError
├── RPCError
├── ParserError
└── AdapterError
Ad-Hoc Provider Access
Use get_provider() for direct RPC interaction without running a full monitor (e.g., balance checks, block queries):
import asyncio
from cryptoscan import get_provider
async def main():
provider = get_provider("ethereum")
await provider.connect()
try:
block = await provider.get_block_number()
print(f"Latest block: {block}")
finally:
await provider.close()
asyncio.run(main())
Metrics & Observability
If you are running CryptoScan in a long-lived billing microservice, you can collect per-method request metrics and export them as Prometheus text format:
from cryptoscan import enable_global_metrics, get_global_metrics
enable_global_metrics()
# ... monitor runs ...
collector = get_global_metrics()
summary = collector.get_summary()
print(f"Requests: {summary.total_requests} | Failed: {summary.failed_requests}")
print(f"Avg Latency: {summary.avg_response_time_ms:.2f}ms")
print(f"Error Rate: {summary.error_rate:.1%}")
print(collector.export_prometheus())
User Guides
Adding Litecoin (LTC), Dogecoin (DOGE), or Other Bitcoin-like Coins
Since Litecoin and Dogecoin are Bitcoin forks, their nodes expose the same RPC API. No custom parser is needed — just create a NetworkConfig with chain_type="bitcoin":
import asyncio
from cryptoscan import create_network_config, register_network, create_monitor, MatchMode
doge_config = create_network_config(
name="dogecoin",
symbol="DOGE",
rpc_url="https://rpc.your-doge-node.com",
chain_type="bitcoin",
decimals=8
)
ltc_config = create_network_config(
name="litecoin",
symbol="LTC",
rpc_url="https://rpc.your-ltc-node.com",
chain_type="bitcoin",
decimals=8
)
register_network(doge_config)
register_network(ltc_config)
async def main():
monitor = create_monitor(
network="dogecoin",
wallet_address="D7q...your_doge_address",
expected_amount="150.0",
match_mode=MatchMode.AT_LEAST,
realtime=False
)
@monitor.on_payment
async def on_payment(event):
info = event.payment_info
print(f"Received {info.amount} {info.currency}! TX: {info.transaction_id}")
await monitor.start()
if __name__ == "__main__":
asyncio.run(main())
Tracking Any ERC-20 or SPL Token (DAI, SHIB, PEPE, etc.)
The library supports any ERC-20 token (Ethereum, BSC, Polygon, etc.) and SPL token (Solana) via TokenConfig. Find the token's contract address and decimals on Etherscan or CoinMarketCap:
from cryptoscan import create_monitor, MatchMode, TokenConfig
pepe_token = TokenConfig(
contract_address="0x6982508145454Ce325dDbE47a25d4ec3d2311933",
symbol="PEPE",
decimals=18
)
monitor = create_monitor(
network="ethereum",
wallet_address="0xYOUR_WALLET_ADDRESS",
expected_amount="1000000.0",
match_mode=MatchMode.AT_LEAST,
token_contract=pepe_token,
auto_stop=True
)
Adding a Custom Blockchain Parser (Monero, Ripple, etc.)
For chains that are neither Bitcoin-like nor EVM-compatible, write a custom parser and register it:
from cryptoscan import ChainParser, register_parser, create_network_config
class RippleParser(ChainParser):
async def get_transactions(self, address, limit, expected_amount=None, match_mode=None, token_contract=None):
# Your XRP API logic here
pass
async def get_transaction(self, tx_id):
# Your XRP API logic here
pass
async def get_block_number(self):
return 0
async def get_block_for_payment(self, block_identifier, wallet_address, expected_amount, latest_block_num=None, match_mode=None, token_contract=None):
return None
register_parser("ripple", RippleParser)
xrp_net = create_network_config(
name="ripple_mainnet",
symbol="XRP",
rpc_url="...",
chain_type="ripple"
)
Security
CryptoScan includes built-in protections for production deployments:
- SSRF Protection
- Log Masking
- Response Limits: 1MB for WebSocket messages, 10MB for HTTP responses.
- SSL Verification: Enabled by default; can be disabled via
UserConfig(ssl_verify=False).
License
MIT License. See LICENSE for details.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pycryptoscan-2.1.0.tar.gz.
File metadata
- Download URL: pycryptoscan-2.1.0.tar.gz
- Upload date:
- Size: 50.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d59679b09768e5084adcf2c40763fa70c6a1f368c261b7a5fd1d4e2398e5b582
|
|
| MD5 |
600e8f80ec7d828b4ac7a1ee130a7a51
|
|
| BLAKE2b-256 |
1bdb42eb548ab9f67266c5322ffe4c7f6db476a7ef6675454a2f0e8a280c5732
|
File details
Details for the file pycryptoscan-2.1.0-py3-none-any.whl.
File metadata
- Download URL: pycryptoscan-2.1.0-py3-none-any.whl
- Upload date:
- Size: 66.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9365a1e93c667fb7166d6e4e47f6f40e8ba14e5f80737c2118f940982fcac2e
|
|
| MD5 |
c7e67f3be86b7b7022056bbd307ecf78
|
|
| BLAKE2b-256 |
81dbdefff70c25142b3c0163afa195dc8597d5bc4b3bdfd1659656a140b5a52a
|