Skip to content

Extract wallet API route registration#348

Open
s0584273828-ctrl wants to merge 1 commit into
ramimbo:mainfrom
s0584273828-ctrl:codex/main-code-health-320
Open

Extract wallet API route registration#348
s0584273828-ctrl wants to merge 1 commit into
ramimbo:mainfrom
s0584273828-ctrl:codex/main-code-health-320

Conversation

@s0584273828-ctrl
Copy link
Copy Markdown
Contributor

@s0584273828-ctrl s0584273828-ctrl commented May 26, 2026

Summary

  • Extract wallet API route registration from app/main.py into focused app/wallet_api.py.
  • Move wallet registration, wallet lookup, GitHub wallet linking, GitHub balance claim, and signed wallet transfer routes behind one route-registration boundary.
  • Keep app/main.py responsible for app wiring and shared dependencies while preserving the existing public API behavior.
  • Extend wallet OpenAPI regression coverage to keep the moved lookup, claim, and transfer routes registered with the expected methods.

Complexity reduced

app/main.py no longer owns wallet-specific request parsing, ledger error handling, wallet lookup, GitHub link/claim, and transfer response shaping for seven wallet-related routes. The wallet API can now be reviewed independently from bounty, admin, webhook, OAuth/session, MCP, ledger, and page routing code.

Why this is distinct

#329 extracted MCP transport, #330 extracted public serializers, #343 extracted admin webhook helpers, #337 targets bounty-attempt routing, and #338 targets auth/session helpers. This PR targets the wallet API route boundary only.

Validation

  • uv run --extra dev python -m pytest tests/test_wallet_api.py -q -> 30 passed.
  • uv run --extra dev python -m pytest -q -> 332 passed.
  • uv run --extra dev ruff check . -> passed.
  • uv run --extra dev ruff format --check . -> 47 files already formatted.
  • uv run --extra dev python -m mypy app -> success.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check -> clean.

MRWK

Bounty #320

No private keys, seed material, secrets, deployment credentials, private vulnerability details, payout credentials, or MRWK price claims are included.

Summary by CodeRabbit

  • Refactor

    • Wallet-related API endpoints consolidated under a single registration mechanism; some boundary GET endpoints are hidden from the public API contract.
  • Bug Fixes

    • Transfers now reject replayed signed requests, returning an error for invalid nonces.
  • Tests

    • Added tests for transfer replay protection and for expected visibility of wallet/github/transfer routes in the API contract.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8c38a955-3823-4594-84fb-22c70fffb60d

📥 Commits

Reviewing files that changed from the base of the PR and between 866a250 and 8158de8.

📒 Files selected for processing (3)
  • app/main.py
  • app/wallet_api.py
  • tests/test_wallet_api.py

📝 Walkthrough

Walkthrough

Moves wallet-related REST endpoint implementations from app/main.py into a new app/wallet_api.py module and registers those routes from app/main.py; tests updated to cover transfer replay rejection and OpenAPI visibility.

Changes

Wallet API Route Consolidation

Layer / File(s) Summary
Wallet API module structure and dependency contract
app/wallet_api.py
Adds app/wallet_api.py with FastAPI/DB/ledger imports and public typing aliases for injected JSON loader, login dependency, required/optional field extractors, wallet address normalizer, and post-only guard.
Wallet API route registration and handlers
app/wallet_api.py
Implements register_wallet_api_routes() registering POST endpoints (wallet register, link-github, github claim, transfers), schema-hidden GET proxies that enforce POST-only, and GET /api/v1/wallets/{address} using session_scope(db_url), ledger service calls, LedgerError→HTTP 400 mapping, and serializers.
Main app route registration wiring
app/main.py
Removes direct imports of wallet ledger submission functions, imports register_wallet_api_routes, and calls it during app setup with db_url, require_github_login, JSON/field helpers, address normalizer, and post_only_route.
Tests: transfer replay and OpenAPI assertions
tests/test_wallet_api.py
Adds wallet_transfer_payload import and a test that posts the same signed /api/v1/transfers body twice asserting the second returns 400 with detail == "invalid nonce"; extends OpenAPI assertions to include GET /api/v1/wallets/{address}, POST /api/v1/github/claim, and POST /api/v1/transfers in /openapi.json.

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: extracting wallet API route registration from app/main.py into a new focused module.
Description check ✅ Passed The description is comprehensive, covering summary, rationale, validation results, and MRWK compliance. All required template sections are addressed with detailed information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_wallet_api.py (1)

267-278: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a replay regression for the extracted signed routes.

This coverage update verifies registration, but it still leaves replay protection untested after the route move. Please add a submit-twice assertion for /api/v1/transfers (same signed body and nonce, second call rejected) so the extraction keeps nonce replay enforcement under test.

Proposed test shape
+def test_wallet_transfer_api_rejects_replay(sqlite_url: str) -> None:
+    create_schema(sqlite_url)
+    sender_key, sender_public, sender_address = _keypair()
+    _, receiver_public, receiver_address = _keypair()
+    client = TestClient(create_app(database_url=sqlite_url, webhook_secret="secret"))
+    _register_wallet(client, sender_public)
+    _register_wallet(client, receiver_public)
+    _fund_wallet(sqlite_url, sender_address)
+
+    payload = {
+        "type": "mrwk_transfer_v1",
+        "from_address": sender_address,
+        "to_address": receiver_address,
+        "amount_microunits": 1_000_000,
+        "nonce": 1,
+        "memo": "",
+    }
+    body = {
+        "from_address": sender_address,
+        "to_address": receiver_address,
+        "amount_mrwk": "1",
+        "nonce": 1,
+        "memo": "",
+        "signature_hex": _sign(sender_key, payload),
+    }
+
+    first = client.post("/api/v1/transfers", json=body)
+    second = client.post("/api/v1/transfers", json=body)
+
+    assert first.status_code == 200
+    assert second.status_code == 400
+    assert second.json()["detail"] == "invalid nonce"

As per coding guidelines, tests/**/*.py: Add or update tests for changed behavior; {app/wallets.py,tests/**/*wallet*}: Wallet changes require signature, nonce, replay, and spend tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_wallet_api.py` around lines 267 - 278, Update the
test_wallet_method_boundary_routes_are_hidden_from_openapi test to include a
replay-regression: construct a valid signed transfer request (use the same
signing helper/logic used elsewhere in tests) and POST it to "/api/v1/transfers"
twice using the same body and nonce; assert the first response is accepted (2xx)
and the second response is rejected due to nonce replay (expect 4xx and/or a
specific replay error). Locate signing helpers and the TestClient/create_app
usage in this test to reuse the same signing/nonce generation so the second
submission reuses the identical nonce.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/test_wallet_api.py`:
- Around line 267-278: Update the
test_wallet_method_boundary_routes_are_hidden_from_openapi test to include a
replay-regression: construct a valid signed transfer request (use the same
signing helper/logic used elsewhere in tests) and POST it to "/api/v1/transfers"
twice using the same body and nonce; assert the first response is accepted (2xx)
and the second response is rejected due to nonce replay (expect 4xx and/or a
specific replay error). Locate signing helpers and the TestClient/create_app
usage in this test to reuse the same signing/nonce generation so the second
submission reuses the identical nonce.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0db98a8b-a58e-482d-8d4e-4b292bc3b04e

📥 Commits

Reviewing files that changed from the base of the PR and between ac99d79 and 8f5f205.

📒 Files selected for processing (3)
  • app/main.py
  • app/wallet_api.py
  • tests/test_wallet_api.py

Copy link
Copy Markdown
Contributor

@TateLyman TateLyman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the wallet API route extraction for Bounty #320.\n\nWhat I checked:\n- app/wallet_api.py: moved wallet registration, wallet lookup, GitHub wallet linking, GitHub balance claim, and signed wallet transfer routes behind register_wallet_api_routes().\n- app/main.py: now wires that route module with the same dependencies for GitHub login, JSON body parsing, string/int validation, wallet normalization, and post-only redirects.\n- tests/test_wallet_api.py and tests/test_wallets.py: existing API and service coverage still exercises registration, lookup, transfer validation, link/claim auth boundaries, nonce handling, and signed transfer replay rejection.\n- CodeRabbit's replay-regression suggestion is useful extra route-level coverage, but I do not see a behavioral blocker in this extraction because the moved /api/v1/transfers handler still delegates to the same submit_wallet_transfer() service path, and the nonce/replay service regression remains green.\n\nValidation run locally on current head 8f5f205:\n- uv run --extra dev python -m pytest tests/test_wallet_api.py tests/test_wallets.py tests/test_security.py -q -> 94 passed.\n- uv run --extra dev python -m pytest -q -> 332 passed.\n- uv run --extra dev ruff check app/main.py app/wallet_api.py tests/test_wallet_api.py -> passed.\n- uv run --extra dev ruff format --check app/main.py app/wallet_api.py tests/test_wallet_api.py -> passed.\n- uv run --extra dev python -m mypy app/main.py app/wallet_api.py -> passed.\n- uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.\n- git diff --check origin/main...HEAD -> clean.\n\nNo blocker found. GitHub reports mergeState CLEAN, hosted quality is green, and CodeRabbit status is green.

@s0584273828-ctrl
Copy link
Copy Markdown
Contributor Author

Updated after the CodeRabbit replay-coverage note.

Change added:

  • test_wallet_transfer_api_rejects_replayed_signed_body now posts the same signed /api/v1/transfers request twice and asserts the replay is rejected with invalid nonce.

Validation after the update:

  • uv run --extra dev python -m pytest tests/test_wallet_api.py -q -> 31 passed
  • uv run --extra dev python -m pytest -q -> 333 passed
  • uv run --extra dev ruff check tests/test_wallet_api.py -> passed
  • uv run --extra dev ruff format --check tests/test_wallet_api.py -> passed
  • uv run --extra dev python -m mypy app -> no issues in 14 source files

Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the wallet API extraction for #320/#340.

Evidence checked:

  • Inspected app/wallet_api.py route registration for wallet registration, lookup, GitHub link, GitHub claim, and signed transfer submission.
  • Checked app/main.py now delegates wallet API route wiring through register_wallet_api_routes while keeping page routes and MCP wallet tools separate.
  • Checked tests/test_wallet_api.py additions for replayed signed transfer rejection and OpenAPI/method-boundary coverage.

Commands run locally on PR head 866a250:

  • python -m pytest tests\test_wallet_api.py::test_wallet_api_register_lookup_and_transfer tests\test_wallet_api.py::test_wallet_transfer_api_rejects_replayed_signed_body tests\test_wallet_api.py::test_wallet_transfer_api_rejects_invalid_requests tests\test_wallet_api.py::test_wallet_method_boundary_routes_are_hidden_from_openapi tests\test_wallet_api.py::test_wallet_api_malformed_transfer_requests_return_4xx tests\test_wallet_api.py::test_wallet_api_malformed_link_and_claim_requests_return_4xx tests\test_wallet_api.py::test_wallet_link_and_claim_require_github_login tests\test_wallet_api.py::test_github_session_can_link_and_claim_wallet -q -> 11 passed
  • python -m ruff check app\main.py app\wallet_api.py tests\test_wallet_api.py -> passed
  • git diff --check origin/main...HEAD -> passed

I also tried python -m pytest tests\test_wallet_api.py -q; it currently fails only on test_github_login_stores_safe_default_for_backslash_next, which exercises OAuth next-path handling outside the wallet API extraction. I did not find a blocker in the changed wallet route boundary.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_wallet_api.py`:
- Around line 309-311: The current tests only verify presence of allowed HTTP
methods for OpenAPI routes but not that disallowed methods are absent; update
the assertions that reference paths (the paths dict) and the specific endpoints
"/api/v1/wallets/{address}", "/api/v1/github/claim", and "/api/v1/transfers" to
assert exclusivity instead of mere presence — e.g. compare the set of keys for
paths["/api/v1/wallets/{address}"] to {"get"} and for the other two endpoints to
{"post"} (or explicitly assert that unwanted methods like "put","patch","delete"
are not in paths[...].keys()), so the test fails if extra methods appear.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c15ffbcf-b211-480d-a63f-21919659b745

📥 Commits

Reviewing files that changed from the base of the PR and between 8f5f205 and 866a250.

📒 Files selected for processing (3)
  • app/main.py
  • app/wallet_api.py
  • tests/test_wallet_api.py

Comment thread tests/test_wallet_api.py Outdated
Copy link
Copy Markdown
Contributor

@ayskobtw-lil ayskobtw-lil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review on current head 866a250.

Good: the wallet API extraction is wired cleanly through register_wallet_api_routes(), and the moved register, lookup, link, claim, and transfer handlers still delegate to the same ledger/wallet service boundaries. The new replay regression also covers the transferred /api/v1/transfers route directly.

One test-hardening item remains: test_wallet_method_boundary_routes_are_hidden_from_openapi now asserts that /api/v1/wallets/{address}, /api/v1/github/claim, and /api/v1/transfers expose the expected method, but only by checking presence. It would still pass if those schema-visible routes accidentally exposed extra methods later. Since this test is specifically guarding the method boundary after moving route registration, please assert exact method sets for the newly added routes, e.g. set(paths["/api/v1/wallets/{address}"]) == {"get"} and set(paths["/api/v1/github/claim"]) == {"post"}, set(paths["/api/v1/transfers"]) == {"post"}. The existing register/link-github checks already prove the hidden GET proxies stay out of OpenAPI.

Validation:

  • python -m pytest tests/test_wallet_api.py::test_wallet_transfer_api_rejects_replayed_signed_body tests/test_wallet_api.py::test_wallet_method_boundary_routes_are_hidden_from_openapi tests/test_wallet_api.py::test_wallet_api_register_lookup_and_transfer -q -> 3 passed.
  • python -m pytest tests/test_wallet_api.py -q -> 31 passed.
  • python -m ruff check app/wallet_api.py app/main.py tests/test_wallet_api.py -> passed.
  • python -m ruff format --check app/wallet_api.py app/main.py tests/test_wallet_api.py -> 3 files already formatted.
  • python -m mypy app/wallet_api.py app/main.py -> success.
  • git diff --check origin/main...HEAD -> clean.

No runtime blocker found in the route extraction, but the OpenAPI method-boundary regression should be tightened so it protects the behavior it names.

@s0584273828-ctrl s0584273828-ctrl force-pushed the codex/main-code-health-320 branch from 866a250 to 8158de8 Compare May 26, 2026 04:52
Copy link
Copy Markdown
Contributor

@newmattock newmattock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review on current head 8158de8: the wallet method-boundary hardening item is addressed, and I do not see a blocker.

Evidence checked:

  • Inspected app/wallet_api.py, app/main.py, and tests/test_wallet_api.py.
  • Verified register_wallet_api_routes() keeps wallet registration, lookup, GitHub linking, GitHub claim, and signed transfer submission behind the wallet API boundary while app.main only wires shared dependencies.
  • Rechecked the prior OpenAPI method-boundary concern: test_wallet_method_boundary_routes_are_hidden_from_openapi now asserts exact method sets for /api/v1/wallets/register, /api/v1/wallets/link-github, /api/v1/wallets/{address}, /api/v1/github/claim, and /api/v1/transfers, so extra schema-visible methods would fail the regression.
  • Confirmed the route-level replay regression for /api/v1/transfers is present and passes.

Validation:

  • uv run --extra dev python -m pytest tests/test_wallet_api.py::test_wallet_transfer_api_rejects_replayed_signed_body tests/test_wallet_api.py::test_wallet_method_boundary_routes_are_hidden_from_openapi tests/test_wallet_api.py::test_wallet_api_register_lookup_and_transfer -q -> 3 passed.
  • uv run --extra dev python -m pytest tests/test_wallet_api.py -q -> 31 passed.
  • uv run --extra dev python -m mypy app/main.py app/wallet_api.py -> success.
  • uv run --extra dev python -m ruff check app/main.py app/wallet_api.py tests/test_wallet_api.py -> passed.
  • uv run --extra dev python -m ruff format --check app/main.py app/wallet_api.py tests/test_wallet_api.py -> 3 files already formatted.

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 26, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Maintainer note after #320 filled: this PR still references closed bounty #320. If you want it considered for the current code-health round, rebase against main, update the bounty reference to #377, and keep the scope distinct from already accepted #328, #337, #364, #375, and #376. This is not accepted or paid as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants