blockapi 2.5.0__tar.gz → 2.5.2__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 (110) hide show
  1. {blockapi-2.5.0 → blockapi-2.5.2}/PKG-INFO +1 -1
  2. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/perpetual/test_perpetual.py +15 -0
  3. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_solana.py +178 -128
  4. blockapi-2.5.2/blockapi/v2/api/solana.py +446 -0
  5. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/sui.py +11 -6
  6. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/models.py +20 -3
  7. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/PKG-INFO +1 -1
  8. {blockapi-2.5.0 → blockapi-2.5.2}/setup.py +1 -1
  9. blockapi-2.5.0/blockapi/v2/api/solana.py +0 -508
  10. {blockapi-2.5.0 → blockapi-2.5.2}/LICENSE.md +0 -0
  11. {blockapi-2.5.0 → blockapi-2.5.2}/README.md +0 -0
  12. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/__init__.py +0 -0
  13. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/services.py +0 -0
  14. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/__init__.py +0 -0
  15. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_blockapi.py +0 -0
  16. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_num.py +0 -0
  17. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_random_user_agent.py +0 -0
  18. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/__init__.py +0 -0
  19. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/__init__.py +0 -0
  20. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/conftest.py +0 -0
  21. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
  22. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
  23. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
  24. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/__init__.py +0 -0
  25. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_magic_eden.py +0 -0
  26. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_opensea.py +0 -0
  27. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_simple_hash.py +0 -0
  28. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_unisat.py +0 -0
  29. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
  30. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
  31. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
  32. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchain_info.py +0 -0
  33. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchainos.py +0 -0
  34. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
  35. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
  36. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
  37. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_cosmos.py +0 -0
  38. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_ethplorer.py +0 -0
  39. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_haskoin.py +0 -0
  40. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_multisources.py +0 -0
  41. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
  42. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
  43. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_sui.py +0 -0
  44. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
  45. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_trezor_zec.py +0 -0
  46. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_base.py +0 -0
  47. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_blockchain_api.py +0 -0
  48. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
  49. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_data.py +0 -0
  50. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_enumerate_classes.py +0 -0
  51. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_generic.py +0 -0
  52. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_models.py +0 -0
  53. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test_data.py +0 -0
  54. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/__init__.py +0 -0
  55. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/address.py +0 -0
  56. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/datetime.py +0 -0
  57. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/num.py +0 -0
  58. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/user_agent.py +0 -0
  59. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/__init__.py +0 -0
  60. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/__init__.py +0 -0
  61. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchain_info.py +0 -0
  62. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchainos.py +0 -0
  63. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchair.py +0 -0
  64. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/cosmos.py +0 -0
  65. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/__init__.py +0 -0
  66. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
  67. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/astar.py +0 -0
  68. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/avalanche.py +0 -0
  69. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/axie.py +0 -0
  70. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/base.py +0 -0
  71. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
  72. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/ethereum.py +0 -0
  73. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/fantom.py +0 -0
  74. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/heco.py +0 -0
  75. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/iotex.py +0 -0
  76. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/klaytn.py +0 -0
  77. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
  78. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/palm.py +0 -0
  79. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/polygon.py +0 -0
  80. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/rsk.py +0 -0
  81. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/debank.py +0 -0
  82. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/debank_maps.py +0 -0
  83. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/ethplorer.py +0 -0
  84. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/haskoin.py +0 -0
  85. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/__init__.py +0 -0
  86. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/magic_eden.py +0 -0
  87. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/opensea.py +0 -0
  88. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/simple_hash.py +0 -0
  89. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/unisat.py +0 -0
  90. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/optimistic_etherscan.py +0 -0
  91. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/__init__.py +0 -0
  92. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
  93. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/perpetual.py +0 -0
  94. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/subscan.py +0 -0
  95. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/__init__.py +0 -0
  96. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/synthetix.py +0 -0
  97. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
  98. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/terra.py +0 -0
  99. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/trezor.py +0 -0
  100. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/web3_utils.py +0 -0
  101. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/base.py +0 -0
  102. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/blockchain_mapping.py +0 -0
  103. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/coin_mapping.py +0 -0
  104. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/coins.py +0 -0
  105. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/SOURCES.txt +0 -0
  106. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/dependency_links.txt +0 -0
  107. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/requires.txt +0 -0
  108. {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/top_level.txt +0 -0
  109. {blockapi-2.5.0 → blockapi-2.5.2}/pyproject.toml +0 -0
  110. {blockapi-2.5.0 → blockapi-2.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blockapi
3
- Version: 2.5.0
3
+ Version: 2.5.2
4
4
  Summary: BlockAPI library
5
5
  Home-page: https://github.com/crypkit/blockapi
6
6
  Author: Devmons s.r.o.
@@ -16,11 +16,17 @@ def perp_api():
16
16
  test_address = '0x134089B387E22f52b1e06CC80d9a5F622032EF74'
17
17
 
18
18
 
19
+ @pytest.mark.skip(
20
+ reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
21
+ )
19
22
  def test_perp_contract_address():
20
23
  contract = perp_contract_address('PERP')
21
24
  assert contract == '0xbC396689893D065F41bc2C6EcbeE5e0085233447'
22
25
 
23
26
 
27
+ @pytest.mark.skip(
28
+ reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
29
+ )
24
30
  def test_perp_invalid_contract_raises():
25
31
  with pytest.raises(ValueError, match='Invalid contract name.'):
26
32
  perp_contract_address("abc")
@@ -37,6 +43,9 @@ def filter_infura_key(request):
37
43
 
38
44
 
39
45
  @pytest.mark.integration
46
+ @pytest.mark.skip(
47
+ reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
48
+ )
40
49
  def test_fetch():
41
50
  key = os.environ.get('INFURA_API_KEY')
42
51
  api = PerpetualApi(f'https://mainnet.infura.io/v3/{key}')
@@ -45,6 +54,9 @@ def test_fetch():
45
54
 
46
55
 
47
56
  @pytest.mark.integration
57
+ @pytest.mark.skip(
58
+ reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
59
+ )
48
60
  def test_fetch_error():
49
61
  api = PerpetualApi(f'https://mainnet.infura.io/v3/no-key')
50
62
  raw = api.fetch_balances(test_address)
@@ -55,6 +67,9 @@ def test_fetch_error():
55
67
  )
56
68
 
57
69
 
70
+ @pytest.mark.skip(
71
+ reason='metadata.perp.exchange/production.json returns 404 — upstream endpoint is offline'
72
+ )
58
73
  def test_fetch_error_raises_from_get_balances():
59
74
  api = PerpetualApi(f'https://mainnet.infura.io/v3/no-key')
60
75
  with pytest.raises(ApiException) as exc:
@@ -1,18 +1,11 @@
1
- import json
2
1
  from decimal import Decimal
2
+ from unittest.mock import patch
3
3
 
4
4
  import pytest
5
5
  from requests_mock import ANY, Mocker
6
- from solders.pubkey import Pubkey
7
6
 
8
7
  from blockapi.test.v2.api.conftest import read_file
9
8
  from blockapi.v2.api import SolanaApi, SolscanApi
10
- from blockapi.v2.api.solana import (
11
- JUP_AG_BAN_LIST_URL,
12
- JUP_AG_TOKEN_LIST_URL,
13
- SOL_TOKEN_LIST_URL,
14
- SONAR_TOKEN_LIST_URL,
15
- )
16
9
  from blockapi.v2.models import (
17
10
  AssetType,
18
11
  BalanceItem,
@@ -23,6 +16,13 @@ from blockapi.v2.models import (
23
16
  )
24
17
 
25
18
 
19
+ @pytest.fixture(autouse=True)
20
+ def _reset_caches():
21
+ SolanaApi._das_cache = {}
22
+ yield
23
+ SolanaApi._das_cache = {}
24
+
25
+
26
26
  def test_merge_balances_with_different_coins(solana_api, balances_with_different_coins):
27
27
  merged = solana_api.merge_balances_with_same_coin(balances_with_different_coins)
28
28
  assert len(merged) == 2
@@ -75,24 +75,21 @@ def test_use_base_url():
75
75
 
76
76
 
77
77
  def test_use_base_url_in_post(
78
- solana_value_response,
79
- solana_response,
78
+ sol_balance_response,
79
+ token_accounts_response,
80
+ das_asset_batch_response,
80
81
  staked_solana_response,
81
- rent_reserve_solana_response,
82
- token_list_sol_response,
83
- token_list_jup_ag_response,
84
- token_list_sonar_response,
85
- ban_list_jup_ag_response,
86
82
  ):
87
83
  test_addr = '5PjMxaijeVVQtuEzxK2NxyJeWwUbpTsi2uXuZ653WoHu'
84
+ empty_token_accounts = '{"jsonrpc":"2.0","result":{"context":{"apiVersion":"1.17.34","slot":268207149},"value":[]},"id":1}'
88
85
 
89
86
  iterator = iter(
90
87
  [
91
- solana_value_response,
92
- solana_response,
93
- '{"result": {"value": []}}',
88
+ sol_balance_response,
89
+ token_accounts_response,
90
+ empty_token_accounts,
91
+ das_asset_batch_response,
94
92
  staked_solana_response,
95
- rent_reserve_solana_response,
96
93
  ]
97
94
  )
98
95
 
@@ -102,52 +99,172 @@ def test_use_base_url_in_post(
102
99
  return data
103
100
 
104
101
  with Mocker() as m:
105
- m.get(SOL_TOKEN_LIST_URL, text=token_list_sol_response)
106
- m.get(JUP_AG_TOKEN_LIST_URL, text=token_list_jup_ag_response)
107
- m.get(SONAR_TOKEN_LIST_URL, text=token_list_sonar_response)
108
- m.get(JUP_AG_BAN_LIST_URL, text=ban_list_jup_ag_response)
109
102
  m.post(ANY, text=get_text),
110
- api = SolanaApi(base_url='https://proxy/solana/', fetch_metaplex=False)
103
+ api = SolanaApi(base_url='https://proxy/solana/')
111
104
  api.get_balance(test_addr)
112
105
 
113
106
 
114
- def test_create_token(
115
- requests_mock,
116
- token_list_sol_response,
117
- token_list_jup_ag_response,
118
- token_list_sonar_response,
119
- ban_list_jup_ag_response,
120
- ):
121
- requests_mock.get(SOL_TOKEN_LIST_URL, text=token_list_sol_response)
122
- requests_mock.get(JUP_AG_TOKEN_LIST_URL, text=token_list_jup_ag_response)
123
- requests_mock.get(SONAR_TOKEN_LIST_URL, text=token_list_sonar_response)
124
- requests_mock.get(JUP_AG_BAN_LIST_URL, text=ban_list_jup_ag_response)
107
+ def test_build_coin_from_das_asset():
108
+ api = SolanaApi()
109
+ asset = {
110
+ 'id': 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn',
111
+ 'interface': 'FungibleToken',
112
+ 'content': {
113
+ 'metadata': {
114
+ 'name': 'Jito Staked SOL',
115
+ 'symbol': 'JITOSOL',
116
+ },
117
+ 'links': {
118
+ 'image': 'https://example.com/jitosol.png',
119
+ },
120
+ },
121
+ 'token_info': {
122
+ 'decimals': 9,
123
+ 'symbol': 'JITOSOL',
124
+ },
125
+ }
125
126
 
126
- coin = SolanaApi().get_token_data('J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn')
127
+ coin = api._build_coin_from_das_asset(asset)
127
128
  assert coin.symbol == 'JITOSOL'
128
129
  assert coin.name == 'Jito Staked SOL'
129
130
  assert coin.decimals == 9
130
131
  assert coin.blockchain == Blockchain.SOLANA
131
132
  assert coin.address == 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn'
133
+ assert coin.info.logo_url == 'https://example.com/jitosol.png'
134
+ assert coin.is_nft is False
135
+ assert 'FungibleToken' in coin.standards
136
+
137
+
138
+ def test_nft_skipped_when_include_nfts_false():
139
+ api = SolanaApi(include_nfts=False)
140
+ asset = {
141
+ 'id': 'NFTmint123',
142
+ 'interface': 'V1_NFT',
143
+ 'content': {
144
+ 'metadata': {
145
+ 'name': 'Cool NFT',
146
+ 'symbol': 'CNFT',
147
+ },
148
+ 'links': {},
149
+ },
150
+ 'token_info': {
151
+ 'decimals': 0,
152
+ },
153
+ }
132
154
 
155
+ coin = api._build_coin_from_das_asset(asset)
156
+ assert coin is not None
157
+ assert coin.is_nft is True
133
158
 
134
- def test_map_mplx_token(
135
- requests_mock,
136
- token_list_sol_response,
137
- token_list_jup_ag_response,
138
- token_list_sonar_response,
139
- ban_list_jup_ag_response,
140
- ):
141
- requests_mock.get(SOL_TOKEN_LIST_URL, text=token_list_sol_response)
142
- requests_mock.get(JUP_AG_TOKEN_LIST_URL, text=token_list_jup_ag_response)
143
- requests_mock.get(SONAR_TOKEN_LIST_URL, text=token_list_sonar_response)
144
- requests_mock.get(JUP_AG_BAN_LIST_URL, text=ban_list_jup_ag_response)
145
-
146
- coin = SolanaApi().get_token_data('METAewgxyPbgwsseH8T16a39CQ5VyVxZi9zXiDPY18m')
147
- assert coin.symbol == 'MPLX'
148
- assert coin.decimals == 6
149
- assert coin.blockchain == Blockchain.SOLANA
150
- assert coin.address == 'METAewgxyPbgwsseH8T16a39CQ5VyVxZi9zXiDPY18m'
159
+
160
+ def test_nft_included_when_include_nfts_true():
161
+ api = SolanaApi(include_nfts=True)
162
+ asset = {
163
+ 'id': 'NFTmint123',
164
+ 'interface': 'V1_NFT',
165
+ 'content': {
166
+ 'metadata': {
167
+ 'name': 'Cool NFT',
168
+ 'symbol': 'CNFT',
169
+ },
170
+ 'links': {
171
+ 'image': 'https://example.com/nft.png',
172
+ },
173
+ },
174
+ 'token_info': {
175
+ 'decimals': 0,
176
+ },
177
+ }
178
+
179
+ coin = api._build_coin_from_das_asset(asset)
180
+ assert coin is not None
181
+ assert coin.symbol == 'CNFT'
182
+ assert coin.name == 'Cool NFT'
183
+ assert coin.is_nft is True
184
+ assert 'V1_NFT' in coin.standards
185
+
186
+
187
+ def test_parse_staked_balance_skips_undelegated():
188
+ api = SolanaApi()
189
+ response = {
190
+ 'result': [
191
+ # Delegated account with stake
192
+ {
193
+ 'account': {
194
+ 'lamports': 2282880,
195
+ 'data': {
196
+ 'parsed': {
197
+ 'info': {
198
+ 'stake': {
199
+ 'delegation': {
200
+ 'stake': '1000000000',
201
+ }
202
+ }
203
+ }
204
+ }
205
+ },
206
+ }
207
+ },
208
+ # Undelegated account: stake key is null
209
+ {
210
+ 'account': {
211
+ 'lamports': 2282880,
212
+ 'data': {
213
+ 'parsed': {
214
+ 'info': {
215
+ 'stake': None,
216
+ }
217
+ }
218
+ },
219
+ }
220
+ },
221
+ # Undelegated account: stake key absent
222
+ {
223
+ 'account': {
224
+ 'lamports': 2282880,
225
+ 'data': {
226
+ 'parsed': {
227
+ 'info': {},
228
+ }
229
+ },
230
+ }
231
+ },
232
+ ]
233
+ }
234
+ result = api._parse_staked_balance(response)
235
+ assert result is not None
236
+ assert result.balance_raw == 1000000000
237
+ assert result.asset_type == AssetType.STAKED
238
+
239
+
240
+ def test_das_cache_stores_sentinel_for_unknown_mint():
241
+ api = SolanaApi()
242
+ unknown_mint = 'UnknownMint111111111111111111111111111111111'
243
+
244
+ with patch.object(
245
+ api,
246
+ '_request',
247
+ return_value={'result': []},
248
+ ):
249
+ api._fetch_das_assets([unknown_mint])
250
+
251
+ assert unknown_mint in api._das_cache
252
+ assert api._das_cache[unknown_mint] == {}
253
+
254
+
255
+ def test_das_cache_prevents_refetch():
256
+ api = SolanaApi()
257
+ # Pre-populate cache
258
+ api._das_cache['mint1'] = {
259
+ 'id': 'mint1',
260
+ 'interface': 'FungibleToken',
261
+ 'content': {'metadata': {'name': 'Token1', 'symbol': 'TK1'}, 'links': {}},
262
+ 'token_info': {'decimals': 6},
263
+ }
264
+ # _fetch_das_assets should skip cached mint
265
+ with patch.object(api, '_request') as mock_request:
266
+ api._fetch_das_assets(['mint1'])
267
+ mock_request.assert_not_called()
151
268
 
152
269
 
153
270
  def test_solscan_get_staked_balance(requests_mock, solscan_staked_response):
@@ -308,43 +425,23 @@ def balances_with_mixed_coins():
308
425
 
309
426
 
310
427
  @pytest.fixture
311
- def solana_response():
312
- return read_file('data/solana/solana_response.json')
313
-
314
-
315
- @pytest.fixture
316
- def solana_value_response():
317
- return json.dumps(dict(result=dict(value=0)))
318
-
319
-
320
- @pytest.fixture
321
- def staked_solana_response():
322
- return read_file('data/solana/staked_solana_response.json')
323
-
324
-
325
- @pytest.fixture
326
- def rent_reserve_solana_response():
327
- return read_file('data/solana/rent_reserve_solana_response.json')
328
-
329
-
330
- @pytest.fixture
331
- def token_list_sol_response():
332
- return read_file('data/solana/token-list-solana.json')
428
+ def sol_balance_response():
429
+ return '{"jsonrpc":"2.0","result":{"context":{"apiVersion":"1.17.34","slot":268207149},"value":0},"id":1}'
333
430
 
334
431
 
335
432
  @pytest.fixture
336
- def token_list_jup_ag_response():
337
- return read_file('data/solana/token-list-jup-ag.csv')
433
+ def token_accounts_response():
434
+ return read_file('data/solana/token_accounts_response.json')
338
435
 
339
436
 
340
437
  @pytest.fixture
341
- def token_list_sonar_response():
342
- return read_file('data/solana/token-list-sonar.json')
438
+ def das_asset_batch_response():
439
+ return read_file('data/solana/das_get_asset_batch_response.json')
343
440
 
344
441
 
345
442
  @pytest.fixture
346
- def ban_list_jup_ag_response():
347
- return read_file('data/solana/ban-list-jup-ag.csv')
443
+ def staked_solana_response():
444
+ return read_file('data/solana/staked_solana_response.json')
348
445
 
349
446
 
350
447
  @pytest.fixture
@@ -506,53 +603,6 @@ def balances_with_different_coins():
506
603
  ]
507
604
 
508
605
 
509
- def test_derive_metaplex_account_address_from_mint(solana_api):
510
- mint = 'C2PxCHLeDkp1BsG1uueu7aGEfXQkKoXxzTgMPQ5DA6QW'
511
- metadata = solana_api.get_metadata_pda(mint)
512
-
513
- assert metadata == '3G5bT5bgpdwiUbYHfoBSe6SDAwiQaTKc3TFGks7bA3Qw'
514
-
515
-
516
- @pytest.mark.vcr()
517
- def test_fetch_metaplex_account(solana_api, metaplex_content):
518
- data = solana_api.fetch_metaplex_account(
519
- '3G5bT5bgpdwiUbYHfoBSe6SDAwiQaTKc3TFGks7bA3Qw'
520
- )
521
- assert data == metaplex_content
522
-
523
-
524
- @pytest.mark.vcr()
525
- def test_parse_metaplex_account(solana_api, metaplex_content):
526
- token = solana_api.parse_metaplex_account(metaplex_content, decimals=9)
527
- assert token['symbol'] == 'ACT'
528
- assert token['name'] == 'Act I : The AI Prophecy'
529
- assert token['decimals'] == 9
530
- assert token['chainId'] == 101
531
- assert token['tags'] == ['SOLANA', 'MEME']
532
- assert (
533
- token['logoURI']
534
- == 'https://gateway.pinata.cloud/ipfs/QmS4m4cBjukg7YhimqhfRUb2pUE4bBPXbrbMBb5hzxNbUA'
535
- )
536
-
537
-
538
- @pytest.fixture
539
- def metaplex_content():
540
- return (
541
- 'BJ5okXRntUEewuGiuQOIMWt8+YkvFnY7/DoghPyz5qwNo8wWpzohlcKPjWIDCl3nggGst3'
542
- 'CeFvU+P1wA6yNMhm8gAAAAQWN0IEkgOiBUaGUgQUkgUHJvcGhlY3kAAAAAAAAAAAAKAAAA'
543
- 'QUNUAAAAAAAAAMgAAABodHRwczovL2dhdGV3YXkucGluYXRhLmNsb3VkL2lwZnMvUW1WZk'
544
- '1QQ1VQczVWMW9tYUR2b25pMzh3OGFFSnBGVGpFWlJOUm1vVnlleFhXZQAAAAAAAAAAAAAA'
545
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
546
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
547
- 'AAAAAAAAAAAAAf4BAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
548
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
549
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
550
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
551
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
552
- 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
553
- )
554
-
555
-
556
606
  def test_merge_balances_contract_merge(solana_api):
557
607
  balances = [
558
608
  BalanceItem(