Skip to content

Code Structure

This guide provides a detailed overview of Routstr Core's codebase organization and key modules.

Directory Layout

routstr-core/
├── routstr/                    # Main application package
│   ├── __init__.py            # Package initialization, loads .env
│   ├── auth.py                # Authentication and authorization
│   ├── balance.py             # Balance management endpoints
│   ├── discovery.py           # Nostr relay discovery
│   ├── proxy.py               # Request proxying logic
│   ├── wallet.py              # Cashu wallet operations
│   │
│   ├── core/                  # Core infrastructure
│   │   ├── __init__.py
│   │   ├── admin.py          # Admin dashboard and API
│   │   ├── db.py             # Database models and connection
│   │   ├── exceptions.py     # Custom exception classes
│   │   ├── logging.py        # Structured logging setup
│   │   ├── main.py           # FastAPI app initialization
│   │   └── middleware.py     # HTTP middleware components
│   │
│   └── payment/               # Payment processing
│       ├── __init__.py
│       ├── cost_calculation.py # Usage cost calculation
│       ├── helpers.py         # Payment utilities
│       ├── lnurl.py          # Lightning URL support
│       ├── models.py         # Model pricing management
│       ├── price.py          # BTC/USD price handling
│       └── x_cashu.py        # Cashu header protocol
│
├── tests/                     # Test suite
│   ├── __init__.py
│   ├── conftest.py           # Pytest configuration
│   ├── unit/                 # Unit tests
│   └── integration/          # Integration tests
│
├── migrations/               # Alembic database migrations
│   ├── alembic.ini
│   ├── env.py
│   ├── script.py.mako
│   └── versions/             # Migration files
│
├── scripts/                  # Utility scripts
│   └── models_meta.py       # Fetch model pricing
│
├── docs/                    # Documentation
├── logs/                    # Application logs (git ignored)
│
├── .github/                 # GitHub Actions workflows
├── .env.example            # Environment variable template
├── .gitignore             # Git ignore rules
├── .dockerignore          # Docker ignore rules
├── Dockerfile             # Container definition
├── Makefile               # Development commands
├── README.md              # Project overview
├── alembic.ini            # Migration configuration
├── compose.yml            # Docker Compose setup
├── compose.testing.yml    # Testing environment
├── pyproject.toml         # Project configuration
└── uv.lock               # Locked dependencies

Key Modules

Application Entry Point

routstr/__init__.py

# Loads environment variables
import dotenv
dotenv.load_dotenv()

# Exports FastAPI app
from .core.main import app as fastapi_app

routstr/core/main.py

# FastAPI application setup
app = FastAPI(
    title="Routstr Node",
    lifespan=lifespan,  # Manages startup/shutdown
)

# Middleware registration
app.add_middleware(CORSMiddleware, ...)
app.add_middleware(LoggingMiddleware)

# Router inclusion
app.include_router(admin_router)
app.include_router(balance_router)
app.include_router(proxy_router)

Authentication Module

routstr/auth.py

Handles API key validation and authorization:

class APIKeyAuth:
    """FastAPI dependency for API key authentication"""

    async def __call__(self, request: Request) -> APIKey:
        # Extract and validate API key
        # Check balance
        # Return authenticated key object

# Usage in routes:
@router.get("/protected")
async def protected_route(api_key: APIKey = Depends(APIKeyAuth())):
    pass

Key functions:

  • create_api_key() - Generate new API keys
  • validate_api_key() - Verify and retrieve key
  • check_balance() - Ensure sufficient funds
  • update_last_used() - Track usage

Payment Processing

routstr/payment/cost_calculation.py

Calculates request costs:

def calculate_request_cost(
    model: str,
    prompt_tokens: int,
    completion_tokens: int,
    **kwargs
) -> CostData:
    """Calculate cost in millisatoshis"""
    # Model-based or fixed pricing
    # Token counting
    # Fee application
    # Currency conversion

routstr/payment/models.py

Manages model pricing data:

class ModelPrice:
    id: str
    name: str
    pricing: dict[str, float]  # USD prices
    context_length: int

# Global model registry
MODELS: dict[str, ModelPrice] = load_models()

# Dynamic price updates
async def update_sats_pricing():
    """Background task to update BTC prices"""

routstr/payment/x_cashu.py

Implements Cashu payment protocol:

class XCashuHandler:
    """Handle x-cashu header payments"""

    async def process_request_payment(
        self, 
        token: str,
        estimated_cost: int
    ) -> PaymentResult:
        # Validate token
        # Check minimum amount
        # Process payment
        # Generate change

Request Proxying

routstr/proxy.py

Core proxy functionality:

@router.api_route("/{path:path}", methods=ALL_METHODS)
async def proxy_request(
    request: Request,
    path: str,
    api_key: APIKey = Depends(APIKeyAuth())
) -> Response:
    """Forward requests to upstream provider"""
    # Build upstream request
    # Stream response
    # Track usage
    # Deduct costs

Key features:

  • Streaming support
  • Header preservation
  • Error handling
  • Usage tracking

Database Layer

routstr/core/db.py

SQLModel definitions:

class APIKey(SQLModel, table=True):
    id: int | None = Field(primary_key=True)
    key_hash: str = Field(index=True, unique=True)
    balance: int  # millisatoshis
    total_deposited: int = 0
    total_spent: int = 0
    created_at: datetime
    expires_at: datetime | None = None
    metadata: dict = Field(default_factory=dict, sa_column=Column(JSON))

class Transaction(SQLModel, table=True):
    id: int | None = Field(primary_key=True)
    api_key_id: int = Field(foreign_key="apikey.id")
    amount: int  # can be negative
    balance_after: int
    type: TransactionType
    description: str
    timestamp: datetime

Admin Interface

routstr/core/admin.py

Web dashboard and admin API:

@admin_router.get("/admin/")
async def admin_dashboard(request: Request):
    """Render admin HTML interface"""
    # Authentication check
    # Load statistics
    # Render template

@admin_router.post("/admin/api/withdraw")
async def withdraw_balance(
    api_key: str,
    amount: int | None = None
) -> WithdrawalResponse:
    """Generate eCash token for withdrawal"""

Features:

  • HTML dashboard
  • API key management
  • Balance withdrawals
  • Usage statistics

Wallet Integration

routstr/wallet.py

Cashu wallet operations:

class WalletManager:
    """Manage Cashu wallet instances"""

    async def redeem_token(
        self, 
        token: str,
        mint_url: str | None = None
    ) -> int:
        """Redeem eCash token and return value"""

    async def create_token(
        self,
        amount: int,
        mint_url: str
    ) -> str:
        """Create eCash token for withdrawal"""

Utility Modules

routstr/core/logging.py

Structured logging configuration:

def setup_logging():
    """Configure JSON structured logging"""
    # Set log level
    # Configure formatters
    # Add handlers

class RequestIdMiddleware:
    """Add request ID to all logs"""

routstr/core/middleware.py

HTTP middleware components:

class LoggingMiddleware:
    """Log all HTTP requests/responses"""

class ErrorHandlingMiddleware:
    """Consistent error responses"""

routstr/core/exceptions.py

Custom exception hierarchy:

class RoustrError(Exception):
    """Base exception with error details"""
    status_code: int
    error_type: str
    detail: str

class PaymentError(RoustrError):
    """Payment-related errors"""

class UpstreamError(RoustrError):
    """Upstream API errors"""

Configuration Files

pyproject.toml

Project metadata and dependencies:

[project]
name = "routstr"
version = "0.1.3"
dependencies = [
    "fastapi[standard]>=0.115",
    "sqlmodel>=0.0.24",
    "cashu",
    # ...
]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[tool.ruff.lint]
select = ["E", "F", "I"]

alembic.ini

Database migration configuration:

[alembic]
script_location = migrations
prepend_sys_path = .
version_path_separator = os

[loggers]
keys = root,sqlalchemy,alembic

Makefile

Development commands:

# Setup commands
setup:
    uv sync
    uv pip install -e .

# Development server
dev:
    fastapi dev routstr --host 0.0.0.0

# Testing
test:
    uv run pytest

# Code quality
lint:
    uv run ruff check .

Code Patterns

Dependency Injection

Using FastAPI's DI system:

# Define dependency
async def get_db() -> AsyncSession:
    async with async_session() as session:
        yield session

# Use in routes
@router.get("/items")
async def get_items(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Item))
    return result.scalars().all()

Async Context Managers

For resource management:

async with httpx.AsyncClient() as client:
    response = await client.get(url)

async with database.transaction():
    # Atomic operations

Type Safety

Leveraging Python 3.11+ features:

# Union types with |
def process(value: str | int) -> dict[str, Any]:
    pass

# Type aliases
Balance = int  # millisatoshis
TokenList = list[dict[str, str]]

Error Handling

Consistent error responses:

try:
    result = await risky_operation()
except SpecificError as e:
    logger.error("Operation failed", exc_info=True)
    raise HTTPException(
        status_code=400,
        detail={
            "error": "specific_error",
            "message": str(e)
        }
    )

Best Practices

Module Organization

  1. Single Responsibility: Each module has one clear purpose
  2. Minimal Imports: Import only what's needed
  3. Circular Dependencies: Avoid by using dependency injection
  4. Public API: Expose through __init__.py

Function Design

  1. Type Hints: Always include complete type annotations
  2. Async First: Use async/await for I/O operations
  3. Error Handling: Raise specific exceptions
  4. Documentation: Docstrings for public functions

Testing Structure

  1. Mirror Source: Test structure matches source
  2. Fixtures: Reusable test data in conftest.py
  3. Mocking: Mock external dependencies
  4. Coverage: Aim for >80% coverage

Next Steps