-
-
Notifications
You must be signed in to change notification settings - Fork 256
feat: add KeyringController:accountAdded event + use keyring events in AccountsController
#7328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
f128679 to
e8ec6c8
Compare
…n AccountsController
e8ec6c8 to
3fe4986
Compare
| const updated = await updateVault(); | ||
| const newKeyrings = this.state.keyrings; | ||
|
|
||
| const oldAccounts = toAccountsMap(oldKeyrings); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kept it as a Map even for oldAccounts for simplicity. We're not sending the KeyringObject alongside :accountRemoved, maybe we should?
But then, should the removed account be part of keyring.accounts still? What about other removed accounts? Should we remove them all before sending the events?
For now, I'd just keep the original signature for :accountRemoved.
| #handleOnKeyringAccountRemoved(address: string) { | ||
| const account = this.listMultichainAccounts().find( | ||
| ({ address: accountAddress }) => accountAddress === address, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Case-sensitive address comparison may fail to find accounts
The #handleOnKeyringAccountRemoved method uses strict equality (===) to compare addresses, while the existing getAccountByAddress method uses case-insensitive comparison with .toLowerCase() on both sides. Ethereum addresses can exist in different case formats (checksummed vs lowercase), so if there's any inconsistency in how addresses are stored versus how they're received from the KeyringController:accountRemoved event, the account lookup will fail silently and the account won't be removed from the state.
|
|
||
| // NOTE: Since we also track "updated" accounts with our patches, we could fire a new event | ||
| // like `accountUpdated` (we would still need to check if anything really changed on the account). | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Event ordering inconsistency when removing selected account
The #handleOnKeyringAccountRemoved method publishes accountRemoved event after calling #update, while the analogous #handleOnKeyringAccountAdded method publishes accountAdded inside the beforeAutoSelectAccount callback of #update. This asymmetry causes an event ordering bug: when the removed account is the currently selected one, #update triggers auto-selection and publishes selectedAccountChange before accountRemoved gets published. This reverses the expected event order. The PR includes a test ("fires :accountAdded before :selectedAccountChange") that validates the correct ordering for added accounts, but the same ordering guarantee isn't maintained for removed accounts. Moving messenger.publish('AccountsController:accountRemoved', ...) into a beforeAutoSelectAccount callback would fix this inconsistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true for the asymmetry, but IMO this makes more sense to call :selectedAccountChange before :accountRemoved:
- We change the account cause it will get removed
- We remove the account
This way, we don't have any "dangling" account being wrongly selected (from a consumer point of view)
| #handleOnKeyringAccountRemoved(address: string): void { | ||
| const account = this.listMultichainAccounts().find( | ||
| ({ address: accountAddress }) => accountAddress === address, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Address comparison uses strict equality instead of case-insensitive
The #handleOnKeyringAccountRemoved method uses strict equality (===) to compare addresses, but the codebase convention (seen in getAccountByAddress and utils.ts) uses case-insensitive comparison with .toLowerCase(). Ethereum addresses can be checksummed (mixed case) or lowercase, so if the KeyringController emits an address with different casing than what's stored in AccountsController, the account won't be found and won't be removed, leaving orphaned accounts in state.
| this.messenger.subscribe( | ||
| 'KeyringController:accountAdded', | ||
| (address, keyring) => this.#handleOnKeyringAccountAdded(address, keyring), | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Wallet lock removes accounts and loses custom names
The old #handleOnKeyringStateChange method had a guard if (!isUnlocked || keyrings.length === 0) { return; } that prevented account state changes when the wallet was locked. The new event-based handlers lack this guard. When setLocked() is called, it sets state.keyrings = [], which triggers accountRemoved events for all accounts via the update override. The AccountsController processes these events and removes all accounts. On unlock, accounts are re-added with auto-generated names via getNextAvailableAccountName, causing loss of any custom account names the user had set.
Explanation
Moving the add/remove account/address logic in the
KeyringController. This way, we don't need to run a complex logic in theAccountsControllereverytime a new account gets added.Part of this logic is now at the
KeyringController-level yes, but the logic itself seems simpler, and hopefully, is faster too!References
N/A
Checklist
Note
Adds
KeyringController:accountAdded/accountRemovedevents and updatesAccountsControllerto use these events instead ofKeyringController:stateChange, with corresponding tests and changelog updates.KeyringController:accountAddedandKeyringController:accountRemovedevents (via overriddenupdate,persistAllKeyrings, andwithKeyring).#updateVaultasync) and add comprehensive event tests.KeyringController:account{Added,Removed}; implement#handleOnKeyringAccountAdded/#handleOnKeyringAccountRemovedto update internal accounts and publishAccountsController:account{Added,Removed}.KeyringController:stateChange; update event wiring and tests (including order guarantees for:accountAddedbefore:selectedAccountChange).AccountsControllernow requiresKeyringController:account{Added,Removed}instead of:stateChange.Written by Cursor Bugbot for commit 5009b0a. This will update automatically on new commits. Configure here.