Skip to content

Commit 822bc90

Browse files
authored
Merge pull request #327 from InjectiveLabs/feat/load_official_tokens_list
Feat/load official tokens list
2 parents 1d35d8d + 355c3a9 commit 822bc90

File tree

10 files changed

+544
-253
lines changed

10 files changed

+544
-253
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
99
- Support for all queries in the "IBC Channel" module
1010
- Support for all queries in the "IBC Client" module
1111
- Support for all queries in the "IBC Connection" module
12+
- Tokens initialization from the official tokens list in https://github.com/InjectiveLabs/injective-lists
1213

1314
### Changed
1415
- Refactored cookies management logic to use all gRPC calls' responses to update the current cookies

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ sudo dnf install python3-devel autoconf automake gcc gcc-c++ libffi-devel libtoo
1616
**macOS**
1717

1818
```bash
19-
brew install autoconf automake libtool
19+
brew install autoconf automake libtool bufbuild/buf/buf
2020
```
2121

2222
### Quick Start
@@ -67,7 +67,7 @@ Upgrade `pip` to the latest version, if you see these warnings:
6767

6868
3. Fetch latest denom config
6969
```
70-
poetry run python pyinjective/fetch_metadata.py
70+
poetry run python pyinjective/utils/fetch_metadata.py
7171
```
7272

7373
Note that the [sync client](https://github.com/InjectiveLabs/sdk-python/blob/master/pyinjective/client.py) has been deprecated as of April 18, 2022. If you are using the sync client please make sure to transition to the [async client](https://github.com/InjectiveLabs/sdk-python/blob/master/pyinjective/async_client.py), for more information read [here](https://github.com/InjectiveLabs/sdk-python/issues/101)

poetry.lock

Lines changed: 274 additions & 245 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyinjective/async_client.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from pyinjective.core.network import Network
4545
from pyinjective.core.tendermint.grpc.tendermint_grpc_api import TendermintGrpcApi
4646
from pyinjective.core.token import Token
47+
from pyinjective.core.tokens_file_loader import TokensFileLoader
4748
from pyinjective.core.tx.grpc.tx_grpc_api import TxGrpcApi
4849
from pyinjective.exceptions import NotFoundError
4950
from pyinjective.proto.cosmos.auth.v1beta1 import query_pb2 as auth_query, query_pb2_grpc as auth_query_grpc
@@ -3264,8 +3265,7 @@ async def _initialize_tokens_and_markets(self):
32643265
spot_markets = dict()
32653266
derivative_markets = dict()
32663267
binary_option_markets = dict()
3267-
tokens_by_symbol = dict()
3268-
tokens_by_denom = dict()
3268+
tokens_by_symbol, tokens_by_denom = await self._tokens_from_official_lists(network=self.network)
32693269
markets_info = (await self.fetch_spot_markets(market_statuses=["active"]))["markets"]
32703270
valid_markets = (
32713271
market_info
@@ -3400,6 +3400,29 @@ def _token_representation(
34003400

34013401
return tokens_by_denom[denom]
34023402

3403+
async def _tokens_from_official_lists(
3404+
self,
3405+
network: Network,
3406+
) -> Tuple[Dict[str, Token], Dict[str, Token]]:
3407+
tokens_by_symbol = dict()
3408+
tokens_by_denom = dict()
3409+
3410+
loader = TokensFileLoader()
3411+
tokens = await loader.load_tokens(network.official_tokens_list_url)
3412+
3413+
for token in tokens:
3414+
if token.denom is not None and token.denom != "" and token.denom not in tokens_by_denom:
3415+
unique_symbol = token.symbol
3416+
for symbol_candidate in [token.symbol, token.name]:
3417+
if symbol_candidate not in tokens_by_symbol:
3418+
unique_symbol = symbol_candidate
3419+
break
3420+
3421+
tokens_by_denom[token.denom] = token
3422+
tokens_by_symbol[unique_symbol] = token
3423+
3424+
return tokens_by_symbol, tokens_by_denom
3425+
34033426
def _initialize_timeout_height_sync_task(self):
34043427
self._cancel_timeout_height_sync_task()
34053428
self._timeout_height_sync_task = asyncio.get_event_loop().create_task(self._timeout_height_sync_process())

pyinjective/core/network.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(
118118
chain_cookie_assistant: CookieAssistant,
119119
exchange_cookie_assistant: CookieAssistant,
120120
explorer_cookie_assistant: CookieAssistant,
121+
official_tokens_list_url: str,
121122
use_secure_connection: Optional[bool] = None,
122123
grpc_channel_credentials: Optional[ChannelCredentials] = None,
123124
grpc_exchange_channel_credentials: Optional[ChannelCredentials] = None,
@@ -144,6 +145,7 @@ def __init__(
144145
self.chain_cookie_assistant = chain_cookie_assistant
145146
self.exchange_cookie_assistant = exchange_cookie_assistant
146147
self.explorer_cookie_assistant = explorer_cookie_assistant
148+
self.official_tokens_list_url = official_tokens_list_url
147149
self.grpc_channel_credentials = grpc_channel_credentials
148150
self.grpc_exchange_channel_credentials = grpc_exchange_channel_credentials
149151
self.grpc_explorer_channel_credentials = grpc_explorer_channel_credentials
@@ -164,6 +166,7 @@ def devnet(cls):
164166
chain_cookie_assistant=DisabledCookieAssistant(),
165167
exchange_cookie_assistant=DisabledCookieAssistant(),
166168
explorer_cookie_assistant=DisabledCookieAssistant(),
169+
official_tokens_list_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/devnet.json",
167170
)
168171

169172
@classmethod
@@ -218,6 +221,7 @@ def testnet(cls, node="lb"):
218221
grpc_exchange_channel_credentials=grpc_exchange_channel_credentials,
219222
grpc_explorer_channel_credentials=grpc_explorer_channel_credentials,
220223
chain_stream_channel_credentials=chain_stream_channel_credentials,
224+
official_tokens_list_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/testnet.json",
221225
)
222226

223227
@classmethod
@@ -259,6 +263,7 @@ def mainnet(cls, node="lb"):
259263
grpc_exchange_channel_credentials=grpc_exchange_channel_credentials,
260264
grpc_explorer_channel_credentials=grpc_explorer_channel_credentials,
261265
chain_stream_channel_credentials=chain_stream_channel_credentials,
266+
official_tokens_list_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json",
262267
)
263268

264269
@classmethod
@@ -276,6 +281,7 @@ def local(cls):
276281
chain_cookie_assistant=DisabledCookieAssistant(),
277282
exchange_cookie_assistant=DisabledCookieAssistant(),
278283
explorer_cookie_assistant=DisabledCookieAssistant(),
284+
official_tokens_list_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json",
279285
)
280286

281287
@classmethod
@@ -289,6 +295,7 @@ def custom(
289295
chain_stream_endpoint,
290296
chain_id,
291297
env,
298+
official_tokens_list_url: str,
292299
chain_cookie_assistant: Optional[CookieAssistant] = None,
293300
exchange_cookie_assistant: Optional[CookieAssistant] = None,
294301
explorer_cookie_assistant: Optional[CookieAssistant] = None,
@@ -322,6 +329,7 @@ def custom(
322329
chain_cookie_assistant=chain_assistant,
323330
exchange_cookie_assistant=exchange_assistant,
324331
explorer_cookie_assistant=explorer_assistant,
332+
official_tokens_list_url=official_tokens_list_url,
325333
grpc_channel_credentials=grpc_channel_credentials,
326334
grpc_exchange_channel_credentials=grpc_exchange_channel_credentials,
327335
grpc_explorer_channel_credentials=grpc_explorer_channel_credentials,
@@ -351,6 +359,7 @@ def custom_chain_and_public_indexer_mainnet(
351359
chain_cookie_assistant=chain_cookie_assistant or DisabledCookieAssistant(),
352360
exchange_cookie_assistant=mainnet_network.exchange_cookie_assistant,
353361
explorer_cookie_assistant=mainnet_network.explorer_cookie_assistant,
362+
official_tokens_list_url=mainnet_network.official_tokens_list_url,
354363
grpc_channel_credentials=None,
355364
grpc_exchange_channel_credentials=mainnet_network.grpc_exchange_channel_credentials,
356365
grpc_explorer_channel_credentials=mainnet_network.grpc_explorer_channel_credentials,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from typing import Dict, List
2+
3+
import aiohttp
4+
5+
from pyinjective.core.token import Token
6+
from pyinjective.utils.logger import LoggerProvider
7+
8+
9+
class TokensFileLoader:
10+
def load_json(self, json: List[Dict]) -> List[Token]:
11+
loaded_tokens = []
12+
13+
for token_info in json:
14+
token = Token(
15+
name=token_info["name"],
16+
symbol=token_info["symbol"],
17+
denom=token_info["denom"],
18+
address=token_info["address"],
19+
decimals=token_info["decimals"],
20+
logo=token_info["logo"],
21+
updated=-1,
22+
)
23+
24+
loaded_tokens.append(token)
25+
26+
return loaded_tokens
27+
28+
async def load_tokens(self, tokens_file_url: str) -> List[Token]:
29+
tokens_list = []
30+
try:
31+
async with aiohttp.ClientSession() as session:
32+
async with session.get(tokens_file_url) as response:
33+
if response.ok:
34+
tokens_list = await response.json(content_type=None)
35+
except Exception as e:
36+
LoggerProvider().logger_for_class(logging_class=self.__class__).warning(
37+
f"there was an error fetching the list of official tokens: {e}"
38+
)
39+
40+
return self.load_json(tokens_list)

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ python = "^3.9"
2525
aiohttp = ">=3.9.2" # Version dependency due to https://github.com/InjectiveLabs/sdk-python/security/dependabot/18
2626
bech32 = "*"
2727
bip32 = "*"
28-
coincurve = "*"
2928
ecdsa = "*"
3029
eip712 = "*"
3130
grpcio = "*"
@@ -35,7 +34,6 @@ mnemonic = "*"
3534
protobuf = "*"
3635
requests = "*"
3736
safe-pysha3 = "*"
38-
urllib3 = "*"
3937
websockets = "*"
4038
web3 = "^6.0"
4139

@@ -45,6 +43,7 @@ pytest-asyncio = "*"
4543
pytest-grpc = "*"
4644
requests-mock = "*"
4745
pytest-cov = "^4.1.0"
46+
pytest-aioresponses = "^0.2.0"
4847

4948
[tool.poetry.group.dev.dependencies]
5049
pre-commit = "^3.4.0"

tests/core/test_network_deprecation_warnings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def test_use_secure_connection_parameter_deprecation_warning(self):
1616
chain_id="chain_id",
1717
fee_denom="fee_denom",
1818
env="env",
19+
official_tokens_list_url="https://tokens.url",
1920
chain_cookie_assistant=DisabledCookieAssistant(),
2021
exchange_cookie_assistant=DisabledCookieAssistant(),
2122
explorer_cookie_assistant=DisabledCookieAssistant(),
@@ -40,6 +41,7 @@ def test_use_secure_connection_parameter_in_custom_network_deprecation_warning(s
4041
chain_stream_endpoint="chain_stream_endpoint",
4142
chain_id="chain_id",
4243
env="env",
44+
official_tokens_list_url="https://tokens.url",
4345
chain_cookie_assistant=DisabledCookieAssistant(),
4446
exchange_cookie_assistant=DisabledCookieAssistant(),
4547
explorer_cookie_assistant=DisabledCookieAssistant(),
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import pytest
2+
3+
from pyinjective.core.tokens_file_loader import TokensFileLoader
4+
5+
6+
class TestTokensFileLoader:
7+
def test_load_tokens(self):
8+
tokens_list = [
9+
{
10+
"address": "",
11+
"isNative": True,
12+
"decimals": 9,
13+
"symbol": "SOL",
14+
"name": "Solana",
15+
"logo": "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/2aa4deed-fa31-4d1a-ba0a-d698b84f3800/public",
16+
"coinGeckoId": "solana",
17+
"denom": "",
18+
"tokenType": "spl",
19+
"tokenVerification": "verified",
20+
"externalLogo": "solana.png",
21+
},
22+
{
23+
"address": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
24+
"isNative": False,
25+
"decimals": 18,
26+
"symbol": "WMATIC",
27+
"logo": "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/0d061e1e-a746-4b19-1399-8187b8bb1700/public",
28+
"name": "Wrapped Matic",
29+
"coinGeckoId": "wmatic",
30+
"denom": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
31+
"tokenType": "evm",
32+
"tokenVerification": "verified",
33+
"externalLogo": "polygon.png",
34+
},
35+
]
36+
37+
loader = TokensFileLoader()
38+
39+
loaded_tokens = loader.load_json(json=tokens_list)
40+
41+
assert len(loaded_tokens) == 2
42+
43+
for token, token_info in zip(loaded_tokens, tokens_list):
44+
assert token.name == token_info["name"]
45+
assert token.symbol == token_info["symbol"]
46+
assert token.denom == token_info["denom"]
47+
assert token.address == token_info["address"]
48+
assert token.decimals == token_info["decimals"]
49+
assert token.logo == token_info["logo"]
50+
51+
@pytest.mark.asyncio
52+
async def test_load_tokens_from_url(self, aioresponses):
53+
loader = TokensFileLoader()
54+
tokens_list = [
55+
{
56+
"address": "",
57+
"isNative": True,
58+
"decimals": 9,
59+
"symbol": "SOL",
60+
"name": "Solana",
61+
"logo": "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/2aa4deed-fa31-4d1a-ba0a-d698b84f3800/public",
62+
"coinGeckoId": "solana",
63+
"denom": "",
64+
"tokenType": "spl",
65+
"tokenVerification": "verified",
66+
"externalLogo": "solana.png",
67+
},
68+
{
69+
"address": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
70+
"isNative": False,
71+
"decimals": 18,
72+
"symbol": "WMATIC",
73+
"logo": "https://imagedelivery.net/DYKOWp0iCc0sIkF-2e4dNw/0d061e1e-a746-4b19-1399-8187b8bb1700/public",
74+
"name": "Wrapped Matic",
75+
"coinGeckoId": "wmatic",
76+
"denom": "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270",
77+
"tokenType": "evm",
78+
"tokenVerification": "verified",
79+
"externalLogo": "polygon.png",
80+
},
81+
]
82+
83+
aioresponses.get(
84+
"https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json", payload=tokens_list
85+
)
86+
loaded_tokens = await loader.load_tokens(
87+
tokens_file_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json"
88+
)
89+
90+
assert len(loaded_tokens) == 2
91+
92+
for token, token_info in zip(loaded_tokens, tokens_list):
93+
assert token.name == token_info["name"]
94+
assert token.symbol == token_info["symbol"]
95+
assert token.denom == token_info["denom"]
96+
assert token.address == token_info["address"]
97+
assert token.decimals == token_info["decimals"]
98+
assert token.logo == token_info["logo"]
99+
100+
@pytest.mark.asyncio
101+
async def test_load_tokens_from_url_returns_nothing_when_request_fails(self, aioresponses):
102+
loader = TokensFileLoader()
103+
104+
aioresponses.get(
105+
"https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json",
106+
status=404,
107+
)
108+
loaded_tokens = await loader.load_tokens(
109+
tokens_file_url="https://github.com/InjectiveLabs/injective-lists/raw/master/tokens/mainnet.json"
110+
)
111+
112+
assert len(loaded_tokens) == 0

0 commit comments

Comments
 (0)