Skip to content
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

feat: integrate NFT api to display names for nfts within simulations for ERC721s #25692

Conversation

OGPoyraz
Copy link
Member

@OGPoyraz OGPoyraz commented Jul 5, 2024

Description

This PR integrates NFT api (/collections) to display name and image of collection for nfts within simulations only for ERC721 nfts.

Open in GitHub Codespaces

Related issues

Fixes: https://github.com/MetaMask/MetaMask-planning/issues/2507
Related to: MetaMask/core#4506

Manual testing steps

  1. Go to Opensea, try buying https://opensea.io/assets/ethereum/0xd4307e0acd12cf46fd6cf93bc264f5d5d1598792/41757 (you don't need to buy) - this is ERC721
  2. See collection name and image in simulations

Screenshots/Recordings

Before

Screenshot 2024-07-15 at 15 40 22

After

Screenshot 2024-07-15 at 15 39 42

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@OGPoyraz OGPoyraz added DO-NOT-MERGE Pull requests that should not be merged team-assets team-confirmations Push issues to confirmations team labels Jul 5, 2024
@OGPoyraz OGPoyraz requested a review from a team as a code owner July 5, 2024 09:51
@OGPoyraz OGPoyraz changed the title Integrate NFT api to display names for nfts within simulations feat: integrate NFT api to display names for nfts within simulations Jul 5, 2024
@@ -15,6 +16,7 @@ export type UseDisplayNameResponse = {
name: string | null;
hasPetname: boolean;
contractDisplayName?: string;
image?: string | null;
Copy link
Member Author

@OGPoyraz OGPoyraz Jul 5, 2024

Choose a reason for hiding this comment

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

I am not super happy to use image here since hook name is also useDisplayName. But I think this perfect place to implement this because image is going to be used in the same component where this is used.
Maybe we can think to rename it to useDisplayNameProps

.map(({ value }) => value),
chainId,
};
}, [JSON.stringify(requests), chainId]);
Copy link
Contributor

@pedronfigueiredo pedronfigueiredo Jul 5, 2024

Choose a reason for hiding this comment

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

JSON.stringify creates a new string every time, so this defeats the purpose of the dependency array. Doesn't simply requests work here, or is requests mutable?

Copy link
Member Author

@OGPoyraz OGPoyraz Jul 5, 2024

Choose a reason for hiding this comment

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

It does create a new string but string is not a reference type so it will actually check the equality before and after with given strings.

If we pass the requests here, we must guarantee that reference is not changing which we cannot do that since requests drops here as a new object evertyime.

In ui/hooks/useDisplayName.ts this is what passed to this hook.

 const nameRequests = requests.map(({ value, type }) => ({
    value,
    type,
  }));

In order to make this work with reference, we need in parent to guarantee that reference is not changing.

 const nameRequests = useMemo(
  () => requests.map(({ value, type }) => ({
    value,
    type,
  }));
  );

So this could be another solution. Which one looks better, wdyt?

@bergeron
Copy link
Contributor

bergeron commented Jul 5, 2024

The quota with our NFT provider is not infinite, and may not be enough to send requests on every transaction simulation. Before adding this feature we need to estimate the number of requests, and minimize requests where possible.

Like only fetching when we can't identify the asset through other means. Or first detecting it's an NFT and not an ERC20 through on chain means like ERC-165. Or checking if we have it in NFT state first. Or if we know the contract address and token id, we should just call tokenURI/URI on the contract. Even without the token id, you can usually call name() on the contract, and sometimes contractURI. Looking for cases that don't actually need off chain indexing.

The quota currently allows around 200 req/s across all NFT endpoints. But our rough calculations were already near that limit just with plans to turn on nft detection setting for everyone + support more chains. And that's already maximally lazy, only fetching when you click into an NFT tab.

ui/hooks/useDisplayName.ts Outdated Show resolved Hide resolved
ui/components/app/name/name.tsx Outdated Show resolved Hide resolved
ui/hooks/useDisplayName.ts Outdated Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.ts Outdated Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.ts Outdated Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.ts Outdated Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.ts Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.ts Outdated Show resolved Hide resolved
ui/hooks/useNftCollectionsMetadata.test.ts Outdated Show resolved Hide resolved
ui/hooks/useDisplayName.ts Outdated Show resolved Hide resolved
@metamaskbot
Copy link
Collaborator

Builds ready [96e71b5]
Page Load Metrics (256 ± 246 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint673651287737
domContentLoaded1097332311
load421905256512246
domInteractive1097332311
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 742 Bytes (0.02%)
  • ui: 2.51 KiB (0.04%)
  • common: 772 Bytes (0.01%)

Copy link

codecov bot commented Jul 10, 2024

Codecov Report

Attention: Patch coverage is 92.15686% with 4 lines in your changes missing coverage. Please review.

Project coverage is 69.75%. Comparing base (5ee57a6) to head (96e71b5).
Report is 14 commits behind head on develop.

Files Patch % Lines
ui/store/actions.ts 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #25692      +/-   ##
===========================================
- Coverage    69.77%   69.75%   -0.02%     
===========================================
  Files         1376     1378       +2     
  Lines        48403    48523     +120     
  Branches     13348    13370      +22     
===========================================
+ Hits         33773    33846      +73     
- Misses       14630    14677      +47     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

jpuri
jpuri previously approved these changes Jul 11, 2024
standard?: TokenStandard;

/** Token ID, if applicable. */
tokenId?: Hex;
Copy link
Member

@matthewwalsh0 matthewwalsh0 Jul 11, 2024

Choose a reason for hiding this comment

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

Petnames is fundamentally about storing and proposing names for obscure string values, in this case Ethereum addresses.

We don't want to couple other concepts to it, rather we should be able to extract the data we need from the contract address and the chain ID / variation alone.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

const nameEntry = nameEntries[index];
const firstPartyContractName = firstPartyContractNames[index];
const singleContractInfo = contractInfo[index];
const watchedNftName = watchedNftNames[value.toLowerCase()]?.name;
const nftCollectionProperties =
nftCollections[
`${value.toLowerCase()}:${hexToDecimal(tokenId as string)}`
Copy link
Member

Choose a reason for hiding this comment

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

This ultimately shouldn't be necessary as the API can source whether a contract address isSpam from the address alone, I've noted this in the core PR also.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

let nftCollectionName;
let nftCollectionImage;

if (!nftCollectionProperties?.isSpam) {
Copy link
Member

Choose a reason for hiding this comment

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

For safety, should we change this to isSpam === false?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

name,
image,
// We don't have a way to determine if a collection is spam from the tokenURI
isSpam: null,
Copy link
Member

Choose a reason for hiding this comment

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

If we can't determine if a contract isSpam, then the data is unusable for the petnames use case.

That's the central mechanism we need to determine if it's safe to display a default petname.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

ui/hooks/useNftCollectionsMetadata.ts Show resolved Hide resolved
@OGPoyraz OGPoyraz force-pushed the 2507-integrate-nft-api-to-display-names-for-nfts-within-simulations branch from 96e71b5 to cae7441 Compare July 15, 2024 11:38
OGPoyraz added a commit to MetaMask/core that referenced this pull request Jul 25, 2024
## Explanation

<!--
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?
-->

This PR exposes NFT `collections` api through NFT controller.

## References

<!--
Are there any issues that this pull request is tied to? Are there other
links that reviewers should consult to understand these changes better?

For example:

* Fixes #12345
* Related to #67890
-->

Related to: MetaMask/MetaMask-planning#2507
Extension PR using this PR's preview build:
MetaMask/metamask-extension#25692

## Changelog

<!--
If you're making any consumer-facing changes, list those changes here as
if you were updating a changelog, using the template below as a guide.

(CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or
FIXED. For security-related issues, follow the Security Advisory
process.)

Please take care to name the exact pieces of the API you've added or
changed (e.g. types, interfaces, functions, or methods).

If there are any breaking changes, make sure to offer a solution for
consumers to follow once they upgrade to the changes.

Finally, if you're only making changes to development scripts or tests,
you may replace the template below with "None".
-->

### `@metamask/assets-controllers`

- **ADDED**: Add `fetchNftCollectionMetadata` to `NFTController` api

## Checklist

- [X] I've updated the test suite for new or updated code as appropriate
- [X] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [X] I've highlighted breaking changes using the "BREAKING" category
above as appropriate

---------

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
AugmentedMode pushed a commit to MetaMask/core that referenced this pull request Jul 30, 2024
## Explanation

<!--
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?
-->

This PR exposes NFT `collections` api through NFT controller.

## References

<!--
Are there any issues that this pull request is tied to? Are there other
links that reviewers should consult to understand these changes better?

For example:

* Fixes #12345
* Related to #67890
-->

Related to: MetaMask/MetaMask-planning#2507
Extension PR using this PR's preview build:
MetaMask/metamask-extension#25692

## Changelog

<!--
If you're making any consumer-facing changes, list those changes here as
if you were updating a changelog, using the template below as a guide.

(CATEGORY is one of BREAKING, ADDED, CHANGED, DEPRECATED, REMOVED, or
FIXED. For security-related issues, follow the Security Advisory
process.)

Please take care to name the exact pieces of the API you've added or
changed (e.g. types, interfaces, functions, or methods).

If there are any breaking changes, make sure to offer a solution for
consumers to follow once they upgrade to the changes.

Finally, if you're only making changes to development scripts or tests,
you may replace the template below with "None".
-->

### `@metamask/assets-controllers`

- **ADDED**: Add `fetchNftCollectionMetadata` to `NFTController` api

## Checklist

- [X] I've updated the test suite for new or updated code as appropriate
- [X] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [X] I've highlighted breaking changes using the "BREAKING" category
above as appropriate

---------

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
@OGPoyraz OGPoyraz removed the DO-NOT-MERGE Pull requests that should not be merged label Sep 2, 2024
Copy link

sonarcloud bot commented Sep 2, 2024

@metamaskbot
Copy link
Collaborator

Builds ready [d8f2b41]
Page Load Metrics (1550 ± 106 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint36523751500352169
domContentLoaded133722511534218105
load134422901550221106
domInteractive13178393818
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 48 Bytes (0.00%)
  • ui: 1.8 KiB (0.02%)
  • common: 114 Bytes (0.00%)

matthewwalsh0
matthewwalsh0 previously approved these changes Sep 19, 2024
let nftCollectionImage;

if (nftCollectionProperties?.isSpam === false) {
nftCollectionName = nftCollectionProperties?.name;
Copy link
Member

Choose a reason for hiding this comment

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

Minor, but could we define an isSpam variable, then assign both of these using const and a ternary?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}, [requests]);

useEffect(() => {
const fetchCollections = async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Minor, but since this is a large function, we could define it outside of the component and useEffect (but still in this file) for greater readability, and even split it up further if needed.

Then we could also use useAsyncResult to handle cancellation on subsequent renders, and also detect loading state.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed this.

) {
const [collectionsMetadata, setCollectionsMetadata] =
useState<CollectionsData>({});
const chainId = useSelector(getCurrentChainId);
Copy link
Member

Choose a reason for hiding this comment

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

Minor, we ideally need to move away from global network so this could be a property or at least a fallback.

Copy link
Member Author

@OGPoyraz OGPoyraz Sep 23, 2024

Choose a reason for hiding this comment

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

Done, added providedChainId parameter to useNftCollectionsMetadata

Copy link

sonarcloud bot commented Sep 23, 2024

@metamaskbot
Copy link
Collaborator

Builds ready [ab8f93f]
Page Load Metrics (1870 ± 128 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint78928171793338162
domContentLoaded155428021848268129
load155828121870267128
domInteractive14269495426
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 48 Bytes (0.00%)
  • ui: 1.89 KiB (0.03%)
  • common: 114 Bytes (0.00%)

@OGPoyraz OGPoyraz merged commit 5e011f8 into develop Sep 23, 2024
77 checks passed
@OGPoyraz OGPoyraz deleted the 2507-integrate-nft-api-to-display-names-for-nfts-within-simulations branch September 23, 2024 10:08
@github-actions github-actions bot locked and limited conversation to collaborators Sep 23, 2024
@metamaskbot metamaskbot added release-12.6.0 Issue or pull request that will be included in release 12.6.0 release-12.5.0 Issue or pull request that will be included in release 12.5.0 and removed release-12.6.0 Issue or pull request that will be included in release 12.6.0 labels Sep 23, 2024
@metamaskbot
Copy link
Collaborator

Missing release label release-12.5.0 on PR. Adding release label release-12.5.0 on PR and removing other release labels(release-12.6.0), as PR was added to branch 12.5.0 when release was cut.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
release-12.5.0 Issue or pull request that will be included in release 12.5.0 team-assets team-confirmations Push issues to confirmations team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants