From 8d9b532d660071df6b33fa55c7b85eaaf20270b6 Mon Sep 17 00:00:00 2001 From: er1c-cartman <144649727+er1c-cartman@users.noreply.github.com> Date: Wed, 27 May 2026 09:24:04 +0900 Subject: [PATCH] Require registered wallet for account transfer status --- app/accounts.py | 19 +++++++-- tests/test_account_routes.py | 81 +++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/app/accounts.py b/app/accounts.py index c20ae16..fa7feb3 100644 --- a/app/accounts.py +++ b/app/accounts.py @@ -11,7 +11,7 @@ from app.db import session_scope from app.ledger.service import TREASURY_ACCOUNT, format_mrwk, get_balance from app.ledger_views import account_ledger_transactions -from app.models import Account +from app.models import Account, Wallet from app.path_params import SQLITE_INTEGER_MAX from app.serializers import ( accepted_work_for_account, @@ -77,7 +77,7 @@ def github_login_from_account(account: str) -> str | None: return login -def account_transfer_status(account: str) -> str: +def account_transfer_status(account: str, *, wallet_registered: bool = False) -> str: if account.startswith("github:"): return "Claim GitHub balances from /me after linking a registered mrwk1 wallet." if account.startswith(("treasury:", "reserve:")): @@ -85,19 +85,30 @@ def account_transfer_status(account: str) -> str: "Internal ledger account. MRWK wallet transfers are only available " "for registered mrwk1 addresses." ) - return "MRWK wallet transfers are enabled for registered mrwk1 addresses." + if account.startswith("mrwk1"): + if wallet_registered: + return "MRWK wallet transfers are enabled for registered mrwk1 addresses." + return "Wallet address is not registered yet. Register this mrwk1 wallet before transfers." + return ( + "This account is not eligible for MRWK wallet transfers. Use a github: " + "account or registered mrwk1 wallet." + ) def account_api_context(session: Session, account: str) -> dict[str, Any]: account = normalized_account(account) account_row = session.get(Account, account) + wallet_registered = account.startswith("mrwk1") and session.get(Wallet, account) is not None return { "account": account, "ledger_address": account, "github_login": github_login_from_account(account), "exists": account_row is not None, "balance_mrwk": format_mrwk(get_balance(session, account)), - "transfer_status": account_transfer_status(account), + "transfer_status": account_transfer_status( + account, + wallet_registered=wallet_registered, + ), "accepted_work": safe_account_accepted_summary(session, account), } diff --git a/tests/test_account_routes.py b/tests/test_account_routes.py index 2d413af..3adb4fa 100644 --- a/tests/test_account_routes.py +++ b/tests/test_account_routes.py @@ -4,7 +4,7 @@ from app.accounts import account_api_context, account_page_context, normalized_account from app.db import create_schema, session_scope -from app.ledger.service import create_bounty, ensure_genesis, pay_bounty +from app.ledger.service import create_bounty, ensure_genesis, pay_bounty, register_wallet from app.main import create_app @@ -89,6 +89,85 @@ def test_registered_account_routes_preserve_api_and_page_shapes(sqlite_url: str) assert f'href="/proofs/{proof.hash}"' in page_response.text +def test_unknown_account_transfer_status_does_not_report_wallet_ready(sqlite_url: str) -> None: + create_schema(sqlite_url) + client = TestClient(create_app(database_url=sqlite_url, webhook_secret="secret")) + + response = client.get("/api/v1/accounts/not-a-wallet") + + assert response.status_code == 200 + assert response.json()["exists"] is False + assert response.json()["transfer_status"] == ( + "This account is not eligible for MRWK wallet transfers. Use a github: " + "account or registered mrwk1 wallet." + ) + + +def test_unregistered_wallet_transfer_status_requires_registration(sqlite_url: str) -> None: + create_schema(sqlite_url) + client = TestClient(create_app(database_url=sqlite_url, webhook_secret="secret")) + account = "mrwk1" + ("0" * 40) + + response = client.get(f"/api/v1/accounts/{account}") + + assert response.status_code == 200 + assert response.json()["exists"] is False + assert response.json()["transfer_status"] == ( + "Wallet address is not registered yet. Register this mrwk1 wallet before transfers." + ) + + +def test_ledger_only_wallet_account_transfer_status_requires_registration( + sqlite_url: str, +) -> None: + create_schema(sqlite_url) + account = "mrwk1" + ("a" * 40) + with session_scope(sqlite_url) as session: + ensure_genesis(session) + bounty = create_bounty( + session, + repo="ramimbo/mergework", + issue_number=179, + issue_url="https://github.com/ramimbo/mergework/issues/179", + title="Ledger-only wallet account", + reward_mrwk="5", + acceptance="Account route should not treat ledger-only wallets as registered.", + ) + pay_bounty( + session, + bounty_id=bounty.id, + to_account=account, + submission_url="https://github.com/ramimbo/mergework/pull/179", + accepted_by="maintainer", + verifier_result={"label": "mrwk:accepted"}, + ) + + client = TestClient(create_app(database_url=sqlite_url, webhook_secret="secret")) + response = client.get(f"/api/v1/accounts/{account}") + + assert response.status_code == 200 + assert response.json()["exists"] is True + assert response.json()["balance_mrwk"] == "5" + assert response.json()["transfer_status"] == ( + "Wallet address is not registered yet. Register this mrwk1 wallet before transfers." + ) + + +def test_registered_wallet_transfer_status_reports_ready(sqlite_url: str) -> None: + create_schema(sqlite_url) + with session_scope(sqlite_url) as session: + wallet = register_wallet(session, public_key_hex="1" * 64, label="Main wallet") + + client = TestClient(create_app(database_url=sqlite_url, webhook_secret="secret")) + response = client.get(f"/api/v1/accounts/{wallet.address}") + + assert response.status_code == 200 + assert response.json()["exists"] is True + assert response.json()["transfer_status"] == ( + "MRWK wallet transfers are enabled for registered mrwk1 addresses." + ) + + def test_normalized_account_keeps_existing_account_validation_boundaries() -> None: assert normalized_account(" Reserve:Bounty:001 ") == "reserve:bounty:1" assert normalized_account("MRWK1" + ("A" * 40)) == "mrwk1" + ("a" * 40)