feat(api): v1 scan/* + watch-only + account-abstraction + tiered secret export (final route group) - #185
Conversation
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a new v1 "scan/accounts" API surface: an ChangesScan/Accounts v1 API
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant AccountsRouter
participant Governor
participant WalletAdmin
Client->>AccountsRouter: GET /api/v1/accounts/watch (T0)
AccountsRouter->>Governor: check CheapRead
AccountsRouter->>WalletAdmin: list watch scans
WalletAdmin-->>AccountsRouter: scan rows
AccountsRouter-->>Client: 200 items+page
Client->>AccountsRouter: POST /api/v1/scan/scans (T1)
AccountsRouter->>AccountsRouter: require_tier(Operator)
AccountsRouter->>WalletAdmin: register scan
WalletAdmin-->>AccountsRouter: scan_id
AccountsRouter-->>Client: 201 scan_id
Client->>AccountsRouter: POST /api/v1/accounts/private-key (T2)
AccountsRouter->>AccountsRouter: require_tier(Admin)
AccountsRouter->>WalletAdmin: get_private_key
WalletAdmin-->>AccountsRouter: secret scalar
AccountsRouter-->>Client: 200 private_key
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
19d7fdd to
dbf038d
Compare
e8ca23b to
992c0d5
Compare
dbf038d to
589d596
Compare
992c0d5 to
a0056f8
Compare
589d596 to
0b81c27
Compare
a0056f8 to
0bbfc28
Compare
0b81c27 to
0f5974c
Compare
0bbfc28 to
c2be87f
Compare
… secret gate (§3.10–§3.11)
The final v1 route group. Adds `/api/v1/scan/*` and `/api/v1/accounts/*` as a
new router that reuses the existing WalletAdmin machinery — never reimplements
scan matching or key derivation — and mounts ONLY prefixes it fully owns.
Why the new prefixes and not `/api/v1/wallet/*`: the pre-existing native wallet
surface owns that whole prefix via a `*rest` catch-all (like compat
`node/shutdown` for the operator group), so a sibling router adding wallet paths
would make `Router::merge` panic on the overlapping catch-all. Reads/build/sign/
send therefore stay on the native surface; this group delivers the genuinely-new
scan + account-abstraction surfaces and the correctly-tiered secret export.
- scan/* (T1, REAL): register/list/get/deregister, {id}/unspent, {id}/transactions,
{id}/boxes attach + detach — snake_case + cursor + nested-error adaptation over
the WalletAdmin scan trait methods; the ScanBoxFilter -1 legality rule is kept.
- watch-only (T0 read, T1 write, REAL): register via scan_p2s_rule, list via the
wallet_interaction="off" scan marker, unspent read via scan_unspent_boxes.
- named accounts + PSBT (T1, honest seams): designed surfaces answering
route_unavailable — no node backing exists (no account_id model, no durable
PSBT session / hints-bag sign variant). Never a fabricated balance or signature.
- private-key export (T2, REAL): `POST /api/v1/accounts/private-key`, admin
api-key + loopback-preferred via the fail-closed require_tier(Admin) gate,
acknowledge-gated, Cache-Control: no-store, backed by get_private_key.
Secret material is unreachable at T0/T1 and (under admin_hard_deny) from any
non-loopback caller — the gate rejects before the handler runs. 12 route tests
cover the T1 scan gate, watch-only reads, the seams, and the full T2 gate matrix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
c2be87f to
5af6b95
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
ergo-api/tests/v1_scan_accounts_routes.rs (1)
119-252: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider adding tests for untested scan/watch endpoints.
Several T1 endpoints have no test coverage:
DELETE /scan/scans/:id(deregister),GET /scan/scans/:id/unspent,GET /scan/scans/:id/transactions,POST /scan/scans/:id/boxes(attach),DELETE /scan/scans/:id/boxes/:box_id(detach), andDELETE /accounts/watch/:id(watch delete). The mock already implementsscan_add_box,scan_stop_tracking, andtransactions_by_scan_id, suggesting these were intended to be exercised. Adding at least a smoke test for each would prevent regressions in route wiring or tier gating.Also applies to: 70-91, 55-69
🤖 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 `@ergo-api/tests/v1_scan_accounts_routes.rs` around lines 119 - 252, Add smoke test coverage for the missing scan/watch routes using the existing test mock in the `v1_scan_accounts_routes` suite. Exercise the route handlers for `scan_add_box`, `scan_stop_tracking`, and `transactions_by_scan_id` through `DELETE /scan/scans/:id`, `GET /scan/scans/:id/unspent`, `GET /scan/scans/:id/transactions`, `POST /scan/scans/:id/boxes`, `DELETE /scan/scans/:id/boxes/:box_id`, and `DELETE /accounts/watch/:id` to verify wiring and tier gating. Keep the tests focused on confirming the endpoints respond and dispatch to the expected mock methods rather than deep business logic.
🤖 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 `@ergo-api/src/v1/accounts/mod.rs`:
- Around line 418-427: The T0 endpoint for scan unspent lookup currently passes
any scan_id through to scan::unspent, unlike watch_list which already enforces
watch-only scans. Update the scan::unspent path in
ergo-api/src/v1/accounts/mod.rs to verify the scan’s wallet_interaction is "off"
before returning results, and return scan_not_found for operator/non-watch
scans. Keep the check aligned with the existing watch_list filtering so the
/api/v1/accounts/watch/:scan_id/unspent route is scoped consistently.
In `@ergo-api/src/v1/accounts/scan.rs`:
- Around line 216-227: The ScanBoxFilter overfetch path is pushing a valid
?limit=2500 request past the validator cap because the code adds 1 to the
clamped limit before calling filter.validate(). Update the limit handling in the
scan logic around ScanBoxFilter so the internal overfetch stays at or below
2500, either by clamping the user-facing limit to 2499 before the
saturating_add(1) or by adjusting ScanBoxFilter::validate() to allow this
internal overfetch case.
---
Nitpick comments:
In `@ergo-api/tests/v1_scan_accounts_routes.rs`:
- Around line 119-252: Add smoke test coverage for the missing scan/watch routes
using the existing test mock in the `v1_scan_accounts_routes` suite. Exercise
the route handlers for `scan_add_box`, `scan_stop_tracking`, and
`transactions_by_scan_id` through `DELETE /scan/scans/:id`, `GET
/scan/scans/:id/unspent`, `GET /scan/scans/:id/transactions`, `POST
/scan/scans/:id/boxes`, `DELETE /scan/scans/:id/boxes/:box_id`, and `DELETE
/accounts/watch/:id` to verify wiring and tier gating. Keep the tests focused on
confirming the endpoints respond and dispatch to the expected mock methods
rather than deep business logic.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b592dc6a-8117-492d-a726-3da1c480a69d
📒 Files selected for processing (5)
ergo-api/src/server.rsergo-api/src/v1/accounts/mod.rsergo-api/src/v1/accounts/scan.rsergo-api/src/v1/mod.rsergo-api/tests/v1_scan_accounts_routes.rs
…unspent mount + limit-cap regression
- The T0 /accounts/watch/{scan_id}/unspent mount used the unscoped
scan::unspent handler, so a PUBLIC caller could read any operator
(wallet-interacting) scan's boxes. New watch_unspent wrapper enforces
the same wallet_interaction="off" scoping watch_list uses; non-watch
scans answer scan_not_found on the public mount (the T1 api-key mount
keeps the unscoped read).
- SCAN_BOX_MAX_LIMIT 2500 → 2499: the overfetch-by-one probe pushed a
valid ?limit=2500 past the ScanBoxFilter 1..=2500 validator (400).
Capping the probe instead would silently break has_more on exact-cap
pages; one-below keeps clamp semantics and honest pagination.
- tests: smoke coverage for deregister/unspent/transactions/attach/
detach/watch-delete wiring + 401 tier gating, plus regressions for
both fixes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
Bump workspace version to 0.5.2 and promote the changelog: the complete v1 product API (#168-#185, #188), shadow validation as a production mode (#193-#195), the operator observability wave (#187, #190, #192, #194), two live accept-invalid consensus fixes (#176, #179), ErgoScript compiler byte-parity completion (#165-#167, #175), and the #160-#163 sync/recovery fixes. Full workspace gate run on the merge result. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
What this is
The last v1 group:
scan/*, watch-only, account-abstraction, and the correctly-tiered secret export.Load-bearing discovery: the pre-existing native
/api/v1/wallet/*surface already OWNS that whole prefix via a*restcatch-all — so merging any new/api/v1/wallet/*sibling makesRouter::mergepanic on the matchit catch-all (same shape as the operator group'snode/shutdown). This group therefore mounts only prefixes it fully owns (scan/*,accounts/*) and does NOT re-mount wallet reads (no collision, no duplication).REAL vs seam
-1legality rule ported) / transactions / boxes attach+detach, over the existingWalletAdminscan methods.scan_p2s_rule(T1); list → scans filtered towallet_interaction=off(T0); {id}/unspent →scan_unspent_boxes(T0); delete (T1).POST /api/v1/accounts/private-key,get_private_key-backed, acknowledge-gated,no-store. Proven Admin + loopback gate: no-key → 401; valid key from non-loopback under hard-deny → 409 at the gate, beforeget_private_keyruns, noprivate_keyin the body; loopback+key → passes. No-key node-wide → both tiers fail closed.route_unavailable(no account_id model / durable PSBT session yet). No fabricated balances or signatures.The pre-existing native
init/restore/rescan/getPrivateKeyroutes sit behind a single weak api-key gate with no tier/loopback check — the real "secret at the wrong tier" exposure (design §0 drift #4). Retrofittingrequire_tieronto the native surface risks its pinned tests, so it's a separate change — this group ships the properly-gated export as the model for it.Other flags
Spec path deviations forced by the matchit catch-all (watch at
/accounts/watch*, PSBT at/transactions-psbt*); v1Reasonenum lackswallet_locked/wallet_exists(111-reason contract locked → mapped to closest with disambiguatingdetail,Internalnever leaks); watch registry has no label persistence (Phase-2).Test plan
🤖 Generated with Claude Code
https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
Summary by CodeRabbit
New Features
Bug Fixes