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.
Files changed (150) hide show
  1. {blockapi-0.31.2 → blockapi-0.32.0}/PKG-INFO +1 -1
  2. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/nft/test_opensea.py +45 -1
  3. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/nft/opensea.py +64 -19
  4. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/coin_mapping.py +2 -1
  5. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/coins.py +8 -0
  6. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/models.py +14 -3
  7. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/PKG-INFO +1 -1
  8. {blockapi-0.31.2 → blockapi-0.32.0}/setup.py +1 -1
  9. {blockapi-0.31.2 → blockapi-0.32.0}/LICENSE.md +0 -0
  10. {blockapi-0.31.2 → blockapi-0.32.0}/README.md +0 -0
  11. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/__init__.py +0 -0
  12. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/__init__.py +0 -0
  13. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/alethio.py +0 -0
  14. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/amberdata.py +0 -0
  15. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/binance.py +0 -0
  16. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchaininfo.py +0 -0
  17. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchainos.py +0 -0
  18. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockchair.py +0 -0
  19. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockcypher.py +0 -0
  20. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockonomics.py +0 -0
  21. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/blockscout.py +0 -0
  22. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/btc.py +0 -0
  23. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cardanoexplorer.py +0 -0
  24. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/chainso.py +0 -0
  25. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cosmos.py +0 -0
  26. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/cryptoid.py +0 -0
  27. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/dcrdata.py +0 -0
  28. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/digonchain.py +0 -0
  29. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/eospark.py +0 -0
  30. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/etherscan.py +0 -0
  31. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/ethplorer.py +0 -0
  32. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/greymass.py +0 -0
  33. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/insight.py +0 -0
  34. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/kyber.py +0 -0
  35. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/neoscan.py +0 -0
  36. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/ontology.py +0 -0
  37. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/solana.py +0 -0
  38. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/stellar.py +0 -0
  39. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/subscan.py +0 -0
  40. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/terra_money.py +0 -0
  41. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/trezor.py +0 -0
  42. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tronscan.py +0 -0
  43. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tzscan.py +0 -0
  44. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/tzstats.py +0 -0
  45. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/zchain.py +0 -0
  46. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/api/zensystem.py +0 -0
  47. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/services.py +0 -0
  48. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/__init__.py +0 -0
  49. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/__init__.py +0 -0
  50. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_amberdata.py +0 -0
  51. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_balances.py +0 -0
  52. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_binance.py +0 -0
  53. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockchainos.py +0 -0
  54. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockchair.py +0 -0
  55. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_blockscout.py +0 -0
  56. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_btc.py +0 -0
  57. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_cosmos.py +0 -0
  58. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_dcrdata.py +0 -0
  59. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_digonchain.py +0 -0
  60. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_insight.py +0 -0
  61. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_kyber.py +0 -0
  62. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_ontio.py +0 -0
  63. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_solana.py +0 -0
  64. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_subscan.py +0 -0
  65. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_terra_money.py +0 -0
  66. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tronscan.py +0 -0
  67. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tzscan.py +0 -0
  68. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/api/test_tzstats.py +0 -0
  69. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_blockapi.py +0 -0
  70. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_num.py +0 -0
  71. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/test_random_user_agent.py +0 -0
  72. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/__init__.py +0 -0
  73. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/__init__.py +0 -0
  74. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/conftest.py +0 -0
  75. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
  76. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
  77. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
  78. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/nft/__init__.py +0 -0
  79. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
  80. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/perpetual/test_perpetual.py +0 -0
  81. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
  82. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
  83. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchainos.py +0 -0
  84. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
  85. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
  86. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
  87. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_ethplorer.py +0 -0
  88. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_multisources.py +0 -0
  89. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
  90. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_solana.py +0 -0
  91. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
  92. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
  93. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_blockchain_api.py +0 -0
  94. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
  95. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_data.py +0 -0
  96. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_enumerate_classes.py +0 -0
  97. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_generic.py +0 -0
  98. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test/v2/test_models.py +0 -0
  99. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/test_data.py +0 -0
  100. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/__init__.py +0 -0
  101. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/address.py +0 -0
  102. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/datetime.py +0 -0
  103. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/ethereum.py +0 -0
  104. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/num.py +0 -0
  105. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/utils/user_agent.py +0 -0
  106. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/__init__.py +0 -0
  107. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/__init__.py +0 -0
  108. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/blockchainos.py +0 -0
  109. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/blockchair.py +0 -0
  110. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/cosmos.py +0 -0
  111. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/__init__.py +0 -0
  112. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
  113. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/astar.py +0 -0
  114. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/avalanche.py +0 -0
  115. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/axie.py +0 -0
  116. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/base.py +0 -0
  117. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
  118. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/ethereum.py +0 -0
  119. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/fantom.py +0 -0
  120. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/heco.py +0 -0
  121. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/iotex.py +0 -0
  122. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/klaytn.py +0 -0
  123. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
  124. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/palm.py +0 -0
  125. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/polygon.py +0 -0
  126. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/covalenth/rsk.py +0 -0
  127. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/debank.py +0 -0
  128. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/debank_maps.py +0 -0
  129. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/ethplorer.py +0 -0
  130. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/nft/__init__.py +0 -0
  131. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/optimistic_etherscan.py +0 -0
  132. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/__init__.py +0 -0
  133. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
  134. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/perpetual/perpetual.py +0 -0
  135. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/solana.py +0 -0
  136. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/subscan.py +0 -0
  137. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/__init__.py +0 -0
  138. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/synthetix.py +0 -0
  139. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
  140. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/terra.py +0 -0
  141. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/trezor.py +0 -0
  142. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/api/web3_utils.py +0 -0
  143. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/base.py +0 -0
  144. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi/v2/blockchain_mapping.py +0 -0
  145. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/SOURCES.txt +0 -0
  146. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/dependency_links.txt +0 -0
  147. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/requires.txt +0 -0
  148. {blockapi-0.31.2 → blockapi-0.32.0}/blockapi.egg-info/top_level.txt +0 -0
  149. {blockapi-0.31.2 → blockapi-0.32.0}/pyproject.toml +0 -0
  150. {blockapi-0.31.2 → blockapi-0.32.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blockapi
3
- Version: 0.31.2
3
+ Version: 0.32.0
4
4
  Summary: BlockAPI library
5
5
  Home-page: UNKNOWN
6
6
  Author: Devmons s.r.o.
@@ -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.contract == '0x8acb0bc7f6c77e4e2aef83ea928d5a6c2a0b7fcd'
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(stats=stats.data, stats_errors=stats.errors),
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.data, fetch_result.extra.get('stats')
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(self, address: str) -> Iterable[Tuple[FetchResult, Optional[str]]]:
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(self._fetch_nfts_page, key=address)
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 None, f'There is no mapping for opensea symbol {symbol}'
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
- contract = data.get('collection')
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
- contract=contract,
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=item.get('order_hash'),
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
- contract: str
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
- contract: str,
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
- contract=contract,
763
+ contracts=contracts,
753
764
  image=image,
754
765
  is_disabled=is_disabled,
755
766
  is_nsfw=is_nsfw,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blockapi
3
- Version: 0.31.2
3
+ Version: 0.32.0
4
4
  Summary: BlockAPI library
5
5
  Home-page: UNKNOWN
6
6
  Author: Devmons s.r.o.
@@ -6,7 +6,7 @@ with open("README.md", "r") as f:
6
6
 
7
7
  PACKAGES = find_packages(where='.')
8
8
 
9
- __version__ = "0.31.2"
9
+ __version__ = "0.32.0"
10
10
 
11
11
  setuptools.setup(
12
12
  name='blockapi',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes