Custom Chain Implementation
Referenced Files in This Document - base.py - manager.py - w3.py - ethereum.py - bsc.py - anvil.py - config.py - chains.yaml - scanner.py - sweeper.py - server.py - ERC20.json
Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
Introduction
This document explains how to implement custom blockchain chains in the cTrip Payment Gateway. It focuses on the BlockchainBase class architecture, inheritance patterns, and the factory-based blockchain manager. You will learn how to configure RPC providers, manage gas pricing (including EIP-1559), build transactions, enable POA middleware, and integrate new chains using the existing factory pattern. Practical examples show how to extend the base class for new networks, configure chain parameters, and handle common issues such as gas estimation failures, connectivity problems, and signing variations across EVM-compatible networks.
Project Structure
The blockchain subsystem is organized around a shared base class with specialized subclasses for supported networks, a configuration-driven factory, and convenience wrappers for Web3 access.
(base.py)"] E["EthereumBlockchain
(ethereum.py)"] S["BSCBlockchain
(bsc.py)"] A["AnvilBlockchain
(anvil.py)"] M["get_blockchains()
(manager.py)"] W["get_w3()
(w3.py)"] end subgraph "Configuration" C["Settings
(config.py)"] Y["chains.yaml"] end subgraph "Services" Scn["ScannerService
(scanner.py)"] Swp["SweeperService
(sweeper.py)"] end subgraph "App" Srv["FastAPI lifespan
(server.py)"] end C --> M Y --> C M --> E M --> S M --> A M --> B W --> M Scn --> W Swp --> W Srv --> M
Diagram sources - base.py - ethereum.py - bsc.py - anvil.py - manager.py - w3.py - config.py - chains.yaml - scanner.py - sweeper.py - server.py
Section sources - base.py - manager.py - w3.py - config.py - chains.yaml - scanner.py - sweeper.py - server.py
Core Components
- BlockchainBase: Shared async interface for RPC connectivity, gas management, transaction building, and receipts. Supports POA middleware injection and EIP-1559 fee calculation fallback.
- Specialized chains:
- EthereumBlockchain: Sets chain ID for Ethereum Mainnet and disables POA.
- BSCBlockchain: Sets chain ID for BSC Mainnet and enables POA middleware.
- AnvilBlockchain: Adds developer utilities (mining, impersonation, balance manipulation) while inheriting base capabilities.
- Factory and access:
- get_blockchains(): Builds a registry from configuration, instantiating specialized or generic base classes per chain.
- get_w3(): Provides AsyncWeb3 instances keyed by chain name.
- Configuration:
- Settings: Loads chains from chains.yaml and validates secrets.
- chains.yaml: Declares chain name, RPC URL, and optional token metadata.
Key capabilities: - Connectivity checks and balance queries (native and ERC20). - Gas caching and estimation with safe defaults. - Transaction builder with EIP-1559 or legacy gas pricing. - Receipt polling with timeouts.
Section sources - base.py - ethereum.py - bsc.py - anvil.py - manager.py - w3.py - config.py - chains.yaml
Architecture Overview
The system initializes blockchain clients during app startup, registers them in app.state, and exposes them via get_w3(). Services consume these clients to scan blocks, detect payments, and perform sweeping.
(server.py)" participant Manager as "get_blockchains()
(manager.py)" participant Factory as "Blockchain Subclasses
(ethereum.py, bsc.py, anvil.py)" participant Base as "BlockchainBase
(base.py)" participant Access as "get_w3()
(w3.py)" App->>Manager : Initialize on startup Manager->>Factory : Instantiate per chain (if specialized) Manager->>Base : Instantiate generic base class Manager-->>App : Dict of chain_name -> client App->>Access : get_w3(chain_name) Access-->>App : AsyncWeb3 instance
Diagram sources - server.py - manager.py - ethereum.py - bsc.py - anvil.py - base.py - w3.py
Detailed Component Analysis
BlockchainBase Class
BlockchainBase encapsulates: - AsyncWeb3 initialization with configurable timeout. - Optional POA middleware injection. - Gas cache with duration control. - EIP-1559 fee history retrieval and fallback to legacy gas pricing. - Transaction building with nonce resolution, chain ID, and gas limits. - Transaction signing and receipt polling.
Diagram sources - base.py
Section sources - base.py
Specialized Chain Classes
- EthereumBlockchain: Hardcodes chain ID for Ethereum Mainnet and disables POA.
- BSCBlockchain: Hardcodes chain ID for BSC Mainnet and enables POA middleware.
- AnvilBlockchain: Inherits base behavior and adds developer utilities for mining, impersonation, and balance manipulation.
Diagram sources - base.py - ethereum.py - bsc.py - anvil.py
Section sources - ethereum.py - bsc.py - anvil.py
Factory Pattern and Configuration
The factory builds a registry of blockchain clients from configuration. If a specialized subclass exists for a chain, it is instantiated; otherwise, a generic base class is used. A fallback ensures at least a local Anvil client is available when configuration is missing.
ethereum | bsc | anvil"} Match --> |Yes| NewSpec["Instantiate specialized subclass"] Match --> |No| NewBase["Instantiate BlockchainBase"] NewSpec --> Register["Add to registry"] NewBase --> Register Skip --> Iterate Iterate --> Done{"Registry empty?"} Done --> |Yes| Fallback["Registry remains empty"] Done --> |No| End(["Ready"]) Fallback --> End
Diagram sources - manager.py - config.py
Section sources - manager.py - config.py - chains.yaml
Web3 Access Wrapper
The get_w3() function retrieves a configured AsyncWeb3 instance by chain name, raising an error if the chain is not configured.
(w3.py)" participant Reg as "_blockchains registry" Caller->>W3 : get_w3(chain_name) W3->>Reg : lookup(chain_name) alt Found Reg-->>W3 : BlockchainBase instance W3-->>Caller : client.w3 else Not found W3-->>Caller : raises ValueError end
Diagram sources - w3.py - manager.py
Section sources - w3.py
Transaction Building and Gas Management
The build_transaction method: - Resolves checksum addresses and nonce. - Attempts EIP-1559 pricing using fee_history and sets maxFeePerGas and maxPriorityFeePerGas. - Falls back to legacy gasPrice if fee_history fails. - Estimates gas with a safety margin and applies a gas limit multiplier. - Returns a complete transaction dict ready for signing.
nonce, from, to, value, data, chainId"] BuildTx --> TryEIP1559{"Fee history available?"} TryEIP1559 --> |Yes| Set1559["Set maxFeePerGas and maxPriorityFeePerGas"] TryEIP1559 --> |No| LegacyGas["Set gasPrice"] Set1559 --> EstimateGas["Estimate gas (safe fallback)"] LegacyGas --> EstimateGas EstimateGas --> ApplyBuffer["Multiply gas by 1.1"] ApplyBuffer --> Return(["Return tx dict"])
Diagram sources - base.py
Section sources - base.py
POA Middleware Integration
POA middleware is injected automatically when use_poa is enabled during initialization. BSC uses POA, so its constructor enables it.
Diagram sources - bsc.py - base.py
Section sources - bsc.py - base.py
EIP-1559 Fee Calculation Integration
Fee history is fetched and used to compute suggested prices. If unavailable, legacy gas pricing is used as a fallback.
Diagram sources - base.py
Section sources - base.py
Service Integration Examples
- ScannerService scans blocks for native and ERC20 payments using get_w3(chain_name).
- SweeperService uses get_w3(chain_name) and settings.private_key to settle funds.
Diagram sources - scanner.py - w3.py
Section sources - scanner.py - sweeper.py - w3.py
Dependency Analysis
The following diagram shows module-level dependencies among blockchain components and configuration.
Diagram sources - config.py - chains.yaml - manager.py - w3.py - base.py - ethereum.py - bsc.py - anvil.py - scanner.py - sweeper.py - server.py
Section sources - config.py - chains.yaml - manager.py - w3.py - base.py - ethereum.py - bsc.py - anvil.py - scanner.py - sweeper.py - server.py
Performance Considerations
- Gas caching: The base class caches gas price for a short duration to reduce RPC calls.
- Gas estimation fallback: On failure, a conservative default is used to avoid blocking transactions.
- EIP-1559 prioritization: Prefer fee_history pricing when available for better fee control.
- Batch scanning: Services scan blocks in batches to limit per-iteration work.
- Middleware overhead: POA middleware adds minimal overhead but is essential for BSC compatibility.
[No sources needed since this section provides general guidance]
Troubleshooting Guide
Common issues and resolutions: - Gas estimation failures: - Symptom: Transactions fail or stall during estimation. - Resolution: The base class falls back to conservative defaults; review transaction data and ensure proper ABI for contract interactions. - Reference: base.py
- Network connectivity errors:
- Symptom: is_connected returns false or calls raise exceptions.
- Resolution: Verify RPC URL and timeout settings; check firewall and provider availability.
-
Reference: base.py
-
Transaction signing variations:
- Symptom: Transactions rejected due to chain ID mismatch or nonce errors.
- Resolution: Ensure chainId is set correctly; resolve pending nonce via provider; sign with the correct private key.
-
POA-specific issues on BSC:
- Symptom: Blocks rejected or extradata errors.
- Resolution: Enable POA middleware via use_poa; specialized BSC class already handles this.
-
Missing chain configuration:
- Symptom: get_w3 raises a "not configured" error.
- Resolution: Add chain entries to chains.yaml and restart the app; the factory provides a fallback Anvil client if config is empty.
- Reference: w3.py, manager.py
Section sources - base.py - base.py - base.py - base.py - bsc.py - w3.py - manager.py
Conclusion
The cTrip Payment Gateway provides a robust, extensible foundation for multi-chain support. By inheriting from BlockchainBase and registering chains via the factory, you can quickly add new EVM-compatible networks. The design cleanly separates concerns: configuration-driven instantiation, shared transaction logic, and service-layer consumption. Following the patterns documented here ensures correct RPC configuration, gas management, POA handling, and integration with the broader application lifecycle.
[No sources needed since this section summarizes without analyzing specific files]
Appendices
Step-by-Step: Implementing a New Custom Chain
- Create a new subclass in app/blockchain/
.py: - Import BlockchainBase.
- Override init to set chain_id and use_poa as needed.
-
Example reference: ethereum.py, bsc.py
-
Register the subclass in the factory:
- Extend get_blockchains() to instantiate your subclass when name matches.
-
Example reference: manager.py
-
Configure the chain:
- Add an entry to chains.yaml with name and rpc_url.
- Optionally include token metadata.
-
Example reference: chains.yaml
-
Verify connectivity and transactions:
- Use get_w3(chain_name) in services to access AsyncWeb3.
- Test balance, gas estimation, and transaction building.
-
Handle network-specific optimizations:
- Enable POA middleware if required (already handled in base class).
- Integrate EIP-1559 pricing automatically via fee_history.
-
Integrate with services:
- Use ScannerService and SweeperService with get_w3(chain_name).
-
Reference: scanner.py, sweeper.py
-
Handle signing and private keys:
- Sign transactions using Account.from_key and sign_transaction.
-
Reference: base.py
-
Manage ABI dependencies:
- ERC20 ABI is loaded by base class; ensure your contracts align with expected ABIs.
- Reference: base.py, ERC20.json
Section sources - ethereum.py - bsc.py - manager.py - chains.yaml - w3.py - base.py - base.py - base.py - base.py - base.py - scanner.py - sweeper.py - ERC20.json