feat(web): let a wallet be unlinked from the dashboard - #333
Merged
blockchain-maxis merged 3 commits intoSep 2, 2026
Merged
Conversation
…oard Wallets could be listed but never removed, so a rotated deploy key or a wrongly-linked wallet kept contributing to a profile permanently. Add account.unlinkWallet, gated by the same session + same-origin guard as every other mutation, which refuses the primary handle wallet (that's a registry operation) and refuses a wallet bound to a different profile with the same "not found" message a nonexistent pubkey gets. The dashboard gets a two-step Unlink/Confirm control per non-primary wallet.
|
@ibochivincent-lang Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
@ibochivincent-lang is attempting to deploy a commit to the blockchainmaxis-8449's projects Team on Vercel. A member of the Team first needs to authorize it. |
✅ Deploy Preview for stellar-signet ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
blockchain-maxis
added a commit
to escaprt/signet
that referenced
this pull request
Sep 2, 2026
Two things needed fixing beyond the mechanical merge. account.test.ts conflicted on its import block (main gained blockchain-maxis#333's unlinkWallet/WalletStore); combined both. My first pass at that also swallowed the closing `});` of the last linkDeployWallet test where the two sides met, which typecheck caught as 'lib/server/account.test.ts(335,1): error TS1005'. Restored — that test (the create-time race resolving as a typed conflict) is now actually running rather than being silently absorbed into the next block. blockchain-maxis#329 made LinkedWallet.source a typed WalletSource after this PR was written, so the writer's plain `string` no longer assigned: lib/server/account.ts(183,5): error TS2322: Type 'string' is not assignable to type '"cli" | "curated" | "onchain"' Threaded WalletSource through linkDeployWallet's signature and LinkWalletStore's create/update data, and routed the row read back through the existing toWalletSource guard — same treatment getAccountWallets already gives the column, since it is an untyped String in the schema. 277/277 web tests pass (7 of them linkDeployWallet's); typecheck, lint and build clean.
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes #286
Summary
Wallets could be listed (
getAccountWallets) but never removed —apps/web/lib/server/trpc.tshadaccount.meandaccount.updateand nothing for wallets. A developer who rotated a deploy key or linked the wrong identity had no way to detach it; the rotated key kept contributing to the profile permanently.Changes
apps/web/lib/server/account.ts: addedunlinkWallet(address, pubkey, store?). It looks up the caller's own profile from their session wallet, then the target wallet, and:WalletStore(mirroring the indexer workers' injectable-store pattern) so the cross-profile case can be unit tested without a real database.apps/web/lib/server/trpc.ts: addedaccount.unlinkWallet, aprotectedProceduremutation (session + same-origin guarded, same asaccount.update), reusing the existingwalletInputvalidator for the pubkey.apps/web/app/(dashboard)/app/wallets/unlink-wallet-button.tsx(new): a client component with a two-step Unlink → Confirm/Cancel control (matching the dashboard's existing styling rather than a nativeconfirm()dialog), calling the new mutation and refreshing the page on success.apps/web/app/(dashboard)/app/wallets/page.tsx: renders the button next to the Explorer link for every non-primary wallet; the primary wallet gets no button, since unlinking it is refused server-side anyway.account.test.tsgained 6 new cases forunlinkWallet(no database, no caller profile, missing wallet, cross-profile refusal, primary refusal, successful delete);trpc.test.tsgained cases for no-session, malformed input, no-database, and cross-originFORBIDDEN.Verification
pnpm typecheck/pnpm lint/pnpm test— all pass across the workspace (one pre-existing, unrelated lint warning inapps/indexer/src/stellar.ts).apps/web/lib/server/account.test.ts— 13/13 passing, including the cross-profile refusal.apps/web/lib/server/trpc.test.ts— 217/217 passing (web suite total), including the newaccount.unlinkWalletsession/origin/input/database cases.