Dev/agent wallet 0316 - #8
Conversation
Code Review ReportProject: x402-demo PR OverviewBranch Information
Commit History
Review SummaryVerdict
Findings at a Glance
SummaryThis PR migrates the x402-demo project from explicit private-key environment variables ( The architectural intent is sound — removing raw private keys from environment variables reduces the risk of accidental secret exposure and simplifies onboarding. However, the PR introduces three major concerns: BSC/EVM wallets are now unconditionally initialised in all components (whereas they were previously optional), the startup event for the facilitator has no error handling around the wallet creation calls, and the TRON facilitator address is no longer logged at startup, reducing operational observability. Several minor inconsistencies around GasFree credential passing and a deprecated FastAPI API also warrant attention before merge. Change Summary1. Agent Wallet Integration (Core Feature)
Purpose: Eliminate raw private keys from environment variables by delegating key management to the new 2. SDK Version Upgrade
Purpose: Align both Python and TypeScript clients with the published v0.5.0 SDK that bundles the agent-wallet dependency. 3. GasFree Conditional Enablement (Facilitator)
Purpose: Allow the facilitator to run without GasFree credentials by degrading gracefully to non-GasFree TRON mechanics. 4. Startup Validation Removal
Purpose: Startup validation is no longer needed at the env-var level because key management is now internal to the agent wallet. 5. Asset Changes
Detailed FindingsMajor[MJ-01] No Error Handling Around Async Wallet Initialization in Startup Event
Description The Code @app.on_event("startup")
async def register_mechanisms():
"""Register all mechanisms with async wallet initialization."""
tron_signer = await TronFacilitatorSigner.create() # no try/except
...
bsc_signer = await EvmFacilitatorSigner.create() # no try/exceptRecommendation @app.on_event("startup")
async def register_mechanisms():
"""Register all mechanisms with async wallet initialization."""
try:
tron_signer = await TronFacilitatorSigner.create()
except Exception as e:
logger.critical(
"Failed to initialise TRON agent wallet — is the agent-wallet "
"CLI configured? Run `agent-wallet init` to set up. Error: %s", e
)
raise SystemExit(1) from e
try:
bsc_signer = await EvmFacilitatorSigner.create()
except Exception as e:
logger.critical("Failed to initialise EVM agent wallet: %s", e)
raise SystemExit(1) from e
...The same pattern should be applied in [MJ-02] EVM/BSC Signer Is Now Unconditionally Required
Description Previously, EVM/BSC support was entirely optional and guarded by Code (facilitator/main.py) # Previously guarded:
# if BSC_PRIVATE_KEY:
# bsc_signer = EvmFacilitatorSigner.from_private_key(BSC_PRIVATE_KEY)
# ...
# Now unconditional:
bsc_signer = await EvmFacilitatorSigner.create() # always runsRecommendation Wrap the EVM signer creation in a try/except and make BSC registration conditional on success, preserving backward compatibility: try:
bsc_signer = await EvmFacilitatorSigner.create()
# register BSC mechanisms ...
print(f"BSC Facilitator Address: {bsc_signer.get_address()}")
except Exception as e:
logger.warning(
"EVM agent wallet not available — BSC networks will be disabled. "
"Configure an EVM key with `agent-wallet` to enable BSC. Error: %s", e
)Apply the same pattern in the Python and TypeScript clients. [MJ-03] TRON Facilitator Address No Longer Logged at Startup
Description After the refactor, the startup banner logs the BSC facilitator address and fee configuration, but the TRON facilitator address is never printed. This is a significant regression in operational visibility: operators cannot verify which TRON wallet is signing transactions, making debugging and auditing significantly harder. The corresponding client-side addresses ( Code # BSC address is printed:
print(f"BSC Facilitator Address: {bsc_facilitator_address}")
# TRON address is MISSING — tron_signer.get_address() is never called
print("=" * 80)
print("X402 Payment Facilitator - Configuration")
print("=" * 80)
print(f"TRON Base Fee: {TRON_BASE_FEE}")
# ... no TRON address hereRecommendation print(f"TRON Facilitator Address: {tron_signer.get_address()}")
print(f"BSC Facilitator Address: {bsc_facilitator_address}")In tron_signer = await TronClientSigner.create()
evm_signer = await EvmClientSigner.create()
print(f"TRON Address : {tron_signer.get_address()}")
print(f"EVM Address : {evm_signer.get_address()}")
print(f"Resource URL : {RESOURCE_URL}")Minor[MN-01]
|
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality / Compatibility |
| File | facilitator/main.py : Line ~107 |
Description
FastAPI deprecated @app.on_event("startup") in v0.93.0 (released Feb 2023) in favour of the lifespan context manager pattern. While the decorator still works, it emits a deprecation warning in recent FastAPI releases and may be removed in a future major version.
Recommendation
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
await register_mechanisms()
yield
# optional teardown here
app = FastAPI(
title="X402 Facilitator",
lifespan=lifespan,
...
)[MN-02] GasFree API Credentials Not Passed in Python Client
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Correctness / Consistency |
| File | client/python/main.py : Lines ~67–72 |
Description
The facilitator's GasFree client is updated to pass api_key and api_secret from environment variables. However, the Python client's GasFreeAPIClient instances are still created with only the base URL — no credentials. If the GasFree API begins requiring authentication for client-side use, or if authenticated calls receive preferential rate limits, the client will break or be throttled while the facilitator works correctly.
Code (client/python/main.py)
gasfree_clients = {
"tron:nile": GasFreeAPIClient(NetworkConfig.get_gasfree_api_base_url("tron:nile")),
"tron:shasta": GasFreeAPIClient(NetworkConfig.get_gasfree_api_base_url("tron:shasta")),
"tron:mainnet": GasFreeAPIClient(NetworkConfig.get_gasfree_api_base_url("tron:mainnet")),
}Recommendation
Mirror the facilitator pattern: read GASFREE_API_KEY / GASFREE_API_SECRET from the environment and pass them to each client constructor (or document explicitly that the client-side GasFree API does not require credentials).
[MN-03] BSC Configuration Printed Before the Section Header Banner
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | facilitator/main.py : Lines ~153–158 |
Description
print(f"BSC Facilitator Address: {bsc_facilitator_address}") and print(f"BSC Base Fee: {BSC_BASE_FEE}") appear before the "=" * 80 / "X402 Payment Facilitator - Configuration" banner. This is inconsistent — the BSC information logically belongs inside the configuration block.
Recommendation
Move the BSC address/fee prints to after the banner header:
print("=" * 80)
print("X402 Payment Facilitator - Configuration")
print("=" * 80)
print(f"TRON Facilitator Address: {tron_signer.get_address()}")
print(f"TRON Base Fee: {TRON_BASE_FEE}")
print(f"BSC Facilitator Address: {bsc_facilitator_address}")
print(f"BSC Base Fee: {BSC_BASE_FEE}")
print(f"GasFree Nile Enabled: {gasfree_enabled_nile}")
...[MN-04] EVM Address No Longer Logged in TypeScript Client
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Observability |
| File | client/typescript/src/main.ts : Lines ~86–94 |
Description
The TypeScript client still logs the TRON address at startup but the EVM address line was removed. Users running BSC payments have no way to confirm which EVM wallet is being used without examining the agent wallet configuration separately.
Recommendation
const evmSigner = await EvmClientSigner.create();
console.log(` TRON Address : ${tronSigner.getAddress()}`);
console.log(` EVM Address : ${evmSigner.getAddress()}`);Suggestions
[S-01] No User-Facing Documentation for Agent Wallet Setup
File: README.md / project docs (not changed in this PR)
Description: The PR removes all private-key env-var references (TRON_PRIVATE_KEY, BSC_PRIVATE_KEY) but adds no documentation explaining what the new TronClientSigner.create() / EvmClientSigner.create() calls require. Existing users upgrading from the main branch will see their configuration break with no clear migration path. The @bankofai/agent-wallet package exposes an agent-wallet CLI binary (visible in package-lock.json) but this is never mentioned anywhere in the changed code.
Suggestion: Add a migration note or update README.md documenting the new setup flow (e.g., npx agent-wallet init).
[S-02] Deletion of assets/openclaw.jpg Unexplained
File: assets/openclaw.jpg
Description: A 570 KB binary asset was deleted without any commit explanation. If this file is referenced in documentation or the README it will create broken links.
Suggestion: Confirm no documentation references this file; if so, update those references simultaneously.
[S-03] Python SDK Pinned Exactly; TypeScript Uses Caret Range
File: requirements.txt vs client/typescript/package.json
Description: The Python SDK is pinned exactly (==0.5.0) while the TypeScript SDK uses a caret range (^0.5.0), allowing automatic minor/patch upgrades. For a demo project this inconsistency is low risk, but aligning the versioning strategy across languages improves predictability.
Suggestion: Either pin both exactly for reproducibility, or use compatible ranges in both.
Positive Observations
| Area | Observation |
|---|---|
| Security | Removing raw private keys from environment variables is a significant security improvement — it eliminates the most common accidental secret-exposure vector in blockchain projects. |
| GasFree handling | The conditional GasFree enablement in facilitator/main.py (based on presence of API credentials) is a clean, non-breaking degradation pattern. |
| BSC signer deduplication | The old code created two separate EvmFacilitatorSigner instances (bsc_signer and bsc_mainnet_signer) from the same private key. The new code correctly uses a single bsc_signer for both testnet and mainnet mechanisms. |
| SDK migration | Moving from a GitHub source reference to a published PyPI package (==0.5.0) improves reproducibility and CI reliability. |
| TypeScript policy inline | The TypeScript PreferGasFreeUSDTPolicy is expressed as a clean anonymous object implementing the PaymentPolicy interface, which is more idiomatic TypeScript than a separate class. |
| Lockfile committed | Adding package-lock.json ensures reproducible installs for the TypeScript client. |
Checklist Results
| Category | Items Checked | Pass | Fail | N/A | Notes |
|---|---|---|---|---|---|
| Correctness | 8 | 5 | 3 | 0 | Unhandled wallet init errors (MJ-01), unconditional BSC creation (MJ-02), GasFree credential inconsistency (MN-02) |
| Security | 10 | 9 | 0 | 1 | Private key removal is a net security improvement; no new injection or auth issues |
| Performance | 7 | 7 | 0 | 0 | No performance regressions introduced |
| Code Quality | 10 | 7 | 3 | 0 | Deprecated API (MN-01), print ordering (MN-03), missing addresses (MJ-03, MN-04) |
| Testing | 7 | 0 | 0 | 7 | No tests in scope (demo project) |
| Documentation | 6 | 3 | 2 | 1 | No migration docs, missing address logging (S-01), unexplained asset deletion (S-02) |
| Compatibility | 5 | 3 | 1 | 1 | BSC now unconditionally required breaks TRON-only deployments (MJ-02) |
| Observability | 4 | 2 | 2 | 0 | TRON facilitator address missing (MJ-03), EVM address missing in TS client (MN-04) |
Disclaimer
This is an automated code review. It supplements but does not replace human review. The reviewer analysed only the diff between main and dev/agent_wallet_0316. Runtime behaviour of the @bankofai/agent-wallet SDK (e.g., key derivation, storage security, network calls made by create()), integration testing, and deployment impact are not covered by this report.
Report generated by Code Review Skill v1.0.0
Date: 2026-03-21
Code Review ReportProject: x402-demo PR OverviewBranch Information
Commit History
Review SummaryVerdict
Findings at a Glance
SummaryThis PR migrates private-key handling from raw environment variables ( The security direction is positive — eliminating plaintext private keys from Change Summary1. Agent Wallet Key Store Integration (Core Feature)
Purpose: Improve secret management by storing TRON and EVM private keys in a local encrypted keystore managed by 2. GasFree Per-Network Credentials
Purpose: Allow operators to configure distinct GasFree API credentials per TRON network (nile / shasta / mainnet), while preserving backward-compatible fallback to a single 3. FastAPI Lifespan Refactor
Purpose: Correct placement of async initialisation into an async context so that 4. Asset Cleanup
Detailed FindingsMajor[MJ-01] Unconditional EVM Signer Creation Breaks TRON-Only Deployments
Description In the original code, the EVM/BSC signer was guarded behind Code # client/python/main.py — now unconditional
evm_signer = await EvmClientSigner.create()
for bsc_network in [NetworkConfig.BSC_TESTNET, NetworkConfig.BSC_MAINNET]:
x402_client.register(bsc_network, ExactPermitEvmClientMechanism(evm_signer))
x402_client.register(bsc_network, ExactEvmClientMechanism(evm_signer))// client/typescript/src/main.ts — now unconditional
const evmSigner = await EvmClientSigner.create();
x402.register('eip155:97', new ExactPermitEvmClientMechanism(evmSigner));# facilitator/main.py — now unconditional
bsc_signer = await EvmFacilitatorSigner.create()
bsc_facilitator_address = bsc_signer.get_address()Recommendation Wrap each EVM # Python pattern
try:
evm_signer = await EvmClientSigner.create()
for bsc_network in [NetworkConfig.BSC_TESTNET, NetworkConfig.BSC_MAINNET]:
x402_client.register(bsc_network, ExactPermitEvmClientMechanism(evm_signer))
x402_client.register(bsc_network, ExactEvmClientMechanism(evm_signer))
print(f"EVM Address: {evm_signer.get_address()}")
except Exception:
print("EVM: not configured (no EVM key in agent-wallet keystore)")[MJ-02] No Error Handling on Async Wallet
|
| Property | Value |
|---|---|
| Severity | Major |
| Category | Correctness / Observability |
| File | client/python/main.py, client/typescript/src/main.ts, facilitator/main.py |
Description
None of the await TronClientSigner.create(), await EvmClientSigner.create(), await TronFacilitatorSigner.create(), or await EvmFacilitatorSigner.create() calls are wrapped in error handling. If the agent-wallet keystore has not been initialised (npx agent-wallet init not yet run), is corrupted, or is otherwise inaccessible, the process will crash with a raw library exception stack trace.
The old code provided an explicit, user-friendly failure message (e.g., "❌ Error: TRON_PRIVATE_KEY not set in .env file") and a clean exit(1). The new code provides no equivalent feedback, making diagnosis significantly harder for operators.
Code
# facilitator/main.py — no guard or error message
async def lifespan(app: FastAPI):
tron_signer = await TronFacilitatorSigner.create() # crashes with raw exception if keystore missing
...
bsc_signer = await EvmFacilitatorSigner.create() # sameRecommendation
Add try/except around the mandatory TRON signer creation with a helpful error message, and exit cleanly:
try:
tron_signer = await TronFacilitatorSigner.create()
except Exception as exc:
print(f"\n❌ Failed to load TRON key from agent-wallet keystore: {exc}")
print("Run: npx agent-wallet init")
sys.exit(1)Apply the same pattern in all three entry points.
[MJ-03] TRON and EVM Addresses Silently Removed from Startup Output (Python Client)
| Property | Value |
|---|---|
| Severity | Major |
| Category | Observability |
| File | client/python/main.py : Lines ~111–116 (removed) |
Description
The old code printed the active TRON and EVM addresses on startup:
TRON Address: T...
EVM Address: 0x...
Resource URL: http://...
These lines were entirely removed from the Python client with no replacement. Operators lose the ability to verify which wallet addresses are active — critical for payment flows. The TypeScript client still prints TRON Address after the refactor, but removes the EVM address line (a lesser version of the same problem).
Code
# Removed — was previously in client/python/main.py
print(f"TRON Address: {tron_signer.get_address()}")
if BSC_PRIVATE_KEY:
print(f"EVM Address: {evm_signer.get_address()}")
print(f"Resource URL: {RESOURCE_URL}")Recommendation
Restore address logging after successful signer creation in both Python and TypeScript clients. Minimally:
print(f"TRON Address : {tron_signer.get_address()}")
print(f"Resource URL : {RESOURCE_URL}")
# After conditional EVM init:
print(f"EVM Address : {evm_signer.get_address()}")Minor
[MN-01] BSC Facilitator Address Printed Before Config Banner
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Code Quality |
| File | facilitator/main.py : Lines ~147–163 |
Description
Due to code restructuring inside the lifespan function, the BSC facilitator address and fee are printed before the "X402 Payment Facilitator – Configuration" banner. The output order is now:
BSC Facilitator Address: 0x...
BSC Base Fee: {...}
================================================================================
X402 Payment Facilitator - Configuration
================================================================================
TRON Base Fee: ...
This is cosmetically inconsistent and confusing: the BSC details appear before the section header that is supposed to contain them.
Recommendation
Move the BSC print statements to inside the config banner block, after the header:
print("=" * 80)
print("X402 Payment Facilitator - Configuration")
print("=" * 80)
print(f"TRON Base Fee: {TRON_BASE_FEE}")
print(f"BSC Facilitator Address: {bsc_facilitator_address}")
print(f"BSC Base Fee: {BSC_BASE_FEE}")
...[MN-02] EVM Address Removed from TypeScript Client Startup Log
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Observability |
| File | client/typescript/src/main.ts : Lines ~84–88 (removed) |
Description
The TypeScript client used to conditionally print the EVM address when BSC_PRIVATE_KEY was set. After the refactor, the EVM signer is always created but its address is never displayed, making it impossible for the operator to confirm which EVM wallet is active.
Recommendation
Add console.log( EVM Address : ${evmSigner.getAddress()}); after the unconditional evmSigner creation (mirroring how TRON address is shown just above).
[MN-03] Single Signer Instance Used for Both BSC Testnet and Mainnet in Facilitator
| Property | Value |
|---|---|
| Severity | Minor |
| Category | Correctness |
| File | facilitator/main.py : Lines ~121–145 |
Description
The old code created two separate signer instances from BSC_PRIVATE_KEY:
bsc_signerfor BSC Testnetbsc_mainnet_signerfor BSC Mainnet
The new code creates a single bsc_signer and reuses it for both networks. While this is likely functionally correct (same key on both networks), it's a silent behavioral change. If the underlying signer object maintains network-specific state (e.g., nonce tracking, connection pooling, or RPC endpoints), sharing one instance across two networks could cause unexpected behaviour under concurrent settlement operations.
Recommendation
Verify that EvmFacilitatorSigner is stateless (or safely reentrant across multiple concurrent networks). If any network-specific state is held, create separate instances per network. Add a comment documenting the intentional sharing.
Suggestions
[S-01] package-lock.json Is a New First-Time Commit — Verify CI Compatibility
File: client/typescript/package-lock.json
Description: A 3,057-line package-lock.json was added to the repository for the first time. This is correct practice for reproducible builds. However, if any CI or Docker build step uses npm install instead of npm ci, the lock file will be silently ignored.
Suggestion: Audit Dockerfile and CI scripts to ensure they run npm ci (which enforces lock-file fidelity) rather than npm install.
[S-02] Exact Version Pin for bankofai-x402 May Be Too Strict
File: requirements.txt
Description: The dependency is now pinned as bankofai-x402[tron,fastapi]==0.5.0. Exact pins prevent automatic uptake of patch-level security fixes.
Suggestion: Consider using a compatible-release specifier ~=0.5.0 to allow patch updates while pinning to the 0.5.x line.
[S-03] Trust Model of @bankofai/agent-wallet Not Documented
File: README.md, client/typescript/package-lock.json
Description: The PR introduces a new dependency on @bankofai/agent-wallet@2.3.0 which manages encrypted private-key storage. The README documents how to use it but says nothing about where the keystore is stored on disk, what encryption algorithm is used, or what the threat model is.
Suggestion: Add a brief security note to the README (e.g., "keystore is stored at ~/.agent-wallet/ encrypted with AES-256-GCM; protect this directory") so operators can make informed security decisions.
Positive Observations
| Area | Observation |
|---|---|
| Security | Replacing plaintext .env private keys with an encrypted keystore is a meaningful, correct security improvement. |
| Async architecture | Migrating FastAPI startup to the @asynccontextmanager lifespan pattern is the modern, recommended approach and allows await to be used safely for key initialisation. |
| GasFree flexibility | The new per-network GASFREE_API_KEY_{NETWORK} env vars with generic fallback is a clean, backward-compatible design. |
| Dependency hygiene | Switching from a git-source reference (git+https://...@v0.4.4) to a stable PyPI release (==0.5.0) improves build reproducibility and supply chain transparency. |
| Code deduplication | The facilitator now uses a single bsc_signer for both testnet and mainnet BSC mechanisms instead of instantiating two identical signers — reducing redundant initialization. |
| Documentation | The README upgrade path for existing users (TRON_PRIVATE_KEY → npx agent-wallet init) is clear and helpful. |
Checklist Results
| Category | Items Checked | Pass | Fail | N/A | Notes |
|---|---|---|---|---|---|
| Correctness | 8 | 5 | 3 | 0 | Unconditional EVM init; missing error handling; removed address diagnostics |
| Security | 10 | 9 | 0 | 1 | Positive improvement overall; no injection risks; CORS allow_origins=["*"] unchanged from main |
| Performance | 7 | 7 | 0 | 0 | No regressions introduced |
| Code Quality | 10 | 8 | 2 | 0 | Print ordering bug; leading-underscore var names slightly inconsistent |
| Testing | 7 | 0 | 0 | 7 | No tests present in diff; test suite not in scope of changed files |
| Documentation | 6 | 5 | 1 | 0 | Agent-wallet security model not documented |
| Compatibility | 5 | 3 | 2 | 0 | Breaking: TRON-only deployments break; SDK version bump from 0.4.x to 0.5.x |
| Observability | 4 | 2 | 2 | 0 | TRON/EVM addresses removed from Python client output; BSC address out-of-order in facilitator |
Disclaimer
This is an automated code review. It supplements but does not replace human review. The reviewer analyzed only the diff between main and dev/agent_wallet_0316. Runtime behaviour, integration testing, keystore encryption correctness, and deployment impact are not covered.
Report generated by Code Review Skill v1.0.0
Date: 2026-03-21
No description provided.