## Explanation
PR #8281 introduced a breaking change that removed
checkAndUpdateSingleNftOwnershipStatus from NftController entirely,
replacing it with a batch-only checkAndUpdateAllNftsOwnershipStatus
flow. However, the MetaMask extension calls
checkAndUpdateSingleNftOwnershipStatus directly after confirmed
transactions to check ownership of a single NFT — making the removal a
regression for extension consumers.
This PR restores checkAndUpdateSingleNftOwnershipStatus with an updated
signature that aligns with the new architecture introduced in #8281: the
batch boolean second argument is removed, the networkClientId is now the
second argument (required), and the method always writes the updated NFT
to state and returns it.
Additionally, a bug introduced in the restored implementation is fixed:
the original draft called this.update() directly followed by
this.#updateNestedNftState(), which also calls this.update() internally
— causing two stateChanged events to fire for a single logical state
change. The redundant this.update() call is removed so the method is
consistent with every other NFT-mutating method in the controller.
<!--
Thanks for your contribution! Take a moment to answer these questions so
that reviewers have the information they need to properly understand
your changes:
* What is the current state of things and why does it need to change?
* What is the solution your changes offer and how does it work?
* Are there any changes whose purpose might not obvious to those
unfamiliar with the domain?
* If your primary goal was to update one package but you found you had
to update another one along the way, why did you do so?
* If you had to upgrade a dependency, why did you do so?
-->
## References
Ticket: https://consensyssoftware.atlassian.net/browse/ASSETS-2959
Extension PR: MetaMask/metamask-extension#41689
<!--
Are there any issues that this pull request is tied to?
Are there other links that reviewers should consult to understand these
changes better?
Are there client or consumer pull requests to adopt any breaking
changes?
For example:
* Fixes #12345
* Related to #67890
-->
## Checklist
- [ ] I've updated the test suite for new or updated code as appropriate
- [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [ ] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Reintroduces and changes the signature/behavior of
`checkAndUpdateSingleNftOwnershipStatus`, which may break existing call
sites and affects how NFT ownership state is mutated.
>
> **Overview**
> Restores `NftController.checkAndUpdateSingleNftOwnershipStatus`
(removed in #8281) to fix consumers that perform per-NFT ownership
checks, and implements it to call `isNftOwner`, update
`isCurrentlyOwned`, persist the updated NFT back into `allNfts`, and
return the updated object.
>
> Updates documentation/tests: adds unit coverage for state updates and
`userAddress` overrides, and updates the changelog to document the
restored method plus the **breaking** signature change (removing the
`batch` boolean argument).
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
5593976. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Description
Upgrades
@metamask/assets-controllersfrom^103.1.0to^104.0.0, which introduces multicall v3 for NFT ownership refresh. This version removes thegetERC721OwnerOfandgetERC1155BalanceOfmessenger actions from theNftController(ownership checks are now handled internally via multicall), and drops theisMainnetboolean parameter fromcheckAndUpdateSingleNftOwnershipStatus.Changes:
package.json/yarn.lock: Bumped@metamask/assets-controllersto^104.0.0nft-controller-messenger.ts: RemovedAssetsContractControllerGetERC721OwnerOfActionandAssetsContractControllerGetERC1155BalanceOfActionfrom the allowed actions and messengerallowlist — these are no longer needed as the NftController handles ownership resolution via multicall v3 internallymetamask-controller.js: Removed thefalse(isMainnet) argument from twocheckAndUpdateSingleNftOwnershipStatuscall sites, matching the updated upstream API signatureui/store/actions.ts: Removed the samefalseargument from the background request wrapperChangelog
CHANGELOG entry: update assets controller (NFT refresh uses multicall v3)
Related issues
Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-2959
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Moderate risk due to a dependency upgrade that changes how NFT ownership is refreshed (now internal/multicall-based) and updates multiple call sites/messenger permissions accordingly.
Overview
Upgrades
@metamask/assets-controllersto^104.0.0and aligns the extension’s NFT ownership refresh calls with the updatedcheckAndUpdateSingleNftOwnershipStatussignature by removing theisMainnetboolean argument from background/UI and controller call sites.Tightens
NftControllermessenger permissions by dropping now-unusedAssetsContractControlleractions (e.g.getERC721OwnerOf,getERC1155BalanceOf), and updates LavaMoat policies to reflect the dependency graph change (removing@metamask/assets-controller>@metamask/assets-controllersentries and allowing@metamask/assets-controllersdirectly).Reviewed by Cursor Bugbot for commit 9c281a4. Bugbot is set up for automated code reviews on this repo. Configure here.