Skip to content

Support multiple validator operator wallets#902

Merged
JoaquinBN merged 5 commits into
devfrom
JoaquinBN/fix-validator-wallet-linking
Jul 6, 2026
Merged

Support multiple validator operator wallets#902
JoaquinBN merged 5 commits into
devfrom
JoaquinBN/fix-validator-wallet-linking

Conversation

@JoaquinBN

@JoaquinBN JoaquinBN commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a first-come-first-served operator wallet claim table for validator profiles
  • allow one validator profile to link multiple operator wallets across Asimov and Bradbury
  • update validator sync to resolve and clear portal attribution from claimed operator wallets
  • refresh the profile operator wallet panel to show linked operators, matched wallets, and networks

Validation

  • backend/env/bin/python backend/manage.py test validators
  • backend/env/bin/python backend/manage.py test validators.tests.test_api ethereum_auth.tests
  • backend/env/bin/python backend/manage.py makemigrations validators ethereum_auth --check --dry-run
  • git diff --check
  • node --check frontend/src/lib/api.js
  • direct Svelte compiler check for frontend/src/routes/ProfileEdit.svelte

Note: full Vite build was not run because the local environment has Node 22.10.0 while Vite requires 20.19+ or 22.12+, and the Rolldown native optional binding is missing.

Summary by CodeRabbit

  • New Features

    • Added support for linking and viewing operator wallets in validator settings.
    • Validator profiles now show linked operator wallets with matched wallet counts, active wallet counts, and network labels.
  • Bug Fixes

    • Improved wallet linking so existing synced wallets can be claimed more reliably.
    • Prevented duplicate operator claims by different validators.
    • Updated wallet syncing to better keep operator links in sync when ownership changes.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@JoaquinBN, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ab9f2d5e-618c-40ab-a72d-e1c63c0dc958

📥 Commits

Reviewing files that changed from the base of the PR and between 51e378d and 39e9262.

📒 Files selected for processing (8)
  • backend/validators/admin.py
  • backend/validators/genlayer_validators_service.py
  • backend/validators/migrations/0017_validatoroperatorwallet.py
  • backend/validators/models.py
  • backend/validators/serializers.py
  • backend/validators/tests/test_api.py
  • backend/validators/views.py
  • frontend/src/routes/ProfileEdit.svelte
📝 Walkthrough

Walkthrough

This PR introduces a ValidatorOperatorWallet model linking operator addresses to validators, with a migration backfilling data from existing ValidatorWallet records. It adds admin registration, a serializer exposing wallet aggregates, reworks the link_by_operator API endpoint and sync-service operator resolution logic, adds backend tests, and updates the ProfileEdit frontend to display and link operator wallets.

Changes

Operator Wallet Feature

Layer / File(s) Summary
Model and migration
backend/validators/models.py, backend/validators/migrations/0016_validatoroperatorwallet.py
Adds ValidatorOperatorWallet model (unique lowercase address, FK to Validator) and a migration creating the table plus a backfill from existing ValidatorWallet operator data.
Validator sync operator resolution
backend/validators/genlayer_validators_service.py
Reworks _process_validator to resolve desired_operator via ValidatorOperatorWallet lookup or User match with get_or_create, updating wallet.operator accordingly.
Serializer and link-by-operator API
backend/validators/serializers.py, backend/validators/views.py
Adds ValidatorOperatorWalletSerializer computing wallet/active counts and networks; reworks link_by_operator transaction locking, conflict detection, and response, and adds operator_wallets to my_wallets.
Admin registration
backend/validators/admin.py
Adds ValidatorOperatorWalletInline on ValidatorAdmin and a standalone ValidatorOperatorWalletAdmin, both computing wallet_count.
Backend tests
backend/validators/tests/test_api.py
Adds ValidatorOperatorWalletLinkTests covering multi-network linking, multi-claim, conflicts, 404s, and sync operator assignment.
ProfileEdit UI
frontend/src/routes/ProfileEdit.svelte
Adds operatorWallets state, formatting helpers, and an "Operator wallets" UI section replacing the prior conditional link input.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant View as link_by_operator view
  participant DB as Database

  Client->>View: POST operator_address
  View->>DB: select_for_update ValidatorOperatorWallet by address
  View->>DB: lock matching ValidatorWallet rows by operator_address
  View->>DB: check wallets linked to different validator
  alt conflict with different validator
    View-->>Client: 409 conflict
  else no conflict
    View->>DB: create/reuse ValidatorOperatorWallet
    View->>DB: link matching ValidatorWallet rows to validator
    View-->>Client: wallets_linked, operator_wallet
  end
Loading

Poem:
A rabbit hops through wallets deep,
Linking operators while others sleep,
Address lowercased, claims held tight,
409s block the wrongful bite,
New cards bloom on the profile screen — 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding support for multiple validator operator wallets.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch JoaquinBN/fix-validator-wallet-linking

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 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 `@backend/validators/admin.py`:
- Around line 22-37: The `wallet_count` logic is duplicated between
`ValidatorOperatorWalletInline` and `ValidatorOperatorWalletAdmin`, so
consolidate it into one shared implementation to prevent future drift. Add a
shared helper on `ValidatorOperatorWallet` (for example a
`matching_wallet_count()` method) or a module-level function that performs the
`ValidatorWallet.objects.filter(operator_address__iexact=...).count()` lookup,
then have both admin `wallet_count` methods delegate to that shared symbol while
preserving the `'-'` fallback for unsaved objects.
- Around line 103-116: `ValidatorOperatorWalletAdmin.wallet_count` is causing an
N+1 query in the changelist by counting `ValidatorWallet` per row; move this
computation into `ValidatorOperatorWalletAdmin.get_queryset` by annotating the
count on the base queryset so `list_display` can read it without extra queries.
Use the admin class and `wallet_count` method as the lookup points, and keep the
address matching consistent with the normalization performed in
`ValidatorOperatorWallet.save()` before removing `__iexact`.

In `@backend/validators/genlayer_validators_service.py`:
- Around line 584-586: The sync logic in genlayer_validators_service should not
blindly overwrite wallet.operator with None when no matching
ValidatorOperatorWallet claim or User exists. Update the operator reconciliation
around the desired_operator assignment and the wallet.operator update so
existing non-claim-based links are preserved unless you explicitly intend to
clear them, and verify that any legitimate operator linkage handled by this sync
path is always backed by a ValidatorOperatorWallet record.
- Around line 571-582: The user lookup in the operator-wallet linking logic can
fail on case-insensitive duplicates because
User.objects.get(address__iexact=wallet.operator_address) may raise
MultipleObjectsReturned. Update the lookup in this block to use a safer
case-insensitive fetch such as filter(...).first(), or otherwise handle
duplicate mixed-case rows explicitly before using user.validator and
ValidatorOperatorWallet.get_or_create.

In `@backend/validators/serializers.py`:
- Around line 81-91: The computed fields in the serializer are re-running
`_wallets(obj)` for `get_wallet_count`, `get_active_wallet_count`, and
`get_networks`, causing multiple queries per object. Update the serializer
methods in `backend/validators/serializers.py` so
`ValidatorWallet.objects.filter(operator_address__iexact=obj.address)` is
evaluated once per object (for example by caching the queryset or fetching the
needed data in one pass) and then derive the count, active count, and unique
networks from that single result inside `get_wallet_count`,
`get_active_wallet_count`, and `get_networks`.

In `@backend/validators/views.py`:
- Around line 242-277: The operator wallet claim flow in the validation view has
a race condition for first-time claims because `existing_claim` can be `None` in
concurrent requests and both can reach
`ValidatorOperatorWallet.objects.create(...)`. Update this branch to use a
conflict-safe pattern in the same validator claim logic: either `get_or_create`
or a `try/except IntegrityError` around
`ValidatorOperatorWallet.objects.create`, then re-fetch the existing row and
continue. Keep the fix localized around `existing_claim`, `wallets`, and
`operator_wallet` in the validator operator claim path.
- Around line 233-278: The first-time claim path in the operator wallet flow can
still race when ValidatorOperatorWallet.objects.create is called after the
existing_claim check, causing an uncaught IntegrityError and a 500. Update the
claim logic in backend/validators/views.py to catch that create conflict inside
the transaction.atomic block, around the ValidatorOperatorWallet.objects.create
call, and return the same 409 conflict response used for the existing_claim
path. Use the existing claim-handling logic in the validator/operator wallet
view to keep the response consistent.

In `@frontend/src/routes/ProfileEdit.svelte`:
- Around line 839-847: The operator address input in ProfileEdit.svelte lacks an
accessible name because it only uses a placeholder. Add an associated label for
operatorAddress, such as a visible or visually hidden <label
for="operatorAddress">, or provide an equivalent aria-label directly on the
input so assistive technologies can identify it.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4d9a6144-967d-4f5e-beb0-286690b4a6d7

📥 Commits

Reviewing files that changed from the base of the PR and between e411ff4 and 51e378d.

📒 Files selected for processing (8)
  • backend/validators/admin.py
  • backend/validators/genlayer_validators_service.py
  • backend/validators/migrations/0016_validatoroperatorwallet.py
  • backend/validators/models.py
  • backend/validators/serializers.py
  • backend/validators/tests/test_api.py
  • backend/validators/views.py
  • frontend/src/routes/ProfileEdit.svelte

Comment thread backend/validators/admin.py
Comment thread backend/validators/admin.py
Comment thread backend/validators/genlayer_validators_service.py Outdated
Comment thread backend/validators/genlayer_validators_service.py
Comment thread backend/validators/serializers.py Outdated
Comment thread backend/validators/views.py
Comment thread backend/validators/views.py Outdated
Comment thread frontend/src/routes/ProfileEdit.svelte
@JoaquinBN JoaquinBN merged commit ab560a9 into dev Jul 6, 2026
3 checks passed
@JoaquinBN JoaquinBN deleted the JoaquinBN/fix-validator-wallet-linking branch July 6, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant