-
Notifications
You must be signed in to change notification settings - Fork 0
Make token and chain settings configurable via .env #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1d9c730
06ca5d2
9b85768
6205931
defe1f2
c8ef2ec
bac2522
f79863b
b5072d5
5892842
26ddefb
22dc3c4
dbfc3ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Application Settings | ||
| APP_NAME=Payment Link Service | ||
| APP_LOGO=/static/logo.png | ||
| APP_HOST=0.0.0.0 | ||
| APP_PORT=8000 | ||
| APP_BASE_URL=http://localhost:8000 | ||
|
|
||
| # x402 Payment Settings | ||
| NETWORK=base | ||
| FACILITATOR_URL=https://x402f1.secondstate.io | ||
| MAX_TIMEOUT_SECONDS=60 | ||
|
|
||
| # Token Settings (USDC on Base Mainnet) | ||
| TOKEN_ADDRESS=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | ||
| TOKEN_NAME=USD Coin | ||
| TOKEN_SYMBOL=USDC | ||
| TOKEN_DECIMALS=6 | ||
|
|
||
| # Chain Settings (Base Mainnet) | ||
| CHAIN_ID=8453 | ||
| EXPLORER_URL=https://basescan.org/tx/ | ||
|
|
||
| # Database Settings | ||
| DATABASE_PATH=payments.db |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,12 +20,27 @@ def __init__(self) -> None: | |
| self.app_base_url: str = os.getenv("APP_BASE_URL", "http://localhost:8000") | ||
|
|
||
| # x402 Payment settings | ||
| # Valid networks: base-sepolia (testnet), base (mainnet) | ||
| self.network: str = os.getenv("NETWORK", "base-sepolia") | ||
| self.facilitator_url: str = os.getenv( | ||
| "FACILITATOR_URL", "https://x402f1.secondstate.io" | ||
| ) | ||
| self.max_timeout_seconds: int = int(os.getenv("MAX_TIMEOUT_SECONDS", "60")) | ||
|
|
||
| # Token settings | ||
| self.token_address: str = os.getenv( | ||
| "TOKEN_ADDRESS", "0x036CbD53842c5426634e7929541eC2318f3dCF7e" | ||
| ) | ||
| self.token_name: str = os.getenv("TOKEN_NAME", "USD Coin") | ||
| self.token_symbol: str = os.getenv("TOKEN_SYMBOL", "USDC") | ||
| self.token_decimals: int = int(os.getenv("TOKEN_DECIMALS", "6")) | ||
|
|
||
| # Chain settings | ||
| self.chain_id: int = int(os.getenv("CHAIN_ID", "84532")) | ||
| self.explorer_url: str = os.getenv( | ||
| "EXPLORER_URL", "https://sepolia.basescan.org/tx/" | ||
| ) | ||
|
Comment on lines
+30
to
+42
|
||
|
|
||
| # Database settings | ||
| self.database_path: str = os.getenv("DATABASE_PATH", "payments.db") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||||||||||||||||||||
| """Payment Link Service - A web app for creating x402-protected payment links.""" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import traceback | ||||||||||||||||||||||||||
| import uuid | ||||||||||||||||||||||||||
| from contextlib import asynccontextmanager | ||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||
|
|
@@ -40,8 +41,6 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: | |||||||||||||||||||||||||
| @app.exception_handler(Exception) | ||||||||||||||||||||||||||
| async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse: | ||||||||||||||||||||||||||
| """Catch all unhandled exceptions and return a JSON error response.""" | ||||||||||||||||||||||||||
| import traceback | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||||||
| status_code=500, | ||||||||||||||||||||||||||
| content={ | ||||||||||||||||||||||||||
|
|
@@ -71,6 +70,36 @@ async def root() -> Response: | |||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @app.get("/create") | ||||||||||||||||||||||||||
| async def create_page() -> Response: | ||||||||||||||||||||||||||
| """Serve the create payment link page.""" | ||||||||||||||||||||||||||
| create_path = STATIC_DIR / "create-payment-link.html" | ||||||||||||||||||||||||||
| if create_path.exists(): | ||||||||||||||||||||||||||
| return FileResponse(create_path) | ||||||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||||||
| status_code=404, | ||||||||||||||||||||||||||
| content={"error": "Page not found"}, | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
Comment on lines
+73
to
+82
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @app.get("/config") | ||||||||||||||||||||||||||
| async def get_config() -> dict[str, str | int]: | ||||||||||||||||||||||||||
| """Return client configuration for the frontend. | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||
| JSON with network, token, and chain configuration. | ||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||
| "network": settings.network, | ||||||||||||||||||||||||||
| "tokenAddress": settings.token_address, | ||||||||||||||||||||||||||
| "tokenName": settings.token_name, | ||||||||||||||||||||||||||
| "tokenSymbol": settings.token_symbol, | ||||||||||||||||||||||||||
| "tokenDecimals": settings.token_decimals, | ||||||||||||||||||||||||||
|
Comment on lines
+90
to
+97
|
||||||||||||||||||||||||||
| JSON with network, token, and chain configuration. | |
| """ | |
| return { | |
| "network": settings.network, | |
| "tokenAddress": settings.token_address, | |
| "tokenName": settings.token_name, | |
| "tokenSymbol": settings.token_symbol, | |
| "tokenDecimals": settings.token_decimals, | |
| JSON with network and chain configuration. | |
| """ | |
| return { | |
| "network": settings.network, |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The header normalization only handles the lowercase 'x-payment' to 'X-Payment' conversion. However, HTTP headers are case-insensitive, and various frameworks/proxies might normalize them differently (e.g., 'X-payment', 'x-Payment', etc.).
Consider using a case-insensitive header lookup instead. For example:
- Iterate through headers_dict to find any case variation of 'x-payment'
- Or normalize all header keys to lowercase and adjust the x402 library expectations accordingly
The current implementation could miss valid X-Payment headers that have different casing.
| if "x-payment" in headers_dict and "X-Payment" not in headers_dict: | |
| headers_dict["X-Payment"] = headers_dict["x-payment"] | |
| # Perform a case-insensitive lookup for any variant of "x-payment" | |
| payment_header_value = None | |
| for header_name, header_value in headers_dict.items(): | |
| if header_name.lower() == "x-payment": | |
| payment_header_value = header_value | |
| break | |
| # Ensure the canonical header key is present for the x402 library | |
| if payment_header_value is not None and "X-Payment" not in headers_dict: | |
| headers_dict["X-Payment"] = payment_header_value |
Copilot
AI
Feb 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The monkey patching approach to modify httpx.AsyncClient.init is fragile and could cause issues in concurrent requests or if the library changes. This modifies a global class method that could affect other parts of the application.
Consider one of these safer alternatives:
- Create a wrapper class that extends httpx.AsyncClient with the desired timeout
- Pass the timeout configuration to the x402 library if it supports it
- Use a context manager to temporarily patch and restore the method
- Submit a PR to the x402 library to support configurable timeouts
If monkey patching is necessary, at least use a thread-local or request-scoped approach to avoid affecting concurrent requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TOKEN_VERSION configuration is added to config.py and the .env examples but is never used in the codebase. The frontend retrieves token name and version from the payment requirements (req.extra?.name and req.extra?.version) at lines 295-296 in index.html, but the server configuration doesn't include tokenVersion in the /config endpoint response.
If TOKEN_VERSION is intended to be used, it should be:
Otherwise, remove TOKEN_VERSION from the configuration to avoid confusion.