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

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. 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.

graph TB subgraph "Blockchain Layer" B["BlockchainBase
(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.

sequenceDiagram participant App as "FastAPI App
(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.

classDiagram class BlockchainBase { +provider_url : str +chain_id : int +use_poa : bool +w3 : AsyncWeb3 -_gas_price_cache -_gas_price_timestamp -_gas_cache_duration : int +__init__(provider_url, chain_id, use_poa, request_timeout) +is_connected() bool +get_balance(address) int +get_token_balance(token_address, user_address) int +get_gas_price(use_cache) int +get_fee_history(block_count, newest_block) dict +estimate_gas(tx) int +build_transaction(from_address, to_address, value_wei, data, gas_limit, nonce) dict +send_transaction(tx, private_key) str +get_receipt(tx_hash, timeout) Any +get_latest_block() int }

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.
classDiagram class BlockchainBase class EthereumBlockchain { +__init__(provider_url, **kwargs) } class BSCBlockchain { +__init__(provider_url, **kwargs) } class AnvilBlockchain { +__init__(provider_url, **kwargs) +mine_blocks(num_blocks) str[] +set_balance(address, balance_eth) bool +impersonate_account(address) bool +stop_impersonating_account(address) bool +reset(forking_url, block_number) bool } EthereumBlockchain --> BlockchainBase : "inherits" BSCBlockchain --> BlockchainBase : "inherits" AnvilBlockchain --> BlockchainBase : "inherits"

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.

flowchart TD Start(["Startup"]) --> LoadCfg["Load chains from settings.chains"] LoadCfg --> Iterate{"For each chain"} Iterate --> HasRPC{"Has rpc_url?"} HasRPC --> |No| Skip["Skip chain"] HasRPC --> |Yes| Match{"Name matches special?
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.

sequenceDiagram participant Caller as "Caller" participant W3 as "get_w3()
(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.

flowchart TD Enter(["build_transaction"]) --> ResolveNonce["Resolve nonce if missing"] ResolveNonce --> BuildTx["Build basic tx fields
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.

sequenceDiagram participant Client as "BSCBlockchain" participant Base as "BlockchainBase" participant W3 as "AsyncWeb3" participant MW as "POA Middleware" Client->>Base : __init__(provider_url, chain_id=56, use_poa=True) Base->>W3 : AsyncHTTPProvider with timeout Base->>W3 : inject(MW, layer=0) if use_poa W3-->>Client : Ready with POA middleware

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.

sequenceDiagram participant Tx as "build_transaction" participant Fee as "get_fee_history" participant GP as "get_gas_price" Tx->>Fee : Fetch fee_history alt Success Fee-->>Tx : baseFeePerGas, rewards Tx->>Tx : Compute maxFeePerGas, maxPriorityFeePerGas else Failure Fee-->>Tx : Exception Tx->>GP : Fallback to gas_price GP-->>Tx : gasPrice end

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.
sequenceDiagram participant Scan as "ScannerService" participant W3 as "get_w3()" participant Eth as "w3.eth" Scan->>W3 : get_w3(chain_name) W3-->>Scan : AsyncWeb3 Scan->>Eth : block_number Scan->>Eth : get_block(full_transactions=True) Scan->>Eth : get_logs(ERC20 Transfer) Scan-->>Scan : Update payment statuses

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.

graph TB CFG["config.py"] YML["chains.yaml"] MAN["manager.py"] W3["w3.py"] BASE["base.py"] ETH["ethereum.py"] BSC["bsc.py"] ANV["anvil.py"] SCN["scanner.py"] SWP["sweeper.py"] SRV["server.py"] CFG --> MAN YML --> CFG MAN --> ETH MAN --> BSC MAN --> ANV MAN --> BASE W3 --> MAN SCN --> W3 SWP --> W3 SRV --> MAN

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.
  • Reference: base.py, base.py

  • POA-specific issues on BSC:

  • Symptom: Blocks rejected or extradata errors.
  • Resolution: Enable POA middleware via use_poa; specialized BSC class already handles this.
  • Reference: bsc.py, base.py

  • 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

  1. Create a new subclass in app/blockchain/.py:
  2. Import BlockchainBase.
  3. Override init to set chain_id and use_poa as needed.
  4. Example reference: ethereum.py, bsc.py

  5. Register the subclass in the factory:

  6. Extend get_blockchains() to instantiate your subclass when name matches.
  7. Example reference: manager.py

  8. Configure the chain:

  9. Add an entry to chains.yaml with name and rpc_url.
  10. Optionally include token metadata.
  11. Example reference: chains.yaml

  12. Verify connectivity and transactions:

  13. Use get_w3(chain_name) in services to access AsyncWeb3.
  14. Test balance, gas estimation, and transaction building.
  15. Example reference: w3.py, base.py

  16. Handle network-specific optimizations:

  17. Enable POA middleware if required (already handled in base class).
  18. Integrate EIP-1559 pricing automatically via fee_history.
  19. Reference: base.py, base.py

  20. Integrate with services:

  21. Use ScannerService and SweeperService with get_w3(chain_name).
  22. Reference: scanner.py, sweeper.py

  23. Handle signing and private keys:

  24. Sign transactions using Account.from_key and sign_transaction.
  25. Reference: base.py

  26. Manage ABI dependencies:

  27. ERC20 ABI is loaded by base class; ensure your contracts align with expected ABIs.
  28. 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