Enable TRON mainnet gasfree USDT - #9
Conversation
Code Review ReportProject: x402-demo PR OverviewBranch Information
Commit History
Review SummaryVerdict
Findings at a Glance
SummaryThis PR extends an existing TRON GasFree (gasless USDT) integration from Nile testnet to TRON Mainnet, and bumps the The primary concern is a backward-compatibility hazard: both Four additional minor issues are noted: the server reads GasFree credentials it never actually passes to any mechanism, mutable module-level lists are used where constants would be safer, commented-out policy code constitutes dead code, and a missing blank line violates PEP 8. Two suggestions are offered for improved clarity and defensiveness. Change Summary1. TRON Mainnet GasFree Activation (
|
| File | Change Type | Description |
|---|---|---|
facilitator/main.py |
Modified | Reads GASFREE_API_KEY_MAINNET / GASFREE_API_SECRET_MAINNET; adds GasFreeAPIClient for tron:mainnet to shared client dict; registers ExactGasFreeFacilitatorMechanism for mainnet when enabled |
server/main.py |
Modified | Reads same env vars to derive gasfree_enabled_mainnet; conditionally registers ExactGasFreeServerMechanism for TRON Mainnet; builds MAINNET_PRICES / MAINNET_SCHEMES lists dynamically |
Purpose: Enables the GasFree (gasless) payment path for USDT on TRON Mainnet in addition to Nile testnet, gated by the presence of API credentials.
2. Dynamic Nile Endpoint Pricing (server/main.py)
| File | Change Type | Description |
|---|---|---|
server/main.py |
Modified | Replaces hard-coded prices / schemes lists in @x402_protected for /protected-nile with conditionally built NILE_PRICES / NILE_SCHEMES lists |
Purpose: Removes previously unconditional exact_gasfree scheme advertisement for Nile, instead advertising it only when the GasFree credentials are actually configured — making the server and facilitator consistent.
3. Client Policy Disabled (client/python/main.py)
| File | Change Type | Description |
|---|---|---|
client/python/main.py |
Modified | Comments out x402_client.register_policy(PreferGasFreeUSDTPolicy) |
Purpose: Disables the custom GasFree-preferring policy so the default SDK selection logic (balance-based) is used during the demo, allowing the test to exercise the standard flow.
4. SDK Version Bump (requirements.txt)
| File | Change Type | Description |
|---|---|---|
requirements.txt |
Modified | bankofai-x402[tron,fastapi] bumped from 0.5.0 to 0.5.1 |
Purpose: Picks up SDK changes required for TRON Mainnet GasFree support.
5. Documentation Update (README.md)
| File | Change Type | Description |
|---|---|---|
README.md |
Modified | Adds exact_gasfree (USDT only) to the TRON Mainnet row of the network table; clarifies which networks support GasFree; documents new env vars GASFREE_API_KEY_MAINNET and GASFREE_API_SECRET_MAINNET |
Purpose: Keeps operator documentation in sync with the new functionality.
Detailed Findings
Major
[MJ-01] Generic Fallback Credential Silently Enables Mainnet GasFree on Existing Deployments
| Property | Value |
|---|---|
| Severity | Major |
| Category | Correctness / Compatibility |
| File | server/main.py : Lines 59–65 · facilitator/main.py : Lines 87–89 |
Description
Both the server and facilitator derive gasfree_enabled_mainnet using the same fallback chain as gasfree_enabled_nile:
# server/main.py (lines 59-65)
gasfree_api_key_nile = os.getenv("GASFREE_API_KEY_NILE") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_nile = os.getenv("GASFREE_API_SECRET_NILE") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_nile = bool(gasfree_api_key_nile and gasfree_api_secret_nile)
gasfree_api_key_mainnet = os.getenv("GASFREE_API_KEY_MAINNET") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_mainnet = os.getenv("GASFREE_API_SECRET_MAINNET") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_mainnet = bool(gasfree_api_key_mainnet and gasfree_api_secret_mainnet)Any operator who previously set only the generic GASFREE_API_KEY / GASFREE_API_SECRET (to enable Nile GasFree) will have both gasfree_enabled_nile and gasfree_enabled_mainnet resolve to True after this upgrade — silently activating GasFree settlement on TRON Mainnet (real funds). This is an unannounced, breaking default-behaviour change.
The same pattern appears in facilitator/main.py lines 83–89, and consequently the gasfree_clients dict passed to both ExactGasFreeFacilitatorMechanism instances will include a tron:mainnet entry derived from the generic credentials.
Code
# facilitator/main.py lines 83-89
gasfree_api_key_nile = os.getenv("GASFREE_API_KEY_NILE") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_nile = os.getenv("GASFREE_API_SECRET_NILE") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_nile = bool(gasfree_api_key_nile and gasfree_api_secret_nile)
gasfree_api_key_mainnet = os.getenv("GASFREE_API_KEY_MAINNET") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_mainnet = os.getenv("GASFREE_API_SECRET_MAINNET") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_mainnet = bool(gasfree_api_key_mainnet and gasfree_api_secret_mainnet)Recommendation
Remove the generic-key fallback only for the mainnet credential, requiring explicit opt-in for the production network:
# Nile: network-specific key, falls back to generic (testnet only, low risk)
gasfree_api_key_nile = os.getenv("GASFREE_API_KEY_NILE") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_nile = os.getenv("GASFREE_API_SECRET_NILE") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_nile = bool(gasfree_api_key_nile and gasfree_api_secret_nile)
# Mainnet: MUST be explicit — no generic fallback (real money)
gasfree_api_key_mainnet = os.getenv("GASFREE_API_KEY_MAINNET")
gasfree_api_secret_mainnet = os.getenv("GASFREE_API_SECRET_MAINNET")
gasfree_enabled_mainnet = bool(gasfree_api_key_mainnet and gasfree_api_secret_mainnet)Apply the same fix symmetrically in both facilitator/main.py and server/main.py. Document this in the README under the upgrade / migration notes.
Minor
[MN-01] Server Reads GasFree Credentials It Never Uses
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | server/main.py : Lines 59–65 |
Description
server/main.py reads GASFREE_API_KEY_* / GASFREE_API_SECRET_* from the environment solely to derive two boolean flags. The credential values themselves are never passed to ExactGasFreeServerMechanism(), which takes no arguments. The inline comment # GasFree flags (kept in sync with facilitator envs) acknowledges the intent, but reading credentials unnecessarily can confuse readers and static analysis tools into thinking they are consumed.
Code
# server/main.py lines 58-65
# GasFree flags (kept in sync with facilitator envs)
gasfree_api_key_nile = os.getenv("GASFREE_API_KEY_NILE") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_nile = os.getenv("GASFREE_API_SECRET_NILE") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_nile = bool(gasfree_api_key_nile and gasfree_api_secret_nile)
gasfree_api_key_mainnet = os.getenv("GASFREE_API_KEY_MAINNET") or os.getenv("GASFREE_API_KEY")
gasfree_api_secret_mainnet = os.getenv("GASFREE_API_SECRET_MAINNET") or os.getenv("GASFREE_API_SECRET")
gasfree_enabled_mainnet = bool(gasfree_api_key_mainnet and gasfree_api_secret_mainnet)Recommendation
Read the env vars directly into the boolean expressions without storing intermediate credential variables:
gasfree_enabled_nile = bool(
os.getenv("GASFREE_API_KEY_NILE") or os.getenv("GASFREE_API_KEY")
) and bool(
os.getenv("GASFREE_API_SECRET_NILE") or os.getenv("GASFREE_API_SECRET")
)
gasfree_enabled_mainnet = bool(os.getenv("GASFREE_API_KEY_MAINNET")) and bool(
os.getenv("GASFREE_API_SECRET_MAINNET")
)This also naturally enforces the explicit-only requirement from MJ-01 for mainnet.
[MN-02] Mutable Module-Level Lists Used as Decorator Constants
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | server/main.py : Lines 184–188, 238–242 |
Description
NILE_PRICES, NILE_SCHEMES, MAINNET_PRICES, and MAINNET_SCHEMES are defined as plain list objects at module scope and then mutated with .append() during startup. They are later consumed by @x402_protected decorators at decoration time. Because Python lists are mutable and these are module-level globals, any inadvertent late append (e.g., in a test harness or monkey-patch) would silently alter the decorator's payment requirements without error. Using mutable state as configuration signals is also harder to reason about than immutable constants.
Code
# server/main.py lines 184-188
NILE_PRICES = ["0.0001 USDT", "0.0001 USDD"]
NILE_SCHEMES = ["exact_permit", "exact_permit"]
if gasfree_enabled_nile:
NILE_PRICES.append("0.0001 USDT")
NILE_SCHEMES.append("exact_gasfree")Recommendation
Build the final lists as tuples (or freeze them after construction) to make their immutability explicit:
_nile_prices = ["0.0001 USDT", "0.0001 USDD"]
_nile_schemes = ["exact_permit", "exact_permit"]
if gasfree_enabled_nile:
_nile_prices.append("0.0001 USDT")
_nile_schemes.append("exact_gasfree")
NILE_PRICES: tuple[str, ...] = tuple(_nile_prices)
NILE_SCHEMES: tuple[str, ...] = tuple(_nile_schemes)Apply the same pattern for the MAINNET_* pair.
[MN-03] Dead Code — PreferGasFreeUSDTPolicy Defined but Never Registered
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | client/python/main.py : Lines 31–41, 108 |
Description
The PreferGasFreeUSDTPolicy class (lines 31–41) remains in the file and is referenced in a comment on line 108, but the actual register_policy call is commented out. The class and its import (TokenRegistry) constitute dead code that will never execute, adding cognitive overhead for future readers and triggering linter warnings.
Code
# client/python/main.py line 108
# x402_client.register_policy(PreferGasFreeUSDTPolicy)Recommendation
Either:
- Remove the
PreferGasFreeUSDTPolicyclass and any imports used exclusively by it (e.g., verifyTokenRegistryis still needed elsewhere), replacing the commented line with a brief note explaining the deliberate omission; or - Retain it with a
# noqaannotation and a docstring clearly stating it is available for manual GasFree preference testing.
For a demo codebase, option 1 keeps the code clean; option 2 is acceptable if the policy is expected to be re-enabled during experimentation.
[MN-04] Missing Blank Line Before Decorator (PEP 8)
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | server/main.py : Lines 188–190 |
Description
There is no blank line separating the if gasfree_enabled_nile: block from the @app.get("/protected-nile") decorator. PEP 8 requires two blank lines between top-level definitions; while decorators are not function bodies, the visual break is expected between a conditional block and the next top-level entity. The MAINNET_* block (lines 238–244) correctly has a trailing blank line before its decorator.
Code
if gasfree_enabled_nile:
NILE_PRICES.append("0.0001 USDT")
NILE_SCHEMES.append("exact_gasfree")
@app.get("/protected-nile") # ← only one blank line here; should be two
@x402_protected(Recommendation
Add a second blank line between the conditional block and the decorator to match the rest of the file's style:
if gasfree_enabled_nile:
NILE_PRICES.append("0.0001 USDT")
NILE_SCHEMES.append("exact_gasfree")
@app.get("/protected-nile")Suggestions
[S-01] Pass Network-Scoped Client Dicts to Each GasFree Mechanism in the Facilitator
File: facilitator/main.py : Lines 122–135
Description: Both the Nile and Mainnet ExactGasFreeFacilitatorMechanism instances receive the same shared gasfree_clients dict, which may contain entries for multiple networks. If the mechanism internally selects a client by network key, this is harmless, but it means each mechanism instance holds a reference to the other network's API credentials unnecessarily. Passing a network-scoped slice makes intent explicit and reduces the risk of credential cross-contamination if the mechanism's routing logic ever changes.
Suggestion:
if network == "nile" and gasfree_enabled_nile:
gasfree_mechanism = ExactGasFreeFacilitatorMechanism(
tron_signer,
clients={"tron:nile": gasfree_clients["tron:nile"]}, # scoped
base_fee=TRON_BASE_FEE,
)
facilitator.register([f"tron:{network}"], gasfree_mechanism)
if network == "mainnet" and gasfree_enabled_mainnet:
gasfree_mechanism = ExactGasFreeFacilitatorMechanism(
tron_signer,
clients={"tron:mainnet": gasfree_clients["tron:mainnet"]}, # scoped
base_fee=TRON_BASE_FEE,
)
facilitator.register([f"tron:{network}"], gasfree_mechanism)[S-02] Document the Generic-Key Fallback Behaviour and Upgrade Path in README
File: README.md
Description: The README documents GASFREE_API_KEY_MAINNET / GASFREE_API_SECRET_MAINNET as new optional vars, but does not mention that the generic GASFREE_API_KEY / GASFREE_API_SECRET values are also read as a fallback. An operator who set generic keys on a prior version (Nile-only) has no way of knowing that upgrading will also activate Mainnet GasFree unless they read the source code. A brief note under the GasFree section and/or an upgrade notice would prevent surprise in production environments.
Suggestion: Add a note such as:
Note for upgraders: If you previously set
GASFREE_API_KEY/GASFREE_API_SECRET(generic keys) to enable GasFree on Nile, be aware that these keys also activate Mainnet GasFree in this release. To keep Mainnet GasFree disabled, unset the generic keys and useGASFREE_API_KEY_NILE/GASFREE_API_SECRET_NILEexplicitly.
Positive Observations
| Area | Observation |
|---|---|
| Conditional registration | Gating ExactGasFreeServerMechanism and ExactGasFreeFacilitatorMechanism registration behind gasfree_enabled_* booleans is correct and ensures GasFree is never advertised when credentials are absent. |
| Refactor: ternary → if-blocks | Replacing the complex conditional expression for gasfree_clients in facilitator/main.py with sequential if blocks is a clear readability improvement. |
| Observability | print(f"GasFree Mainnet Enabled: {gasfree_enabled_mainnet}") added to both server and facilitator startup banners makes the runtime configuration immediately visible in logs. |
| Symmetric implementation | Nile and Mainnet credential loading, client construction, mechanism registration, and endpoint pricing follow a parallel and consistent pattern, making the code easy to extend for future networks. |
| README accuracy | The network support table and GasFree section in the README are updated to accurately reflect USDT-only restriction on Mainnet GasFree. |
| SDK bump | Pinning to bankofai-x402==0.5.1 picks up upstream library support; using == rather than >= prevents accidental breakage from future SDK releases in this demo. |
Checklist Results
| Category | Items Checked | Pass | Fail | N/A | Notes |
|---|---|---|---|---|---|
| Correctness | 8 | 7 | 1 | 0 | Generic fallback silently enables mainnet (MJ-01) |
| Security | 10 | 10 | 0 | 0 | No hardcoded secrets; credentials properly env-sourced |
| Performance | 7 | 7 | 0 | 0 | Startup-time registration; no runtime perf concerns |
| Code Quality | 10 | 6 | 4 | 0 | Mutable globals (MN-02), dead code (MN-03), PEP 8 (MN-04), misleading reads (MN-01) |
| Testing | 7 | 0 | 0 | 7 | Demo project; no test suite in scope |
| Documentation | 6 | 5 | 1 | 0 | Fallback behaviour / upgrade path not documented (S-02) |
| Compatibility | 5 | 4 | 1 | 0 | Silent breaking change for existing deployments (MJ-01) |
| Observability | 4 | 4 | 0 | 0 | GasFree enabled flags added to startup logs |
Disclaimer
This is an automated code review. It supplements but does not replace human review. The reviewer analyzed only the diff between main and feature/tron-mainnet-gasfree-usdt. Runtime behaviour, integration testing against the TRON Mainnet GasFree API, and deployment impact are not covered.
Report generated by Code Review Skill v1.0.0
Date: 2026-03-27
Summary