blockapi 0.31.2__tar.gz → 0.32.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {blockapi-0.31.2 → blockapi-0.32.0}/PKG-INFO +1 -1
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/nft/test_opensea.py +45 -1
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/nft/opensea.py +64 -19
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/coin_mapping.py +2 -1
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/coins.py +8 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/models.py +14 -3
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/PKG-INFO +1 -1
- {blockapi-0.31.2 → blockapi-0.32.0}/setup.py +1 -1
- {blockapi-0.31.2 → blockapi-0.32.0}/LICENSE.md +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/README.md +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/alethio.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/amberdata.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/binance.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchaininfo.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchainos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchair.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockcypher.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockonomics.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockscout.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/btc.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cardanoexplorer.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/chainso.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cosmos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cryptoid.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/dcrdata.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/digonchain.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/eospark.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/etherscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/ethplorer.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/greymass.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/insight.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/kyber.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/neoscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/ontology.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/solana.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/stellar.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/subscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/terra_money.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/trezor.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tronscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tzscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tzstats.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/zchain.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/zensystem.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/services.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_amberdata.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_balances.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_binance.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockchainos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockchair.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockscout.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_btc.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_cosmos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_dcrdata.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_digonchain.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_insight.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_kyber.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_ontio.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_solana.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_subscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_terra_money.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tronscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tzscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tzstats.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_blockapi.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_num.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_random_user_agent.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/conftest.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/nft/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/perpetual/test_perpetual.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchainos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_ethplorer.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_multisources.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_solana.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_blockchain_api.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_data.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_enumerate_classes.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_generic.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_models.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test_data.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/address.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/datetime.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/ethereum.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/num.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/user_agent.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/blockchainos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/blockchair.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/cosmos.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/astar.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/avalanche.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/axie.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/base.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/ethereum.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/fantom.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/heco.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/iotex.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/klaytn.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/palm.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/polygon.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/rsk.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/debank.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/debank_maps.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/ethplorer.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/nft/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/optimistic_etherscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/perpetual.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/solana.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/subscan.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/__init__.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/synthetix.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/terra.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/trezor.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/web3_utils.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/base.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/blockchain_mapping.py +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/SOURCES.txt +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/dependency_links.txt +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/requires.txt +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/top_level.txt +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/pyproject.toml +0 -0
- {blockapi-0.31.2 → blockapi-0.32.0}/setup.cfg +0 -0
|
@@ -54,6 +54,11 @@ def listings_response():
|
|
|
54
54
|
return read_file('data/opensea/listings.json')
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
@pytest.fixture
|
|
58
|
+
def locked_listings_response():
|
|
59
|
+
return read_file('data/opensea/listings-locked.json')
|
|
60
|
+
|
|
61
|
+
|
|
57
62
|
@pytest.fixture
|
|
58
63
|
def collection_stats_response():
|
|
59
64
|
return read_file('data/opensea/collection-stats.json')
|
|
@@ -134,6 +139,9 @@ def test_parse_nfts(requests_mock, api, nfts_response, nfts_next_response):
|
|
|
134
139
|
|
|
135
140
|
nfts = api.fetch_nfts(nfts_test_address)
|
|
136
141
|
parsed = api.parse_nfts(nfts)
|
|
142
|
+
|
|
143
|
+
assert not parsed.cursor
|
|
144
|
+
|
|
137
145
|
assert len(parsed.data) == 2
|
|
138
146
|
data = parsed.data[1]
|
|
139
147
|
assert data.ident == '550885'
|
|
@@ -162,6 +170,25 @@ def test_parse_nfts(requests_mock, api, nfts_response, nfts_next_response):
|
|
|
162
170
|
assert data.amount == 1
|
|
163
171
|
|
|
164
172
|
|
|
173
|
+
def test_fetch_nfts_with_limit(
|
|
174
|
+
requests_mock, api_w_limit, nfts_response, nfts_next_response
|
|
175
|
+
):
|
|
176
|
+
requests_mock.get(
|
|
177
|
+
f'https://api.opensea.io/api/v2/chain/ethereum/account/{nfts_test_address}/nfts',
|
|
178
|
+
text=nfts_response,
|
|
179
|
+
)
|
|
180
|
+
requests_mock.get(
|
|
181
|
+
f'https://api.opensea.io/api/v2/chain/ethereum/account/{nfts_test_address}/nfts?next=LXBrPTE0MDMyMTEyOTU=',
|
|
182
|
+
text=nfts_next_response,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
nfts = api_w_limit.fetch_nfts(nfts_test_address)
|
|
186
|
+
parsed = api_w_limit.parse_nfts(nfts)
|
|
187
|
+
|
|
188
|
+
assert nfts.cursor == 'LXBrPTE0MDMyMTEyOTU='
|
|
189
|
+
assert parsed.cursor == 'LXBrPTE0MDMyMTEyOTU='
|
|
190
|
+
|
|
191
|
+
|
|
165
192
|
def test_parse_offers(requests_mock, api, offers_response, offers_next_response):
|
|
166
193
|
requests_mock.get(
|
|
167
194
|
f'https://api.opensea.io/api/v2/offers/collection/ever-fragments-of-civitas/all',
|
|
@@ -261,6 +288,19 @@ def test_parse_listings(requests_mock, api, listings_response):
|
|
|
261
288
|
assert data.pay_amount == Decimal('0.02')
|
|
262
289
|
|
|
263
290
|
|
|
291
|
+
def test_ignore_locked_listings(requests_mock, api, locked_listings_response):
|
|
292
|
+
requests_mock.get(
|
|
293
|
+
f'https://api.opensea.io/api/v2/listings/collection/ever-fragments-of-civitas/all',
|
|
294
|
+
text=locked_listings_response,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
listings = api.fetch_listings(test_collection_slug)
|
|
298
|
+
parsed = api.parse_listings(listings)
|
|
299
|
+
|
|
300
|
+
assert not parsed.errors
|
|
301
|
+
assert not parsed.data
|
|
302
|
+
|
|
303
|
+
|
|
264
304
|
def test_parse_collection(
|
|
265
305
|
requests_mock, api, collection_response, collection_stats_response
|
|
266
306
|
):
|
|
@@ -300,7 +340,11 @@ def test_parse_collection(
|
|
|
300
340
|
assert data.total_stats.floor_price == Decimal('0.008')
|
|
301
341
|
assert data.total_stats.coin == COIN_ETH
|
|
302
342
|
assert data.blockchain == Blockchain.ETHEREUM
|
|
303
|
-
assert data.
|
|
343
|
+
assert len(data.contracts) == 2
|
|
344
|
+
assert data.contracts[0].blockchain == Blockchain.ETHEREUM
|
|
345
|
+
assert data.contracts[0].address == '0x8acb0bc7f6c77e4e2aef83ea928d5a6c2a0b7fcd'
|
|
346
|
+
assert data.contracts[1].blockchain == Blockchain.ETHEREUM
|
|
347
|
+
assert data.contracts[1].address == '0x9acb0bc7f6c77e4e2aef83ea928d5a6c2a0b7fcd'
|
|
304
348
|
|
|
305
349
|
|
|
306
350
|
def test_create_with_unsupported_blockchain():
|
|
@@ -19,6 +19,7 @@ from blockapi.v2.models import (
|
|
|
19
19
|
AssetType,
|
|
20
20
|
Blockchain,
|
|
21
21
|
Coin,
|
|
22
|
+
ContractInfo,
|
|
22
23
|
FetchResult,
|
|
23
24
|
NftCollection,
|
|
24
25
|
NftCollectionIntervalStats,
|
|
@@ -68,6 +69,8 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
68
69
|
Blockchain.ZORA: 'zora',
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
opensea_blockchains = {n: b for b, n in supported_blockchains.items()}
|
|
73
|
+
|
|
71
74
|
coin = COIN_ETH
|
|
72
75
|
api_options = ApiOptions(
|
|
73
76
|
blockchain=Blockchain.ETHEREUM,
|
|
@@ -106,9 +109,9 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
106
109
|
self._sleep_provider = sleep_provider or SleepProvider()
|
|
107
110
|
self._limit = limit
|
|
108
111
|
|
|
109
|
-
def fetch_nfts(self, address: str) -> FetchResult:
|
|
110
|
-
logger.info(f'Fetch nfts from {address}')
|
|
111
|
-
return self._coalesce(self._yield_nfts(address))
|
|
112
|
+
def fetch_nfts(self, address: str, cursor: Optional[str] = None) -> FetchResult:
|
|
113
|
+
logger.info(f'Fetch nfts from {address}, cursor={cursor}')
|
|
114
|
+
return self._coalesce(self._yield_nfts(address, cursor))
|
|
112
115
|
|
|
113
116
|
def parse_nfts(self, fetch_result: FetchResult) -> ParseResult:
|
|
114
117
|
logger.info(f'Parse nfts')
|
|
@@ -174,12 +177,16 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
174
177
|
headers=self._headers,
|
|
175
178
|
collection=collection,
|
|
176
179
|
chain=self._opensea_chain,
|
|
177
|
-
extra=dict(
|
|
180
|
+
extra=dict(
|
|
181
|
+
collection=collection, stats=stats.data, stats_errors=stats.errors
|
|
182
|
+
),
|
|
178
183
|
)
|
|
179
184
|
|
|
180
185
|
def parse_collection(self, fetch_result: FetchResult) -> ParseResult:
|
|
181
186
|
parsed, error = self._parse_collection(
|
|
182
|
-
fetch_result.
|
|
187
|
+
fetch_result.extra.get('collection'),
|
|
188
|
+
fetch_result.data,
|
|
189
|
+
fetch_result.extra.get('stats'),
|
|
183
190
|
)
|
|
184
191
|
errors = []
|
|
185
192
|
if error:
|
|
@@ -192,9 +199,13 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
192
199
|
data=[parsed] if parsed else None, errors=errors if errors else None
|
|
193
200
|
)
|
|
194
201
|
|
|
195
|
-
def _yield_nfts(
|
|
202
|
+
def _yield_nfts(
|
|
203
|
+
self, address: str, cursor: Optional[str] = None
|
|
204
|
+
) -> Iterable[Tuple[FetchResult, Optional[str]]]:
|
|
196
205
|
try:
|
|
197
|
-
yield from self._yield_fetch_data(
|
|
206
|
+
yield from self._yield_fetch_data(
|
|
207
|
+
self._fetch_nfts_page, key=address, cursor=cursor
|
|
208
|
+
)
|
|
198
209
|
except Exception as e:
|
|
199
210
|
logger.error(f'Error fetching OpenSea NFTs from {address}')
|
|
200
211
|
logger.exception(e)
|
|
@@ -318,9 +329,10 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
318
329
|
)
|
|
319
330
|
|
|
320
331
|
def _parse_collection(
|
|
321
|
-
self, data: dict, stat_data: dict
|
|
332
|
+
self, collection_ident: str, data: dict, stat_data: dict
|
|
322
333
|
) -> tuple[Optional[NftCollection], Optional[str]]:
|
|
323
334
|
if not stat_data or not data:
|
|
335
|
+
logger.warning(f'No data for collection: {collection_ident}')
|
|
324
336
|
return None, None
|
|
325
337
|
|
|
326
338
|
total = stat_data.get('total')
|
|
@@ -329,7 +341,10 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
329
341
|
if not coin:
|
|
330
342
|
coin = COIN_ETH
|
|
331
343
|
if symbol:
|
|
332
|
-
return
|
|
344
|
+
return (
|
|
345
|
+
None,
|
|
346
|
+
f'There is no mapping for opensea symbol {symbol} (collection={collection_ident})',
|
|
347
|
+
)
|
|
333
348
|
|
|
334
349
|
total_stats = NftCollectionTotalStats.from_api(
|
|
335
350
|
volume=total.get('volume'),
|
|
@@ -346,19 +361,12 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
346
361
|
week_stats = self._parse_collection_stats(intervals, 'one_week')
|
|
347
362
|
month_stats = self._parse_collection_stats(intervals, 'one_month')
|
|
348
363
|
|
|
349
|
-
|
|
350
|
-
if contracts := data.get('contracts'):
|
|
351
|
-
if contract_filtered := [
|
|
352
|
-
c.get('address')
|
|
353
|
-
for c in contracts
|
|
354
|
-
if c.get('chain') == self._opensea_chain
|
|
355
|
-
]:
|
|
356
|
-
contract = contract_filtered[0].lower()
|
|
364
|
+
contracts = list(self._yield_contracts(collection_ident, data.get('contracts')))
|
|
357
365
|
|
|
358
366
|
collection = NftCollection.from_api(
|
|
359
367
|
ident=data.get('collection'),
|
|
360
368
|
name=data.get('name'),
|
|
361
|
-
|
|
369
|
+
contracts=contracts,
|
|
362
370
|
image=data.get('image_url'),
|
|
363
371
|
is_disabled=data.get('is_disabled'),
|
|
364
372
|
is_nsfw=data.get('is_nsfw'),
|
|
@@ -445,6 +453,11 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
445
453
|
_,
|
|
446
454
|
) = self._parse_offer_item(offer[0])
|
|
447
455
|
|
|
456
|
+
offer_key = item.get('order_hash')
|
|
457
|
+
if self._is_locked(params.get('consideration'), direction, offerer):
|
|
458
|
+
logger.info(f'Locked {direction} skipped: offer_key={offer_key}')
|
|
459
|
+
return None
|
|
460
|
+
|
|
448
461
|
price = item.get('price', dict()).get('current')
|
|
449
462
|
if direction == NftOfferDirection.LISTING and price:
|
|
450
463
|
pay_amount = Decimal(price.get('value'))
|
|
@@ -458,7 +471,7 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
458
471
|
)
|
|
459
472
|
|
|
460
473
|
return NftOffer.from_api(
|
|
461
|
-
offer_key=
|
|
474
|
+
offer_key=offer_key,
|
|
462
475
|
direction=direction,
|
|
463
476
|
collection=collection,
|
|
464
477
|
contract=contract,
|
|
@@ -476,6 +489,15 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
476
489
|
pay_amount=pay_amount,
|
|
477
490
|
)
|
|
478
491
|
|
|
492
|
+
@staticmethod
|
|
493
|
+
def _is_locked(items: list[dict], direction: NftOfferDirection, offerer: str):
|
|
494
|
+
locks = [t for t in items if t.get('itemType') not in ['0', '1']]
|
|
495
|
+
if direction == NftOfferDirection.LISTING:
|
|
496
|
+
return any(locks)
|
|
497
|
+
|
|
498
|
+
locks = [t for t in locks if t.get('recipient').lower() != offerer]
|
|
499
|
+
return any(locks)
|
|
500
|
+
|
|
479
501
|
@staticmethod
|
|
480
502
|
def _parse_contract(item) -> Optional[str]:
|
|
481
503
|
criteria = item.get('criteria')
|
|
@@ -559,3 +581,26 @@ class OpenSeaApi(BlockchainApi, INftProvider, INftParser):
|
|
|
559
581
|
cursor=last_cursor,
|
|
560
582
|
time=last.time,
|
|
561
583
|
)
|
|
584
|
+
|
|
585
|
+
def _yield_contracts(self, ident: str, contracts: dict) -> Iterable[ContractInfo]:
|
|
586
|
+
if not contracts:
|
|
587
|
+
return
|
|
588
|
+
|
|
589
|
+
for item in contracts:
|
|
590
|
+
chain = item.get('chain')
|
|
591
|
+
blockchain = self.opensea_blockchains.get(chain)
|
|
592
|
+
address = item.get('address')
|
|
593
|
+
|
|
594
|
+
if not blockchain:
|
|
595
|
+
logger.warning(
|
|
596
|
+
f'Could not map {chain} to known blockchains for collection {ident}'
|
|
597
|
+
)
|
|
598
|
+
continue
|
|
599
|
+
|
|
600
|
+
if not address:
|
|
601
|
+
logger.warning(
|
|
602
|
+
f'No address available for item with chain {chain} for collection {ident}'
|
|
603
|
+
)
|
|
604
|
+
continue
|
|
605
|
+
|
|
606
|
+
yield ContractInfo.from_api(blockchain=blockchain, address=address)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
from blockapi.v2.coins import COIN_ETH, COIN_PRIME, COIN_USDC, COIN_WETH
|
|
1
|
+
from blockapi.v2.coins import COIN_DAI, COIN_ETH, COIN_PRIME, COIN_USDC, COIN_WETH
|
|
2
2
|
from blockapi.v2.models import Blockchain, Coin
|
|
3
3
|
|
|
4
4
|
OPENSEA_COINS: dict[str, Coin] = {
|
|
5
5
|
'ETH': COIN_ETH,
|
|
6
6
|
'USDC': COIN_USDC,
|
|
7
7
|
'PRIME': COIN_PRIME,
|
|
8
|
+
'DAI': COIN_DAI,
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
OPENSEA_CONTRACTS = {
|
|
@@ -243,6 +243,14 @@ COIN_XDAI = Coin(
|
|
|
243
243
|
info=CoinInfo(coingecko_id=CoingeckoId.XDAI),
|
|
244
244
|
)
|
|
245
245
|
|
|
246
|
+
COIN_DAI = Coin(
|
|
247
|
+
symbol='DAI',
|
|
248
|
+
name='Dai',
|
|
249
|
+
decimals=18,
|
|
250
|
+
blockchain=Blockchain.ETHEREUM,
|
|
251
|
+
info=CoinInfo(coingecko_id=CoingeckoId.DAI),
|
|
252
|
+
)
|
|
253
|
+
|
|
246
254
|
COIN_OKT = Coin(
|
|
247
255
|
symbol='OKT',
|
|
248
256
|
name='OKC',
|
|
@@ -270,6 +270,7 @@ class CoingeckoId(str, Enum):
|
|
|
270
270
|
CELO = 'celo'
|
|
271
271
|
COSMOS = 'cosmos'
|
|
272
272
|
CRONOS = 'crypto-com-chain'
|
|
273
|
+
DAI = 'dai'
|
|
273
274
|
DOGECOIN = 'dogecoin'
|
|
274
275
|
ETHEREUM = 'ethereum'
|
|
275
276
|
EVMOS = 'evmos'
|
|
@@ -716,11 +717,21 @@ class NftCollectionTotalStats:
|
|
|
716
717
|
)
|
|
717
718
|
|
|
718
719
|
|
|
720
|
+
@attr.s(auto_attribs=True, slots=True, frozen=True)
|
|
721
|
+
class ContractInfo:
|
|
722
|
+
blockchain: Blockchain
|
|
723
|
+
address: str
|
|
724
|
+
|
|
725
|
+
@classmethod
|
|
726
|
+
def from_api(cls, *, blockchain: Blockchain, address: str):
|
|
727
|
+
return cls(blockchain=blockchain, address=address)
|
|
728
|
+
|
|
729
|
+
|
|
719
730
|
@attr.s(auto_attribs=True, slots=True, frozen=True)
|
|
720
731
|
class NftCollection:
|
|
721
732
|
ident: str
|
|
722
733
|
name: str
|
|
723
|
-
|
|
734
|
+
contracts: list[ContractInfo]
|
|
724
735
|
image: Optional[str]
|
|
725
736
|
is_disabled: bool
|
|
726
737
|
is_nsfw: bool
|
|
@@ -736,7 +747,7 @@ class NftCollection:
|
|
|
736
747
|
*,
|
|
737
748
|
ident: str,
|
|
738
749
|
name: str,
|
|
739
|
-
|
|
750
|
+
contracts: list[ContractInfo],
|
|
740
751
|
image: Optional[str],
|
|
741
752
|
is_disabled: bool,
|
|
742
753
|
is_nsfw: bool,
|
|
@@ -749,7 +760,7 @@ class NftCollection:
|
|
|
749
760
|
return cls(
|
|
750
761
|
ident=ident,
|
|
751
762
|
name=name,
|
|
752
|
-
|
|
763
|
+
contracts=contracts,
|
|
753
764
|
image=image,
|
|
754
765
|
is_disabled=is_disabled,
|
|
755
766
|
is_nsfw=is_nsfw,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|