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.
- {blockapi-2.5.0 → blockapi-2.5.2}/PKG-INFO +1 -1
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/perpetual/test_perpetual.py +15 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_solana.py +178 -128
- blockapi-2.5.2/blockapi/v2/api/solana.py +446 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/sui.py +11 -6
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/models.py +20 -3
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/PKG-INFO +1 -1
- {blockapi-2.5.0 → blockapi-2.5.2}/setup.py +1 -1
- blockapi-2.5.0/blockapi/v2/api/solana.py +0 -508
- {blockapi-2.5.0 → blockapi-2.5.2}/LICENSE.md +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/README.md +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/services.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_blockapi.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_num.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/test_random_user_agent.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/conftest.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/covalenth/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/covalenth/test_ethereum.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/fake_sleep_provider.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_magic_eden.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_opensea.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_simple_hash.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/nft/test_unisat.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/perpetual/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/synthetix/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/synthetix/test_synthetix.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchain_info.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchainos.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_btc.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_doge.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_blockchair_ltc.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_cosmos.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_ethplorer.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_haskoin.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_multisources.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_optimistic_etherscan.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_subscan_polkadot.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_sui.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_trezor_btc.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/api/test_trezor_zec.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_base.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_blockchain_api.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_blockchain_mapping.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_data.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_enumerate_classes.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_generic.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test/v2/test_models.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/test_data.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/address.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/datetime.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/num.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/utils/user_agent.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchain_info.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchainos.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/blockchair.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/cosmos.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/arbitrum.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/astar.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/avalanche.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/axie.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/base.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/binance_smart_chain.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/ethereum.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/fantom.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/heco.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/iotex.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/klaytn.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/moonbeam.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/palm.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/polygon.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/covalenth/rsk.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/debank.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/debank_maps.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/ethplorer.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/haskoin.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/magic_eden.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/opensea.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/simple_hash.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/nft/unisat.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/optimistic_etherscan.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/perp_abi.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/perpetual/perpetual.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/subscan.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/__init__.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/synthetix.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/synthetix/synthetix_abi.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/terra.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/trezor.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/api/web3_utils.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/base.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/blockchain_mapping.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/coin_mapping.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi/v2/coins.py +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/SOURCES.txt +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/dependency_links.txt +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/requires.txt +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/blockapi.egg-info/top_level.txt +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/pyproject.toml +0 -0
- {blockapi-2.5.0 → blockapi-2.5.2}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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/'
|
|
103
|
+
api = SolanaApi(base_url='https://proxy/solana/')
|
|
111
104
|
api.get_balance(test_addr)
|
|
112
105
|
|
|
113
106
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
312
|
-
return
|
|
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
|
|
337
|
-
return read_file('data/solana/
|
|
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
|
|
342
|
-
return read_file('data/solana/
|
|
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
|
|
347
|
-
return read_file('data/solana/
|
|
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(
|