blockapi 0.48.2__tar.gz → 0.48.3__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 (156) hide show
  1. {blockapi-0.48.2 → blockapi-0.48.3}/PKG-INFO +1 -1
  2. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_solana.py +49 -1
  3. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/solana.py +102 -7
  4. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi.egg-info/PKG-INFO +1 -1
  5. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi.egg-info/requires.txt +1 -0
  6. {blockapi-0.48.2 → blockapi-0.48.3}/setup.py +2 -1
  7. {blockapi-0.48.2 → blockapi-0.48.3}/LICENSE.md +0 -0
  8. {blockapi-0.48.2 → blockapi-0.48.3}/README.md +0 -0
  9. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/__init__.py +0 -0
  10. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/__init__.py +0 -0
  11. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/alethio.py +0 -0
  12. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/amberdata.py +0 -0
  13. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/binance.py +0 -0
  14. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockchaininfo.py +0 -0
  15. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockchainos.py +0 -0
  16. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockchair.py +0 -0
  17. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockcypher.py +0 -0
  18. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockonomics.py +0 -0
  19. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/blockscout.py +0 -0
  20. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/btc.py +0 -0
  21. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/cardanoexplorer.py +0 -0
  22. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/chainso.py +0 -0
  23. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/cosmos.py +0 -0
  24. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/cryptoid.py +0 -0
  25. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/dcrdata.py +0 -0
  26. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/digonchain.py +0 -0
  27. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/eospark.py +0 -0
  28. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/etherscan.py +0 -0
  29. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/ethplorer.py +0 -0
  30. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/greymass.py +0 -0
  31. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/insight.py +0 -0
  32. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/kyber.py +0 -0
  33. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/neoscan.py +0 -0
  34. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/ontology.py +0 -0
  35. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/solana.py +0 -0
  36. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/stellar.py +0 -0
  37. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/subscan.py +0 -0
  38. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/terra_money.py +0 -0
  39. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/trezor.py +0 -0
  40. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/tronscan.py +0 -0
  41. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/tzscan.py +0 -0
  42. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/tzstats.py +0 -0
  43. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/zchain.py +0 -0
  44. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/api/zensystem.py +0 -0
  45. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/services.py +0 -0
  46. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/__init__.py +0 -0
  47. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/__init__.py +0 -0
  48. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_amberdata.py +0 -0
  49. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_balances.py +0 -0
  50. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_binance.py +0 -0
  51. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_blockchainos.py +0 -0
  52. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_blockchair.py +0 -0
  53. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_blockscout.py +0 -0
  54. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_btc.py +0 -0
  55. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_cosmos.py +0 -0
  56. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_dcrdata.py +0 -0
  57. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_digonchain.py +0 -0
  58. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_insight.py +0 -0
  59. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_kyber.py +0 -0
  60. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_ontio.py +0 -0
  61. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_solana.py +0 -0
  62. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_subscan.py +0 -0
  63. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_terra_money.py +0 -0
  64. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_tronscan.py +0 -0
  65. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_tzscan.py +0 -0
  66. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/api/test_tzstats.py +0 -0
  67. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/test_blockapi.py +0 -0
  68. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/test_num.py +0 -0
  69. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/test_random_user_agent.py +0 -0
  70. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/__init__.py +0 -0
  71. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/__init__.py +0 -0
  72. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/conftest.py +0 -0
  73. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
  74. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
  75. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
  76. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/nft/__init__.py +0 -0
  77. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/nft/test_magic_eden.py +0 -0
  78. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/nft/test_opensea.py +0 -0
  79. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/nft/test_simple_hash.py +0 -0
  80. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
  81. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/perpetual/test_perpetual.py +0 -0
  82. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
  83. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
  84. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_blockchainos.py +0 -0
  85. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
  86. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
  87. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
  88. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_cosmos.py +0 -0
  89. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_ethplorer.py +0 -0
  90. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_multisources.py +0 -0
  91. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
  92. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
  93. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
  94. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_base.py +0 -0
  95. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_blockchain_api.py +0 -0
  96. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
  97. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_data.py +0 -0
  98. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_enumerate_classes.py +0 -0
  99. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_generic.py +0 -0
  100. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test/v2/test_models.py +0 -0
  101. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/test_data.py +0 -0
  102. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/__init__.py +0 -0
  103. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/address.py +0 -0
  104. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/datetime.py +0 -0
  105. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/ethereum.py +0 -0
  106. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/num.py +0 -0
  107. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/utils/user_agent.py +0 -0
  108. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/__init__.py +0 -0
  109. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/__init__.py +0 -0
  110. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/blockchainos.py +0 -0
  111. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/blockchair.py +0 -0
  112. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/cosmos.py +0 -0
  113. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/__init__.py +0 -0
  114. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
  115. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/astar.py +0 -0
  116. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/avalanche.py +0 -0
  117. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/axie.py +0 -0
  118. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/base.py +0 -0
  119. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
  120. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/ethereum.py +0 -0
  121. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/fantom.py +0 -0
  122. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/heco.py +0 -0
  123. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/iotex.py +0 -0
  124. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/klaytn.py +0 -0
  125. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
  126. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/palm.py +0 -0
  127. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/polygon.py +0 -0
  128. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/covalenth/rsk.py +0 -0
  129. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/debank.py +0 -0
  130. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/debank_maps.py +0 -0
  131. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/ethplorer.py +0 -0
  132. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/nft/__init__.py +0 -0
  133. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/nft/magic_eden.py +0 -0
  134. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/nft/opensea.py +0 -0
  135. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/nft/simple_hash.py +0 -0
  136. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/optimistic_etherscan.py +0 -0
  137. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/perpetual/__init__.py +0 -0
  138. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
  139. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/perpetual/perpetual.py +0 -0
  140. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/subscan.py +0 -0
  141. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/synthetix/__init__.py +0 -0
  142. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/synthetix/synthetix.py +0 -0
  143. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
  144. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/terra.py +0 -0
  145. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/trezor.py +0 -0
  146. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/api/web3_utils.py +0 -0
  147. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/base.py +0 -0
  148. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/blockchain_mapping.py +0 -0
  149. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/coin_mapping.py +0 -0
  150. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/coins.py +0 -0
  151. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi/v2/models.py +0 -0
  152. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi.egg-info/SOURCES.txt +0 -0
  153. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi.egg-info/dependency_links.txt +0 -0
  154. {blockapi-0.48.2 → blockapi-0.48.3}/blockapi.egg-info/top_level.txt +0 -0
  155. {blockapi-0.48.2 → blockapi-0.48.3}/pyproject.toml +0 -0
  156. {blockapi-0.48.2 → blockapi-0.48.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blockapi
3
- Version: 0.48.2
3
+ Version: 0.48.3
4
4
  Summary: BlockAPI library
5
5
  Home-page: UNKNOWN
6
6
  Author: Devmons s.r.o.
@@ -3,6 +3,7 @@ from decimal import Decimal
3
3
 
4
4
  import pytest
5
5
  from requests_mock import ANY, Mocker
6
+ from solders.pubkey import Pubkey
6
7
 
7
8
  from blockapi.test.v2.api.conftest import read_file
8
9
  from blockapi.v2.api import SolanaApi, SolscanApi
@@ -99,7 +100,7 @@ def test_use_base_url_in_post(
99
100
  m.get(SONAR_TOKEN_LIST_URL, text=token_list_sonar_response)
100
101
  m.get(JUP_AG_BAN_LIST_URL, text=ban_list_jup_ag_response)
101
102
  m.post(ANY, text=get_text),
102
- api = SolanaApi(base_url='https://proxy/solana/')
103
+ api = SolanaApi(base_url='https://proxy/solana/', fetch_metaplex=False)
103
104
  api.get_balance(test_addr)
104
105
 
105
106
 
@@ -496,3 +497,50 @@ def balances_with_different_coins():
496
497
  is_wallet=True,
497
498
  ),
498
499
  ]
500
+
501
+
502
+ def test_derive_metaplex_account_address_from_mint(solana_api):
503
+ mint = 'C2PxCHLeDkp1BsG1uueu7aGEfXQkKoXxzTgMPQ5DA6QW'
504
+ metadata = solana_api.get_metadata_pda(mint)
505
+
506
+ assert metadata == '3G5bT5bgpdwiUbYHfoBSe6SDAwiQaTKc3TFGks7bA3Qw'
507
+
508
+
509
+ @pytest.mark.vcr()
510
+ def test_fetch_metaplex_account(solana_api, metaplex_content):
511
+ data = solana_api.fetch_metaplex_account(
512
+ '3G5bT5bgpdwiUbYHfoBSe6SDAwiQaTKc3TFGks7bA3Qw'
513
+ )
514
+ assert data == metaplex_content
515
+
516
+
517
+ @pytest.mark.vcr()
518
+ def test_parse_metaplex_account(solana_api, metaplex_content):
519
+ token = solana_api.parse_metaplex_account(metaplex_content, decimals=9)
520
+ assert token['symbol'] == 'ACT'
521
+ assert token['name'] == 'Act I : The AI Prophecy'
522
+ assert token['decimals'] == 9
523
+ assert token['chainId'] == 101
524
+ assert token['tags'] == ['SOLANA', 'MEME']
525
+ assert (
526
+ token['logoURI']
527
+ == 'https://gateway.pinata.cloud/ipfs/QmS4m4cBjukg7YhimqhfRUb2pUE4bBPXbrbMBb5hzxNbUA'
528
+ )
529
+
530
+
531
+ @pytest.fixture
532
+ def metaplex_content():
533
+ return (
534
+ 'BJ5okXRntUEewuGiuQOIMWt8+YkvFnY7/DoghPyz5qwNo8wWpzohlcKPjWIDCl3nggGst3'
535
+ 'CeFvU+P1wA6yNMhm8gAAAAQWN0IEkgOiBUaGUgQUkgUHJvcGhlY3kAAAAAAAAAAAAKAAAA'
536
+ 'QUNUAAAAAAAAAMgAAABodHRwczovL2dhdGV3YXkucGluYXRhLmNsb3VkL2lwZnMvUW1WZk'
537
+ '1QQ1VQczVWMW9tYUR2b25pMzh3OGFFSnBGVGpFWlJOUm1vVnlleFhXZQAAAAAAAAAAAAAA'
538
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
539
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
540
+ 'AAAAAAAAAAAAAf4BAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
541
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
542
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
543
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
544
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
545
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ=='
546
+ )
@@ -1,8 +1,11 @@
1
+ import base64
1
2
  import json
2
3
  from typing import Dict, Iterable, Optional, Set
3
4
 
5
+ import requests
4
6
  from cytoolz import reduceby
5
7
  from requests import Response
8
+ from solders.pubkey import Pubkey
6
9
 
7
10
  from blockapi.utils.user_agent import get_random_user_agent
8
11
  from blockapi.v2.base import (
@@ -54,16 +57,24 @@ class SolanaApi(CustomizableBlockchainApi, BalanceMixin):
54
57
 
55
58
  token_program_id = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
56
59
  token2022_program_id = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'
60
+ METADATA_PROGRAM_ID = Pubkey.from_string(
61
+ "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
62
+ )
63
+
57
64
  _tokens_map: Optional[Dict[str, Dict]] = None
58
65
  _ban_list: Optional[Set[str]] = None
59
66
 
67
+ def __init__(self, base_url: Optional[str] = None, fetch_metaplex: bool = True):
68
+ super().__init__(base_url)
69
+ self.fetch_metaplex = fetch_metaplex
70
+
60
71
  @property
61
72
  def tokens_map(self) -> Dict[str, Dict]:
62
73
  if self._tokens_map is None:
63
74
  map_jup_ag = self._get_token_map_jup_ag()
64
75
  map_solana = self._get_token_map_solana()
65
- map_sonar = self._get_token_map_sonar()
66
- self._tokens_map = {**map_jup_ag, **map_sonar, **map_solana}
76
+ self._tokens_map = {**map_jup_ag, **map_solana}
77
+
67
78
  return self._tokens_map
68
79
 
69
80
  def _get_token_map_solana(self) -> Dict[str, Dict]:
@@ -266,15 +277,19 @@ class SolanaApi(CustomizableBlockchainApi, BalanceMixin):
266
277
 
267
278
  address = info['mint']
268
279
 
269
- # TODO unknown token is for 99% NFT, add loading NFT metadata using metaplex
270
- # token account
271
- if address not in self.tokens_map:
272
- return
273
-
274
280
  # ignore banned tokens
275
281
  if address in self.ban_list:
276
282
  return
277
283
 
284
+ # TODO unknown token is for 99% NFT, add loading NFT metadata using metaplex
285
+ # token account
286
+ # TODO move fetching tokens_map to fetch
287
+ if address not in self.tokens_map:
288
+ if not self.update_token_from_metaplex(
289
+ address, decimals=info['tokenAmount']['decimals']
290
+ ):
291
+ return
292
+
278
293
  return BalanceItem.from_api(
279
294
  balance_raw=info['tokenAmount']['amount'],
280
295
  coin=self.get_token_data(address),
@@ -301,6 +316,86 @@ class SolanaApi(CustomizableBlockchainApi, BalanceMixin):
301
316
  ),
302
317
  )
303
318
 
319
+ def update_token_from_metaplex(self, address: str, decimals: int) -> bool:
320
+ if not self.fetch_metaplex:
321
+ return False
322
+
323
+ pda = self.get_metadata_pda(address)
324
+ content = self.fetch_metaplex_account(pda)
325
+ token = self.parse_metaplex_account(content, decimals)
326
+ if not token or not token.get('symbol'):
327
+ return False
328
+
329
+ self.tokens_map[address] = token
330
+ return True
331
+
332
+ def get_metadata_pda(self, mint_address: str) -> str:
333
+ """
334
+ Derives Metaplex metadata address from mint address
335
+
336
+ :param mint_address: Token mint to get metadata for, e.g. `8ZperFA2cciv2rN2YC1QC3YjkuGcTQYiq9vc9aKmdoWM`
337
+ :return: Token metadata account
338
+ """
339
+ return str(
340
+ Pubkey.find_program_address(
341
+ [
342
+ b"metadata",
343
+ bytes(self.METADATA_PROGRAM_ID),
344
+ bytes(Pubkey.from_string(mint_address)),
345
+ ],
346
+ self.METADATA_PROGRAM_ID,
347
+ )[0]
348
+ )
349
+
350
+ def fetch_metaplex_account(self, metadata_pda: str) -> Optional[str]:
351
+ """
352
+ Fetch and validate metaplex account content
353
+ :param metadata_pda: address of metadata account
354
+ :return: base64 encoded account content
355
+ """
356
+ response = self._request(
357
+ 'getAccountInfo', [metadata_pda, {"encoding": "base64"}]
358
+ )
359
+ value = response['result']['value']
360
+ if value:
361
+ data = value['data'][0]
362
+ if value['data'][1] != 'base64':
363
+ return None
364
+
365
+ if value['owner'] != 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s':
366
+ return None
367
+
368
+ return data
369
+
370
+ return None
371
+
372
+ def parse_metaplex_account(self, content: str, decimals: int) -> Optional[dict]:
373
+ if not content:
374
+ return None
375
+
376
+ raw = base64.b64decode(content)
377
+
378
+ url = raw[119:255].decode('utf-8').rstrip('\x00')
379
+ data = requests.get(url)
380
+ if data:
381
+ data.raise_for_status()
382
+ parsed = data.json()
383
+ return dict(
384
+ name=parsed.get('name'),
385
+ symbol=parsed.get('symbol'),
386
+ tags=parsed.get('tags'),
387
+ logoURI=parsed.get('image'),
388
+ decimals=decimals,
389
+ chainId=101,
390
+ )
391
+
392
+ return dict(
393
+ name=raw[69:101].decode('utf-8').rstrip('\x00'),
394
+ symbol=raw[105:115].decode('utf-8').rstrip('\x00'),
395
+ decimals=decimals,
396
+ chainId=101,
397
+ )
398
+
304
399
  def _request(self, method, params):
305
400
  body = json.dumps(
306
401
  {'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blockapi
3
- Version: 0.48.2
3
+ Version: 0.48.3
4
4
  Summary: BlockAPI library
5
5
  Home-page: UNKNOWN
6
6
  Author: Devmons s.r.o.
@@ -14,3 +14,4 @@ pytest
14
14
  pytest-vcr
15
15
  requests_mock>=1.9.3
16
16
  attrs<=22.1.0,>=17.4.0
17
+ solders==0.22.0
@@ -6,7 +6,7 @@ with open("README.md", "r") as f:
6
6
 
7
7
  PACKAGES = find_packages(where='.')
8
8
 
9
- __version__ = "0.48.2"
9
+ __version__ = "0.48.3"
10
10
 
11
11
  setuptools.setup(
12
12
  name='blockapi',
@@ -34,5 +34,6 @@ setuptools.setup(
34
34
  'pytest-vcr',
35
35
  'requests_mock>=1.9.3',
36
36
  'attrs>=17.4.0,<=22.1.0',
37
+ 'solders==0.22.0',
37
38
  ],
38
39
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes