Repository: rotki/rotki

Issue:
NFT queries no longer work - switch to opensea v2
## Problem Definition

It seems that Opensea which was the sole provider of NFT data for us has dev-rugged us: https://twitter.com/LefterisJP/status/1751561509929369940

It was probably not noticed fast enough due to the fact we query cached DB nft data: https://github.com/rotki/rotki/blob/3d267d0591a342f53a5a1110f0e25f1cd10bb6ab/rotkehlchen/rotkehlchen.py#L947

So it only got noticed after a user reporting seeing an NFT they sold, still show in rotki even after a restart.

We generally need to not rely only on opensea for NFT data: https://github.com/rotki/rotki/issues/4911

## Task

For the purpose of this issue we should keep it simple and just switch to using opensea v2: https://docs.opensea.io/reference/list_nfts_by_account so that NFT detection works properly again


Discussion:

Resolving patch:
diff --git a/rotkehlchen/chain/ethereum/utils.py b/rotkehlchen/chain/ethereum/utils.py
--- a/rotkehlchen/chain/ethereum/utils.py
+++ b/rotkehlchen/chain/ethereum/utils.py
@@ -182,7 +182,10 @@ def _get_response_image(response: requests.adapters.Response) -> bytes:
     if response.status_code != HTTPStatus.OK:
         raise RemoteError(f'{response.url} failed with {response.status_code}')
     content_type = response.headers.get('Content-Type')
-    if content_type is None or content_type.startswith('image') is False:
+    if (
+        content_type is None or
+        content_type != 'null' and content_type.startswith('image') is False  # cloudflare removes the content type and sets it to null  # noqa: E501
+    ):
         raise RemoteError(f'{response.url} return non-image content type {content_type}')
 
     return response.content
diff --git a/rotkehlchen/externalapis/opensea.py b/rotkehlchen/externalapis/opensea.py
--- a/rotkehlchen/externalapis/opensea.py
+++ b/rotkehlchen/externalapis/opensea.py
@@ -2,7 +2,7 @@
 import logging
 import re
 from json.decoder import JSONDecodeError
-from typing import TYPE_CHECKING, Any, Literal, NamedTuple, overload
+from typing import TYPE_CHECKING, Any, Literal, NamedTuple
 
 import gevent
 import requests
@@ -34,7 +34,7 @@
     from rotkehlchen.db.dbhandler import DBHandler
 
 ASSETS_MAX_LIMIT = 50  # according to opensea docs
-CONTRACTS_MAX_LIMIT = 300  # according to opensea docs
+CONTRACTS_MAX_LIMIT = 200  # according to opensea docs
 
 
 ERC721_RE = re.compile(r'eip155:1/erc721:(.*?)/(.*)')
@@ -100,52 +100,32 @@ def __init__(
         self.session = requests.session()
         self.session.headers.update({
             'Content-Type': 'application/json',
-            # Their API seems to get limited by cloudflare after 1-2 requests ... unless
-            # the user agent is a browser. We lose nothing by doing this and may revert if they fix
-            # https://twitter.com/LefterisJP/status/1483017589869711364
-            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',  # noqa: E501
         })
         self.collections: dict[str, Collection] = {}
         self.backup_key: str | None = None
         self.ethereum_inquirer = ethereum_inquirer
         self.eth_asset = ethereum_inquirer.native_token
 
-    @overload
     def _query(
             self,
-            endpoint: Literal['assets', 'collectionstats', 'asset'],
+            endpoint: Literal['assets', 'collectionstats', 'asset', 'collection'],
             options: dict[str, Any] | None = None,
             timeout: tuple[int, int] | None = None,
     ) -> dict[str, Any]:
-        ...
-
-    @overload
-    def _query(
-            self,
-            endpoint: Literal['collections'],
-            options: dict[str, Any] | None = None,
-            timeout: tuple[int, int] | None = None,
-    ) -> list[dict[str, Any]]:
-        ...
-
-    def _query(
-            self,
-            endpoint: Literal['assets', 'collections', 'collectionstats', 'asset'],
-            options: dict[str, Any] | None = None,
-            timeout: tuple[int, int] | None = None,
-    ) -> list[dict[str, Any]] | dict[str, Any]:
         """May raise RemoteError"""
         api_key = self._get_api_key()
         if api_key is not None:
             self.session.headers.update({'X-API-KEY': api_key})
 
         if endpoint == 'collectionstats':
-            query_str = f'https://api.opensea.io/api/v1/collection/{options["name"]}/stats'  # type: ignore
+            query_str = f'https://api.opensea.io/api/v2/collections/{options["name"]}/stats'  # type: ignore
+        elif endpoint == 'collection':
+            query_str = f'https://api.opensea.io/api/v2/collections/{options["collection_slug"]}'  # type: ignore
         elif endpoint == 'asset':
-            query_str = f'https://api.opensea.io/api/v1/asset/{options["address"]}/{options["item_id"]}'  # type: ignore
+            query_str = f'https://api.opensea.io/api/v2/chain/ethereum/contract/{options["address"]}/nfts/{options["item_id"]}'  # type: ignore
             options = None
         else:
-            query_str = f'https://api.opensea.io/api/v1/{endpoint}'
+            query_str = f'https://api.opensea.io/api/v2/chain/ethereum/account/{options["address"]}/nfts'  # type: ignore
 
         backoff = 1
         backoff_limit = 33
@@ -207,8 +187,8 @@ def _deserialize_nft(
     ) -> 'NFT':
         """May raise:
 
-        - DeserializationError if the given dict can't be deserialized
-        - UnknownAsset if the given payment token isn't known
+        - DeserializationError if the given dict can't be deserialized or
+        we couldn't read the collection.
         """
         if not isinstance(entry, dict):
             raise DeserializationError(
@@ -246,38 +226,35 @@ def _deserialize_nft(
             collection = None
             # NFT might not be part of a collection
             if 'collection' in entry:
-                saved_entry = self.collections.get(entry['collection']['name'])
+                saved_entry = self.collections.get(entry['collection'])
                 if saved_entry is None:
                     # we haven't got this collection in memory. Query opensea for info
                     self.gather_account_collections(account=owner_address)
                     # try to get the info again
-                    saved_entry = self.collections.get(entry['collection']['name'])
+                    saved_entry = self.collections.get(entry['collection'])
 
                 if saved_entry:
                     collection = saved_entry
                     if saved_entry.floor_price is not None:
                         floor_price = saved_entry.floor_price
                 else:  # should not happen. That means collections endpoint doesnt return anything
-                    collection_data = entry['collection']
-                    collection = Collection(
-                        name=collection_data['name'],
-                        banner_image=collection_data['banner_image_url'],
-                        description=collection_data['description'],
-                        large_image=collection_data['large_image_url'],
+                    raise DeserializationError(
+                        f'Could not find collection {entry["collection"]} in opensea collections '
+                        f'endpoint',
                     )
 
             price_in_eth = max(last_price_in_eth, floor_price)
             price_in_usd = price_in_eth * eth_usd_price
-            token_id = entry['asset_contract']['address'] + '_' + entry['token_id']
-            if entry['asset_contract']['asset_contract_type'] == 'semi-fungible':
+            token_id = entry['contract'] + '_' + entry['identifier']
+            if entry['token_standard'] == 'erc1155':
                 token_id += f'_{owner_address!s}'
             return NFT(  # can raise KeyError due to arg init
                 token_identifier=NFT_DIRECTIVE + token_id,
-                background_color=entry['background_color'],
+                background_color=None,
                 image_url=entry['image_url'],
                 name=entry['name'],
-                external_link=entry['external_link'],
-                permalink=entry['permalink'],
+                external_link=entry['metadata_url'],
+                permalink=entry['opensea_url'],
                 price_eth=price_in_eth,
                 price_usd=price_in_usd,
                 collection=collection,
@@ -287,59 +264,63 @@ def _deserialize_nft(
 
     def gather_account_collections(self, account: ChecksumEvmAddress) -> None:
         """Gathers account collection information and keeps them in memory"""
-        offset = 0
-        options = {'offset': offset, 'limit': CONTRACTS_MAX_LIMIT, 'asset_owner': account}
+        options = {'next': None, 'limit': ASSETS_MAX_LIMIT, 'address': account}
 
         raw_result: list[dict[str, Any]] = []
         while True:
-            result = self._query(endpoint='collections', options=options)
-            raw_result.extend(result)
-            if len(result) != CONTRACTS_MAX_LIMIT:
+            result = self._query(endpoint='assets', options=options)
+            raw_result.extend(result['nfts'])
+            if len(result['nfts']) != ASSETS_MAX_LIMIT:
                 break
 
             # else continue by paginating
-            offset += CONTRACTS_MAX_LIMIT
-            options['offset'] = offset
+            options['next'] = result['next']
 
         for entry in raw_result:
-            if len(entry['primary_asset_contracts']) == 0:
+            options = {'collection_slug': entry['collection']}
+            collection = self._query(endpoint='collection', options=options)
+            if len(collection['contracts']) == 0:
                 continue  # skip if no contract (opensea makes everything a collection of 1)
-            name = entry['name']
-            if name in self.collections:
+            slug = collection['collection']
+            if slug in self.collections:
                 continue  # do not requery already queried collection
 
             # To get the floor price we need to query a different endpoint since opensea are idiots
             # https://github.com/rotki/rotki/issues/3676
-            stats_result = self._query(endpoint='collectionstats', options={'name': entry['slug']})
+            stats_result = self._query(
+                endpoint='collectionstats', options={'name': entry['collection']},
+            )
+            log.debug(stats_result)
+            _floor_price = (
+                stats_result['total']['floor_price'] or stats_result['total']['average_price']
+            )
             floor_price = deserialize_optional_to_optional_fval(
-                value=stats_result['stats']['floor_price'],
+                value=_floor_price,
                 name='floor price',
                 location='opensea',
             )
-            self.collections[name] = Collection(
-                name=name,
-                banner_image=entry['banner_image_url'],
-                description=entry['description'],
-                large_image=entry['large_image_url'],
+            self.collections[slug] = Collection(
+                name=collection['name'],
+                banner_image=collection['banner_image_url'],
+                description=collection['description'],
+                large_image=collection['image_url'],
                 floor_price=floor_price,
             )
 
     def get_account_nfts(self, account: ChecksumEvmAddress) -> list[NFT]:
         """May raise RemoteError"""
-        offset = 0
-        options = {'order_direction': 'desc', 'offset': offset, 'limit': ASSETS_MAX_LIMIT, 'owner': account}  # noqa: E501
+        options = {'next': None, 'limit': ASSETS_MAX_LIMIT, 'address': account}
         eth_usd_price = Inquirer.find_usd_price(A_ETH)
 
         raw_result = []
         while True:
             result = self._query(endpoint='assets', options=options)
-            raw_result.extend(result['assets'])
-            if len(result['assets']) != ASSETS_MAX_LIMIT:
+            raw_result.extend(result['nfts'])
+            if len(result['nfts']) != ASSETS_MAX_LIMIT:
                 break
 
             # else continue by paginating
-            offset += ASSETS_MAX_LIMIT
-            options['offset'] = offset
+            options['next'] = result['next']
 
         nfts = []
         for entry in raw_result:
@@ -381,7 +362,7 @@ def get_nft_image(
                     'item_id': item_id,
                 },
             )
-            return result['image_url']
+            return result['nft']['image_url']
         except (RemoteError, KeyError) as e:
             msg = str(e)
             if isinstance(e, KeyError):


Test patch:
diff --git a/rotkehlchen/tests/api/test_nfts.py b/rotkehlchen/tests/api/test_nfts.py
--- a/rotkehlchen/tests/api/test_nfts.py
+++ b/rotkehlchen/tests/api/test_nfts.py
@@ -31,20 +31,22 @@
 TEST_ACC1 = '0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12'  # lefteris.eth
 TEST_ACC2 = '0x3Ba6eB0e4327B96aDe6D4f3b578724208a590CEF'
 TEST_ACC3 = '0xC21A5ee89D306353e065a6dd5779470DE395DBaC'
-TEST_ACC4 = '0xc37b40ABdB939635068d3c5f13E7faF686F03B65'  # yabir.eth
+TEST_ACC4 = '0xc37b40ABdB939635068d3c5f13E7faF686F03B65'  # yabir.eth, gashawk nft
 TEST_ACC5 = '0x4bBa290826C253BD854121346c370a9886d1bC26'  # nebolax.eth
-TEST_ACC6 = '0x3e649c5Eac6BBEE8a4F2A2945b50d8e582faB3bf'  # contains a uniswap-v3 nft
+TEST_ACC6 = '0x3e649c5Eac6BBEE8a4F2A2945b50d8e582faB3bf'  # contains uniswap-v3 nft
 NFT_ID_FOR_TEST_ACC4 = '_nft_0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85_26612040215479394739615825115912800930061094786769410446114278812336794170041'  # noqa: E501
+NFT_ID_FOR_TEST_ACC4_2 = '_nft_0xfd9d8036f899ed5a9fd8cac7968e5f24d3db2a64_1_0xc37b40ABdB939635068d3c5f13E7faF686F03B65'  # noqa: E501
 NFT_ID_FOR_TEST_ACC5 = '_nft_0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85_73552724610198397480670284492690114609730214421511097849210414928326607694469'  # noqa: E501
 NFT_ID_FOR_TEST_ACC6_1 = '_nft_0xc36442b4a4522e871399cd717abdd847ab11fe88_360680'
 NFT_ID_FOR_TEST_ACC6_2 = '_nft_0xc36442b4a4522e871399cd717abdd847ab11fe88_360762'
+NFT_ID_FOR_TEST_ACC6_3 = '_nft_0xc36442b4a4522e871399cd717abdd847ab11fe88_530086'
 
 TEST_NFT_NEBOLAX_ETH = NFT(
     token_identifier='_nft_0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85_73552724610198397480670284492690114609730214421511097849210414928326607694469',
     background_color=None,
     image_url='https://openseauserdata.com/files/8fd18b22e4c81aff3998956e7a712d93.svg',
     name='nebolax.eth',
-    external_link='https://app.ens.domains/name/nebolax.eth',
+    external_link='https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/73552724610198397480670284492690114609730214421511097849210414928326607694469',
     permalink='https://opensea.io/assets/ethereum/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/73552724610198397480670284492690114609730214421511097849210414928326607694469',
     price_eth=FVal(0.0012),
     price_usd=FVal(1.2379458),
@@ -62,7 +64,7 @@
     background_color=None,
     image_url='https://openseauserdata.com/files/3f7c0c7d1ba51e61fe05ef53875f9f7e.svg',
     name='yabir.eth',
-    external_link='https://app.ens.domains/name/yabir.eth',
+    external_link='https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/26612040215479394739615825115912800930061094786769410446114278812336794170041',
     permalink='https://opensea.io/assets/ethereum/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/26612040215479394739615825115912800930061094786769410446114278812336794170041',
     price_eth=FVal(0.00098),
     price_usd=FVal(1.2379458),
@@ -115,7 +117,7 @@ def test_nft_query(rotkehlchen_api_server, start_with_valid_premium):
     for entry in nfts:
         if entry['token_identifier'] == '_nft_0xc3f733ca98e0dad0386979eb96fb1722a1a05e69_129':
             assert entry['name'] == 'MoonCat #129: 0x0082206dcb'
-            assert entry['external_link'] == 'https://chainstation.mooncatrescue.com/mooncats/129'
+            assert entry['external_link'] == 'https://api.mooncat.community/traits/129'
             assert 'image_url' in entry
             assert FVal(entry['price_eth']) > ZERO
             assert FVal(entry['price_usd']) > ZERO
@@ -216,13 +218,18 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
         'nftsbalanceresource',
     ), json={'async_query': False, 'ignore_cache': True})
     result_ignored_cache = assert_proper_response_with_result(response)
-    assert result_ignored_cache['entries_found'] == 4
+    assert result_ignored_cache['entries_found'] == 6
     for nft_balance in result_ignored_cache['entries']:
         if nft_balance['id'] == NFT_ID_FOR_TEST_ACC4:
             assert nft_balance['name'] == 'yabir.eth'
             assert nft_balance['collection_name'] == 'ENS: Ethereum Name Service'
             assert nft_balance['is_lp'] is False
             assert FVal(nft_balance['usd_price']) > ZERO
+        elif nft_balance['id'] == NFT_ID_FOR_TEST_ACC4_2:
+            assert nft_balance['name'] == 'GasHawk Nest NFT'
+            assert nft_balance['collection_name'] == 'GasHawk NFTs'
+            assert nft_balance['is_lp'] is False
+            assert FVal(nft_balance['usd_price']) >= ZERO
         elif nft_balance['id'] == NFT_ID_FOR_TEST_ACC5:
             assert nft_balance['name'] == 'nebolax.eth'
             assert nft_balance['collection_name'] == 'ENS: Ethereum Name Service'
@@ -236,6 +243,11 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
         elif nft_balance['id'] == NFT_ID_FOR_TEST_ACC6_2:
             assert nft_balance['name'] == 'Uniswap - 0.3% - SHIB/WETH - 79010000<>166260000'
             assert nft_balance['collection_name'] == 'Uniswap V3 Positions'
+            assert nft_balance['is_lp'] is False
+            assert FVal(nft_balance['usd_price']) > ZERO
+        elif nft_balance['id'] == NFT_ID_FOR_TEST_ACC6_3:
+            assert nft_balance['name'] == 'Uniswap - 1% - SHIB/WETH - 201050000<>546470000'
+            assert nft_balance['collection_name'] == 'Uniswap V3 Positions'
             assert nft_balance['is_lp'] is True
             assert FVal(nft_balance['usd_price']) > ZERO
         else:
@@ -269,8 +281,8 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
         'offset': 2,
     })
     result = assert_proper_response_with_result(response)
-    assert result['entries_found'] == 4
-    assert result['entries_total'] == 4
+    assert result['entries_found'] == 6
+    assert result['entries_total'] == 6
 
     # ignore an nft
     response = requests.put(
@@ -278,7 +290,7 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
             rotkehlchen_api_server,
             'ignoredassetsresource',
         ),
-        json={'assets': [NFT_ID_FOR_TEST_ACC4, NFT_ID_FOR_TEST_ACC6_2]},
+        json={'assets': [NFT_ID_FOR_TEST_ACC4, NFT_ID_FOR_TEST_ACC6_2, NFT_ID_FOR_TEST_ACC4_2]},
     )
 
     # Make sure that ignoring cache doesn't remove any NFTs from the app
@@ -295,13 +307,15 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
     assert result_with_cache == result_ignored_cache
 
     # Check that filtering ignored nfts works
-    assert result_with_cache['entries_found'] == 4
-    assert result_with_cache['entries_total'] == 4
+    assert result_with_cache['entries_found'] == 6
+    assert result_with_cache['entries_total'] == 6
     assert {entry['id'] for entry in result_with_cache['entries']} == {
         NFT_ID_FOR_TEST_ACC4,
+        NFT_ID_FOR_TEST_ACC4_2,
         NFT_ID_FOR_TEST_ACC5,
         NFT_ID_FOR_TEST_ACC6_1,
         NFT_ID_FOR_TEST_ACC6_2,
+        NFT_ID_FOR_TEST_ACC6_3,
     }
 
     # Make sure that after invalidating NFTs cache ignored NFTs remain ignored
@@ -318,19 +332,20 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
     assert result_with_cache == result_ignored_cache
 
     # Check that the response is correct
-    assert result_with_cache['entries_found'] == 2
-    assert result_with_cache['entries_total'] == 4
-    expected_nfts_without_ignored = {NFT_ID_FOR_TEST_ACC5, NFT_ID_FOR_TEST_ACC6_1}
+    assert result_with_cache['entries_found'] == 3
+    assert result_with_cache['entries_total'] == 6
+    expected_nfts_without_ignored = {NFT_ID_FOR_TEST_ACC5, NFT_ID_FOR_TEST_ACC6_1, NFT_ID_FOR_TEST_ACC6_3}  # noqa: E501
     assert {entry['id'] for entry in result_with_cache['entries']} == expected_nfts_without_ignored
     response = requests.get(api_url_for(
         rotkehlchen_api_server,
         'nftsbalanceresource',
     ), json={'async_query': False, 'ignore_cache': False, 'ignored_assets_handling': 'show only'})
     result = assert_proper_response_with_result(response)
-    assert result['entries_found'] == 2
-    assert result['entries_total'] == 4
+    assert result['entries_found'] == 3
+    assert result['entries_total'] == 6
     assert {entry['id'] for entry in result['entries']} == {
         NFT_ID_FOR_TEST_ACC4,
+        NFT_ID_FOR_TEST_ACC4_2,
         NFT_ID_FOR_TEST_ACC6_2,
     }
 
@@ -363,7 +378,7 @@ def test_nft_balances_and_prices(rotkehlchen_api_server):
     assert result == {
         'name': 'yabir.eth',
         'asset_type': 'nft',
-        'image_url': 'https://openseauserdata.com/files/238f73fa1bbea518eec64f9c9d5ed7fe.svg',
+        'image_url': 'https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/0x3ad5e1887ef8024efa6d5070ccfda4868783a5343e1089e47fda9b4f7ce4f2b9/image',
         'collection_name': 'ENS: Ethereum Name Service',
     }
 
@@ -419,7 +434,7 @@ def mock_get_all_nft_data(addresses, **kwargs):  # pylint: disable=unused-argume
             background_color=None,
             image_url='https://openseauserdata.com/files/3f7c0c7d1ba51e61fe05ef53875f9f7e.svg',
             name='nebolax.eth',
-            external_link='https://app.ens.domains/name/nebolax.eth',
+            external_link='https://metadata.ens.domains/mainnet/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/73552724610198397480670284492690114609730214421511097849210414928326607694469',
             permalink='https://opensea.io/assets/ethereum/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/73552724610198397480670284492690114609730214421511097849210414928326607694469',
             price_eth=FVal(0.5),
             price_usd=FVal(1.2379458),
@@ -559,126 +574,49 @@ def test_nft_no_price(rotkehlchen_api_server):
     nft_module = rotki.chains_aggregator.get_module('nfts')
 
     def mock_session_get(url, params, timeout):  # pylint: disable=unused-argument
-        if 'assets' in url:
+        if '/nfts' in url:
             response = """
-        {"assets":[{
-        "animation_original_url": "https://resources.smarttokenlabs.com/devcon6/Comp_ETH.mp4",
-        "animation_url": "https://openseauserdata.com/files/8ce39e632261c0d61bb122c314f49ef7.mp4",
-        "asset_contract": {
-            "address": "0x7522dc5a357891b4daec194e285551ea5ea66d09",
-            "asset_contract_type": "non-fungible",
-            "buyer_fee_basis_points": 0,
-            "created_date": "2022-10-14T22:45:02.644744",
-            "default_to_fiat": false,
-            "description": "bla bla",
-            "dev_buyer_fee_basis_points": 0,
-            "dev_seller_fee_basis_points": 0,
-            "external_link": "https://devcon-vi.attest.tickets/",
-            "image_url": "https://i.seadn.io/gae/ED1IC2aJj9RlHheixOlc3CH_oV4I1egLchaDAkqccx5EEC-foJcG0CUZNt9QhkIyLKOS1ZDqvkhPQcxSyvLDCWeytSv-Flv8acRM8g?w=500&auto=format",
-            "name": "Unidentified contract",
-            "nft_version": null,
-            "only_proxied_transfers": false,
-            "opensea_buyer_fee_basis_points": 0,
-            "opensea_seller_fee_basis_points": 250,
-            "opensea_version": null,
-            "owner": null,
-            "payout_address": null,
-            "schema_name": "ERC721",
-            "seller_fee_basis_points": 250,
-            "symbol": "",
-            "total_supply": null
-        },
-        "background_color": null,
-        "collection": {
-            "banner_image_url": null,
-            "chat_url": null,
-            "created_date": "2022-10-15T01:30:47.057694+00:00",
-            "default_to_fiat": false,
-            "description": "bla bla",
-            "dev_buyer_fee_basis_points": "0",
-            "dev_seller_fee_basis_points": "0",
-            "discord_url": null,
-            "display_data": {"card_display_style": "contain", "images": []},
-            "external_url": "https://devcon-vi.attest.tickets/",
-            "featured": false,
-            "featured_image_url": null,
-            "fees": {"opensea_fees": {"0x0000a26b00c1f0df003000390027140000faa719": 250}, "seller_fees": {}},
-            "hidden": false,
-            "image_url": "https://i.seadn.io/gae/ED1IC2aJj9RlHheixOlc3CH_oV4I1egLchaDAkqccx5EEC-foJcG0CUZNt9QhkIyLKOS1ZDqvkhPQcxSyvLDCWeytSv-Flv8acRM8g?w=500&auto=format",
-            "instagram_username": null,
-            "is_nsfw": false,
-            "is_rarity_enabled": false,
-            "is_subject_to_whitelist": false,
-            "large_image_url": "https://i.seadn.io/gae/ED1IC2aJj9RlHheixOlc3CH_oV4I1egLchaDAkqccx5EEC-foJcG0CUZNt9QhkIyLKOS1ZDqvkhPQcxSyvLDCWeytSv-Flv8acRM8g?w=500&auto=format",
-            "medium_username": null,
-            "name": "Devcon VI Souvenir V4",
-            "only_proxied_transfers": false,
-            "opensea_buyer_fee_basis_points": "0",
-            "opensea_seller_fee_basis_points": "250",
-            "payout_address": null,
-            "require_email": false,
-            "safelist_request_status": "not_requested",
-            "short_description": null,
-            "slug": "devcon-vi-souvenir-v4",
-            "telegram_url": null,
-            "twitter_username": null,
-            "wiki_url": null
-        },
-        "creator": {
-            "address": "0xff3efd475907f5c6dc173fe42c2dd3a58ef740bf",
-            "config": "",
-            "profile_img_url": "https://storage.googleapis.com/opensea-static/opensea-profile/10.png",
-            "user": {"username": null}
-        },
-        "decimals": null,
-        "description": "bla bla",
-        "external_link": null,
-        "id": 724471261,
-        "image_original_url": "https://resources.smarttokenlabs.com/devcon6/ETH.webp",
-        "image_preview_url": "https://i.seadn.io/gae/-N3ctyPYwIF0s-pShI-Zcg96KJr7dYG05KyFtI25WG0yIZeOpBxjAIIUBiBmHcbviAFkY57Xfo0-MRaonHHx4a53LS-q2yOoEwzF?w=500&auto=format",
-        "image_thumbnail_url": "https://i.seadn.io/gae/-N3ctyPYwIF0s-pShI-Zcg96KJr7dYG05KyFtI25WG0yIZeOpBxjAIIUBiBmHcbviAFkY57Xfo0-MRaonHHx4a53LS-q2yOoEwzF?w=500&auto=format",
-        "image_url": "https://i.seadn.io/gae/-N3ctyPYwIF0s-pShI-Zcg96KJr7dYG05KyFtI25WG0yIZeOpBxjAIIUBiBmHcbviAFkY57Xfo0-MRaonHHx4a53LS-q2yOoEwzF?w=500&auto=format",
-        "is_nsfw": false,
-        "is_presale": false,
-        "last_sale": null,
-        "listing_date": null,
-        "name": "Devcon VI Souvenir",
-        "num_sales": 0,
-        "owner": {
-            "address": "0x0000000000000000000000000000000000000000",
-            "config": "",
-            "profile_img_url": "https://storage.googleapis.com/opensea-static/opensea-profile/1.png",
-            "user": {"username": "NullAddress"}
-        },
-        "permalink": "https://opensea.io/assets/ethereum/0x7522dc5a357891b4daec194e285551ea5ea66d09/336510496872176433120578",
-        "rarity_data": null,
-        "seaport_sell_orders": null,
-        "supports_wyvern": true,
-        "token_id": "336510496872176433120578",
-        "token_metadata": "https://resources.smarttokenlabs.com/1/0x7522dc5a357891b4daec194e285551ea5ea66d09/336510496872176433120578",
-        "top_bid": null,
-        "traits": [],
-        "transfer_fee": null,
-        "transfer_fee_payment_token": null}]}
-        """  # noqa: E501
-        elif 'collections' in url:
-            response = """[{
-                "primary_asset_contracts": [1, 2, 3],
-                "name": "Devcon VI Souvenir V4",
-                "slug": "devconsouvenir",
-                "banner_image_url": "url",
-                "large_image_url": "url",
-                "description": "bla bla"
-            }]"""
+            {
+                "nfts": [
+                    {
+                    "identifier": "336510496872176433120578",
+                    "collection": "devcon-vi-souvenir-v4",
+                    "contract": "0x7522dc5a357891b4daec194e285551ea5ea66d09",
+                    "token_standard": "erc721",
+                    "name": "Devcon VI Souvenir",
+                    "description": "Devcon is the Ethereum conference for developers, researchers, thinkers, and makers.An intensive introduction for new Ethereum explorers, a global family reunion for those already a part of our ecosystem, and a source of energy and creativity for all. This ticket NFT serves as a souvenir for Devcon Bogota. With a symbolic focus on LATAM, and the beautiful destination of Bogota Colombia. The bird of paradise is known as the ultimate symbol of paradise and freedom. Due to its tropical nature, this flower symbolizes freedom and joy.",
+                    "image_url": "https://resources.smarttokenlabs.com/devcon6/ETH.webp",
+                    "metadata_url": "https://resources.smarttokenlabs.com/1/0x7522dc5a357891b4daec194e285551ea5ea66d09/336510496872176433120578",
+                    "opensea_url": "https://opensea.io/assets/ethereum/0x7522dc5a357891b4daec194e285551ea5ea66d09/336510496872176433120578",
+                    "updated_at": "2022-10-17T06:36:48.750908",
+                    "is_disabled": false,
+                    "is_nsfw": false
+                    }
+                ]
+            }"""  # noqa: E501
         elif '/stats' in url:
             response = """{
-                "stats": {
-                    "floor_price": null
+                "total": {
+                    "volume": 0,
+                    "sales": 0,
+                    "average_price": 0,
+                    "num_owners": 435,
+                    "market_cap": 0,
+                    "floor_price": 0,
+                    "floor_price_symbol": ""
                 }
             }"""
+        elif '/collections/' in url:
+            response = """{
+                "contracts": [{"address":1,"chain":"ethereum"}, {"address":2,"chain":"ethereum"}],
+                "name": "Devcon VI Souvenir V4",
+                "collection": "devcon-vi-souvenir-v4",
+                "banner_image_url": "url",
+                "image_url": "url",
+                "description": "bla bla"
+            }"""
         else:
             raise AssertionError(f'Unexpected url {url} queried in test')
-
         return MockResponse(200, response)
 
     patched_opensea = patch.object(nft_module.opensea.session, 'get', wraps=mock_session_get)
@@ -691,16 +629,19 @@ def mock_session_get(url, params, timeout):  # pylint: disable=unused-argument
         )
     result = assert_proper_response_with_result(response)
     assert result == {
-        'entries': [{
-            'collection_name': 'Devcon VI Souvenir V4',
-            'id': '_nft_0x7522dc5a357891b4daec194e285551ea5ea66d09_336510496872176433120578',
-            'image_url': 'https://i.seadn.io/gae/-N3ctyPYwIF0s-pShI-Zcg96KJr7dYG05KyFtI25WG0yIZeOpBxjAIIUBiBmHcbviAFkY57Xfo0-MRaonHHx4a53LS-q2yOoEwzF?w=500&auto=format',
-            'is_lp': False,
-            'manually_input': False,
-            'name': 'Devcon VI Souvenir',
-            'price_asset': 'ETH',
-            'price_in_asset': '0',
-            'usd_price': '0.0'}],
+        'entries': [
+            {
+                'id': '_nft_0x7522dc5a357891b4daec194e285551ea5ea66d09_336510496872176433120578',
+                'name': 'Devcon VI Souvenir',
+                'price_in_asset': '0',
+                'price_asset': 'ETH',
+                'manually_input': False,
+                'is_lp': False,
+                'image_url': 'https://resources.smarttokenlabs.com/devcon6/ETH.webp',
+                'usd_price': '0.0',
+                'collection_name': 'Devcon VI Souvenir V4',
+            },
+        ],
         'entries_found': 1,
         'entries_total': 1,
         'total_usd_value': '0.0',
diff --git a/rotkehlchen/tests/data/example_ens_avatar.png b/rotkehlchen/tests/data/example_ens_avatar.png
Binary files a/rotkehlchen/tests/data/example_ens_avatar.png and b/rotkehlchen/tests/data/example_ens_avatar.png differ
diff --git a/rotkehlchen/tests/unit/test_evm_names.py b/rotkehlchen/tests/unit/test_evm_names.py
--- a/rotkehlchen/tests/unit/test_evm_names.py
+++ b/rotkehlchen/tests/unit/test_evm_names.py
@@ -203,6 +203,6 @@ def test_download_ens_avatar(ethereum_inquirer, opensea):
             tempdir / 'tewshi.eth.png',
             tempdir / 'nebolax.eth.png',
         }
-        downloaded_hash = file_md5(tempdir / 'nebolax.eth.png')
+        downloaded_hash = file_md5(tempdir / 'mafeoza.eth.png')
         expected_hash = file_md5(Path(__file__).parent.parent / 'data' / 'example_ens_avatar.png')
         assert downloaded_hash == expected_hash, 'Downloaded avatar should match the expected avatar'  # noqa: E501