strideutils 0.2.11__tar.gz → 0.3.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.
- {strideutils-0.2.11 → strideutils-0.3.0}/PKG-INFO +1 -1
- {strideutils-0.2.11 → strideutils-0.3.0}/pyproject.toml +1 -1
- strideutils-0.3.0/strideutils/coingecko.py +147 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/config_examples/strideutils_config.yaml.example +24 -12
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/invariant_helpers.py +1 -1
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/run_safely.py +1 -0
- strideutils-0.3.0/strideutils/stride_config.py +367 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/stride_requests.py +37 -97
- strideutils-0.2.11/strideutils/coingecko.py +0 -35
- strideutils-0.2.11/strideutils/stride_config.py +0 -301
- {strideutils-0.2.11 → strideutils-0.3.0}/README.md +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/Makefile +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/__init__.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/config_examples/strideutils_secrets.yaml.example +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/cryptography.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/exec_tx.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/sheets_connector.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/slack_connector.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/stride_alerts.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/stride_upstash.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils/twilio_connector.py +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/__init__.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/coingecko.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/cryptography.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/exec_tx.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/invariant_helpers.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/run_safely.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/sheets_connector.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/slack_connector.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/stride_alerts.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/stride_config.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/stride_requests.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/stride_upstash.pyi +0 -0
- {strideutils-0.2.11 → strideutils-0.3.0}/strideutils-stubs/twilio_connector.pyi +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exposes an easy API to get prices from coingecko
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from strideutils import stride_config, stride_requests
|
|
11
|
+
from strideutils.stride_config import config
|
|
12
|
+
|
|
13
|
+
COINGECKO_ENDPOINT = "https://pro-api.coingecko.com/"
|
|
14
|
+
COINGECKO_PRICE_QUERY = "api/v3/simple/price?ids={ticker_id}&vs_currencies=usd"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_coingecko_name(chain_config: stride_config.ChainConfig) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Returns the coingecko name for a given chain
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
return chain_config.coingecko_name
|
|
23
|
+
except AttributeError:
|
|
24
|
+
return chain_config.name
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_token_price(
|
|
28
|
+
ticker: str,
|
|
29
|
+
api_token: str = config.COINGECKO_API_TOKEN,
|
|
30
|
+
cache_response: bool = False,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Reads token price from coingecko
|
|
34
|
+
If a redis_client is provided, then store the value.
|
|
35
|
+
"""
|
|
36
|
+
# TODO: Consider using the coingecko ID for stTokens instead of the redemption rate
|
|
37
|
+
# Get redemption rate for calculating st token prices
|
|
38
|
+
redemption_rate = float(1)
|
|
39
|
+
if ticker.startswith('st') and ticker[3].isupper():
|
|
40
|
+
redemption_rate = stride_requests.get_redemption_rate(ticker[2:])
|
|
41
|
+
ticker = ticker[2:]
|
|
42
|
+
# get coingecko_name
|
|
43
|
+
try:
|
|
44
|
+
coingecko_name = get_coingecko_name(config.get_chain(ticker=ticker))
|
|
45
|
+
except KeyError:
|
|
46
|
+
coingecko_name = ticker
|
|
47
|
+
# query endopint
|
|
48
|
+
endpoint = COINGECKO_ENDPOINT + COINGECKO_PRICE_QUERY.format(ticker_id=coingecko_name)
|
|
49
|
+
headers = {'x-cg-pro-api-key': api_token}
|
|
50
|
+
response = stride_requests.request(endpoint, headers=headers, cache_response=cache_response)
|
|
51
|
+
price = response[coingecko_name]["usd"] * redemption_rate
|
|
52
|
+
|
|
53
|
+
return price
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_token_price_history(token: str, num_days=90, use_key=False, memo={}) -> pd.Series:
|
|
57
|
+
"""
|
|
58
|
+
returns a Pandas Series with token price data going back num_days
|
|
59
|
+
e.g. get_token_price_history('uatom', 30) returns a Series with 30 days of ATOM price data
|
|
60
|
+
TODO: Figure out why API key is throwing an error when used.
|
|
61
|
+
"""
|
|
62
|
+
price_key = (token, num_days)
|
|
63
|
+
if price_key not in memo:
|
|
64
|
+
tstr = get_coingecko_name(config.get_chain(ticker=token))
|
|
65
|
+
rurl = f"https://api.coingecko.com/api/v3/coins/{tstr}/market_chart?vs_currency=usd&days={num_days}"
|
|
66
|
+
rel_headers = {"x-cg-pro-api-key": config.COINGECKO_API_TOKEN}
|
|
67
|
+
if not use_key:
|
|
68
|
+
rel_headers = None
|
|
69
|
+
r = requests.get(
|
|
70
|
+
rurl,
|
|
71
|
+
headers=rel_headers,
|
|
72
|
+
)
|
|
73
|
+
assert r.status_code == 200
|
|
74
|
+
out_df = pd.DataFrame(r.json()["prices"])
|
|
75
|
+
out_df.columns = ["time", "price"]
|
|
76
|
+
out_df["time"] = pd.to_datetime(out_df["time"], unit="ms")
|
|
77
|
+
out_df["price"] = out_df["price"].astype(float)
|
|
78
|
+
out_df.set_index("time", inplace=True)
|
|
79
|
+
memo[token] = out_df
|
|
80
|
+
return memo[token]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_token_volume_history(token: str, num_days=90, use_key=False, memo={}) -> pd.Series:
|
|
84
|
+
"""
|
|
85
|
+
returns a Pandas Series with token volume data going back num_days
|
|
86
|
+
e.g. get_token_volume_history('uatom', 30) returns a Series with 30 days of ATOM volume data
|
|
87
|
+
TODO: Figure out why API key is throwing an error when used.
|
|
88
|
+
"""
|
|
89
|
+
volume_key = (token, num_days)
|
|
90
|
+
if volume_key not in memo:
|
|
91
|
+
tstr = get_coingecko_name(config.get_chain(ticker=token))
|
|
92
|
+
rurl = f"https://api.coingecko.com/api/v3/coins/{tstr}/market_chart?vs_currency=usd&days={num_days}"
|
|
93
|
+
rel_headers = {"x-cg-pro-api-key": config.COINGECKO_API_TOKEN}
|
|
94
|
+
if not use_key:
|
|
95
|
+
rel_headers = None
|
|
96
|
+
r = requests.get(
|
|
97
|
+
rurl,
|
|
98
|
+
headers=rel_headers,
|
|
99
|
+
)
|
|
100
|
+
assert r.status_code == 200
|
|
101
|
+
out_df = pd.DataFrame(r.json()["total_volumes"])
|
|
102
|
+
out_df.columns = ["time", "volume"]
|
|
103
|
+
out_df["time"] = pd.to_datetime(out_df["time"], unit="ms")
|
|
104
|
+
out_df["volume"] = out_df["volume"].astype(float)
|
|
105
|
+
out_df.set_index("time", inplace=True)
|
|
106
|
+
memo[token] = out_df
|
|
107
|
+
return memo[token]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_dataframe_of_prices(token_list: List[str], num_days: int = 90) -> pd.DataFrame:
|
|
111
|
+
"""
|
|
112
|
+
returns a Pandas DataFrame with token price data going back num_days, for the given tokens
|
|
113
|
+
e.g. get_dataframe_of_prices(["ATOM", "OSMO", "STRD"], 30) returns a DataFrame of 30 days of price data
|
|
114
|
+
"""
|
|
115
|
+
# gather the dataframe
|
|
116
|
+
df = {}
|
|
117
|
+
for token in token_list:
|
|
118
|
+
df[token] = get_token_price_history(token, num_days)["price"]
|
|
119
|
+
df = pd.DataFrame(df)
|
|
120
|
+
# resample based on how much data we're gathering
|
|
121
|
+
if num_days > 90:
|
|
122
|
+
df = df.resample("1D").last()
|
|
123
|
+
elif num_days >= 2:
|
|
124
|
+
df = df.resample("1H").last()
|
|
125
|
+
else:
|
|
126
|
+
df = df.resample("5min").last()
|
|
127
|
+
return df
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_dataframe_of_volumes(token_list: List[str], num_days: int = 90) -> pd.DataFrame:
|
|
131
|
+
"""
|
|
132
|
+
returns a Pandas DataFrame with token volume data going back num_days, for the given tokens
|
|
133
|
+
e.g. get_dataframe_of_volumes(["ATOM", "OSMO", "STRD"], 30) returns a DataFrame of 30 days of volume data
|
|
134
|
+
"""
|
|
135
|
+
# gather the dataframe
|
|
136
|
+
df = {}
|
|
137
|
+
for token in token_list:
|
|
138
|
+
df[token] = get_token_volume_history(token, num_days)["volume"]
|
|
139
|
+
df = pd.DataFrame(df)
|
|
140
|
+
# resample based on how much data we're gathering
|
|
141
|
+
if num_days > 90:
|
|
142
|
+
df = df.resample("1D").last()
|
|
143
|
+
elif num_days >= 2:
|
|
144
|
+
df = df.resample("1H").last()
|
|
145
|
+
else:
|
|
146
|
+
df = df.resample("5min").last()
|
|
147
|
+
return df
|
{strideutils-0.2.11 → strideutils-0.3.0}/strideutils/config_examples/strideutils_config.yaml.example
RENAMED
|
@@ -25,8 +25,8 @@ stride:
|
|
|
25
25
|
denom_decimals: 6
|
|
26
26
|
api_endpoint:
|
|
27
27
|
rpc_endpoint:
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
library_api_endpoint:
|
|
29
|
+
library_rpc_endpoint:
|
|
30
30
|
|
|
31
31
|
# Host Zone Chains
|
|
32
32
|
host_zones:
|
|
@@ -37,8 +37,8 @@ host_zones:
|
|
|
37
37
|
denom: uatom
|
|
38
38
|
ticker: ATOM
|
|
39
39
|
denom_decimals: 6
|
|
40
|
+
denom_on_stride: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
|
|
40
41
|
inflation_frequency_hours: -1 # Every block
|
|
41
|
-
stride_token_hash: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2
|
|
42
42
|
api_endpoint:
|
|
43
43
|
rpc_endpoint:
|
|
44
44
|
|
|
@@ -48,8 +48,8 @@ host_zones:
|
|
|
48
48
|
denom: uosmo
|
|
49
49
|
ticker: OSMO
|
|
50
50
|
denom_decimals: 6
|
|
51
|
+
denom_on_stride: ibc/D24B4564BCD51D3D02D9987D92571EAC5915676A9BD6D9B0C1D0254CB8A5EA34
|
|
51
52
|
inflation_frequency_hours: 24
|
|
52
|
-
stride_token_hash: ibc/D24B4564BCD51D3D02D9987D92571EAC5915676A9BD6D9B0C1D0254CB8A5EA34
|
|
53
53
|
api_endpoint:
|
|
54
54
|
rpc_endpoint:
|
|
55
55
|
|
|
@@ -59,8 +59,8 @@ host_zones:
|
|
|
59
59
|
denom: aevmos
|
|
60
60
|
ticker: EVMOS
|
|
61
61
|
denom_decimals: 18
|
|
62
|
+
denom_on_stride: ibc/4B322204B4F59D770680FE4D7A565DDC3F37BFF035474B717476C66A4F83DD72
|
|
62
63
|
inflation_frequency_hours: 24
|
|
63
|
-
stride_token_hash: ibc/4B322204B4F59D770680FE4D7A565DDC3F37BFF035474B717476C66A4F83DD72
|
|
64
64
|
api_endpoint:
|
|
65
65
|
rpc_endpoint:
|
|
66
66
|
|
|
@@ -71,8 +71,8 @@ host_zones:
|
|
|
71
71
|
denom: inj
|
|
72
72
|
ticker: INJ
|
|
73
73
|
denom_decimals: 18
|
|
74
|
+
denom_on_stride: ibc/A7454562FF29FE068F42F9DE4805ABEF54F599D1720B345D6518D9B5C64EA6D2
|
|
74
75
|
inflation_frequency_hours: -1 # Every block
|
|
75
|
-
stride_token_hash: ibc/A7454562FF29FE068F42F9DE4805ABEF54F599D1720B345D6518D9B5C64EA6D2
|
|
76
76
|
api_endpoint:
|
|
77
77
|
rpc_endpoint:
|
|
78
78
|
|
|
@@ -83,8 +83,8 @@ host_zones:
|
|
|
83
83
|
coingecko_name: juno-network
|
|
84
84
|
ticker: JUNO
|
|
85
85
|
denom_decimals: 6
|
|
86
|
+
denom_on_stride: ibc/DA356E369C3E5CF6A9F1DCD99CE8ED55FBD595E676A5CF033CE784C060492D5A
|
|
86
87
|
inflation_frequency_hours: -1 # Every block
|
|
87
|
-
stride_token_hash: ibc/DA356E369C3E5CF6A9F1DCD99CE8ED55FBD595E676A5CF033CE784C060492D5A
|
|
88
88
|
api_endpoint:
|
|
89
89
|
rpc_endpoint:
|
|
90
90
|
|
|
@@ -94,8 +94,8 @@ host_zones:
|
|
|
94
94
|
denom: ustars
|
|
95
95
|
ticker: STARS
|
|
96
96
|
denom_decimals: 6
|
|
97
|
+
denom_on_stride: ibc/7EAE5BEF3A26B64AFBD89828AFDDB1DC7024A0276D22745201632C40E6E634D0
|
|
97
98
|
inflation_frequency_hours: -1 # Every block
|
|
98
|
-
stride_token_hash: ibc/7EAE5BEF3A26B64AFBD89828AFDDB1DC7024A0276D22745201632C40E6E634D0
|
|
99
99
|
api_endpoint:
|
|
100
100
|
rpc_endpoint:
|
|
101
101
|
|
|
@@ -106,8 +106,8 @@ host_zones:
|
|
|
106
106
|
denom: uluna
|
|
107
107
|
ticker: LUNA
|
|
108
108
|
denom_decimals: 6
|
|
109
|
+
denom_on_stride: ibc/E61BCB1126F42A2ED73B4CEA2221C9635BC2102F0417543C38071779F991942E
|
|
109
110
|
inflation_frequency_hours: -1 # Every block
|
|
110
|
-
stride_token_hash: ibc/E61BCB1126F42A2ED73B4CEA2221C9635BC2102F0417543C38071779F991942E
|
|
111
111
|
api_endpoint:
|
|
112
112
|
rpc_endpoint:
|
|
113
113
|
|
|
@@ -117,8 +117,8 @@ host_zones:
|
|
|
117
117
|
denom: uumee
|
|
118
118
|
ticker: UMEE
|
|
119
119
|
denom_decimals: 6
|
|
120
|
+
denom_on_stride: ibc/1A2271226209D309902AFF4F21BD21237CB514DD24EA2EE0423BF74C6135D8B8
|
|
120
121
|
inflation_frequency_hours: -1 # Every block
|
|
121
|
-
stride_token_hash: ibc/1A2271226209D309902AFF4F21BD21237CB514DD24EA2EE0423BF74C6135D8B8
|
|
122
122
|
api_endpoint:
|
|
123
123
|
rpc_endpoint:
|
|
124
124
|
|
|
@@ -128,8 +128,8 @@ host_zones:
|
|
|
128
128
|
denom: ucmdx
|
|
129
129
|
ticker: CMDX
|
|
130
130
|
denom_decimals: 6
|
|
131
|
+
denom_on_stride: ibc/EB66980014602E6BD50A1CB9FFB8FA694DC3EC10A48D2C1C649D732954F88D4A
|
|
131
132
|
inflation_frequency_hours: -1 # Every block
|
|
132
|
-
stride_token_hash: ibc/EB66980014602E6BD50A1CB9FFB8FA694DC3EC10A48D2C1C649D732954F88D4A
|
|
133
133
|
api_endpoint:
|
|
134
134
|
rpc_endpoint:
|
|
135
135
|
|
|
@@ -139,11 +139,23 @@ host_zones:
|
|
|
139
139
|
denom: usomm
|
|
140
140
|
ticker: SOMM
|
|
141
141
|
denom_decimals: 6
|
|
142
|
+
denom_on_stride: ibc/B86EFF6D227E3E33D7E3B5E65D0C1BB5BD79CCB56D35A9D824F0DD5D52CA43BA
|
|
142
143
|
inflation_frequency_hours: -1 # Every block
|
|
143
|
-
stride_token_hash: ibc/B86EFF6D227E3E33D7E3B5E65D0C1BB5BD79CCB56D35A9D824F0DD5D52CA43BA
|
|
144
144
|
api_endpoint:
|
|
145
145
|
rpc_endpoint:
|
|
146
146
|
|
|
147
|
+
celestia:
|
|
148
|
+
name: celestia
|
|
149
|
+
id: celestia
|
|
150
|
+
coingecko_name: celestia
|
|
151
|
+
denom: utia
|
|
152
|
+
ticker: TIA
|
|
153
|
+
denom_on_stride: ibc/BF3B4F53F3694B66E13C23107C84B6485BD2B96296BB7EC680EA77BBA75B4801
|
|
154
|
+
inflation_frequency_hours: -1 # Every block
|
|
155
|
+
api_endpoint: https://celestia-strd-api.polkachu.com
|
|
156
|
+
rpc_endpoint: https://celestia-strd-rpc.polkachu.com
|
|
157
|
+
ica_enabled: false
|
|
158
|
+
|
|
147
159
|
# Other endpoints
|
|
148
160
|
app_chains:
|
|
149
161
|
neutron:
|
|
@@ -16,7 +16,7 @@ def check_slack_on_mainnet(str_output: bool = True, verbose: bool = False) -> Un
|
|
|
16
16
|
output = ""
|
|
17
17
|
for zone in config.host_zones:
|
|
18
18
|
host_zone_id = zone.id
|
|
19
|
-
host_zone = stride_requests.
|
|
19
|
+
host_zone = stride_requests.get_host_zone(host_zone_id)
|
|
20
20
|
|
|
21
21
|
rr = host_zone['redemption_rate']
|
|
22
22
|
min_rr, max_rr = host_zone['min_redemption_rate'], host_zone['max_redemption_rate']
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file loads and provide an object `config` to access all secrets, configs, and chain info
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import itertools
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass, field, fields
|
|
8
|
+
from typing import Any, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
import yaml
|
|
11
|
+
|
|
12
|
+
MISSING_CONFIG_MSG = "Configuration or environment variable '{x}' is not set or unknown"
|
|
13
|
+
|
|
14
|
+
ENV = os.environ.get("ENV")
|
|
15
|
+
if ENV not in ["DEV", "PROD"]:
|
|
16
|
+
raise ValueError("Environment variable 'ENV' must be set to either DEV or PROD")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ConfigObj:
|
|
21
|
+
"""Raise an error if a config is not set."""
|
|
22
|
+
|
|
23
|
+
def __getattribute__(self, name):
|
|
24
|
+
"""
|
|
25
|
+
Called every time a field is attempted to be accessed
|
|
26
|
+
Falls back to getattr if the field is not found
|
|
27
|
+
"""
|
|
28
|
+
value = super().__getattribute__(name)
|
|
29
|
+
if value == "" or value is None:
|
|
30
|
+
raise AttributeError(MISSING_CONFIG_MSG.format(x=name))
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
def __iter__(self):
|
|
34
|
+
"""Allow iterating over set values"""
|
|
35
|
+
for subfield in fields(self):
|
|
36
|
+
if hasattr(self, subfield.name):
|
|
37
|
+
yield getattr(self, subfield.name)
|
|
38
|
+
|
|
39
|
+
def __contains__(self, field) -> bool:
|
|
40
|
+
"""Allows for checking if an attribute is present with `in`"""
|
|
41
|
+
if not hasattr(self, field):
|
|
42
|
+
return False
|
|
43
|
+
return bool(getattr(self, field))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ConfigDict(dict):
|
|
47
|
+
def __getitem__(self, key):
|
|
48
|
+
"""Raise an error if an unset key is indexed."""
|
|
49
|
+
if key not in self:
|
|
50
|
+
raise KeyError(MISSING_CONFIG_MSG.format(x=key))
|
|
51
|
+
value = super().__getitem__(key)
|
|
52
|
+
if value == "" or value is None:
|
|
53
|
+
raise KeyError(MISSING_CONFIG_MSG.format(x=key))
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Use ConfigDict in the yaml parser
|
|
58
|
+
class Loader(yaml.FullLoader):
|
|
59
|
+
def construct_yaml_map(self, node):
|
|
60
|
+
data = ConfigDict()
|
|
61
|
+
yield data
|
|
62
|
+
value = self.construct_mapping(node)
|
|
63
|
+
data.update(value)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Loader.add_constructor('tag:yaml.org,2002:map', Loader.construct_yaml_map)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(repr=False)
|
|
70
|
+
class ChainConfig(ConfigObj):
|
|
71
|
+
"""Config relevant for all chains"""
|
|
72
|
+
|
|
73
|
+
name: str = ""
|
|
74
|
+
id: str = ""
|
|
75
|
+
coingecko_name: str = ""
|
|
76
|
+
denom: str = ""
|
|
77
|
+
denom_decimals: int = 6
|
|
78
|
+
ticker: str = ""
|
|
79
|
+
api_endpoint: str = ""
|
|
80
|
+
rpc_endpoint: str = ""
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return f"ChainConfig(name={self.name}, id={self.id})"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(repr=False)
|
|
87
|
+
class StrideChainConfig(ChainConfig):
|
|
88
|
+
"""Config specific to stride"""
|
|
89
|
+
|
|
90
|
+
library_api_endpoint: str = ""
|
|
91
|
+
library_rpc_endpoint: str = ""
|
|
92
|
+
|
|
93
|
+
def __repr__(self) -> str:
|
|
94
|
+
return f"StrideChainConfig(name={self.name}, id={self.id})"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(repr=False)
|
|
98
|
+
class HostChainConfig(ChainConfig):
|
|
99
|
+
"""Config specific to stride's host zones"""
|
|
100
|
+
|
|
101
|
+
# Frequency with which staking rewards are issued
|
|
102
|
+
# -1 indicates every block
|
|
103
|
+
inflation_frequency_hours: int = -1
|
|
104
|
+
# IBC denom of the native token as it sits on stride
|
|
105
|
+
denom_on_stride: str = ""
|
|
106
|
+
# Indicates whether the chain has ICA enabled
|
|
107
|
+
ica_enabled: bool = True
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str:
|
|
110
|
+
return f"HostChainConfig(name={self.name}, id={self.id})"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(repr=False)
|
|
114
|
+
class HostChainsConfig(ConfigObj):
|
|
115
|
+
cosmoshub: HostChainConfig = field(default_factory=HostChainConfig)
|
|
116
|
+
osmosis: HostChainConfig = field(default_factory=HostChainConfig)
|
|
117
|
+
evmos: HostChainConfig = field(default_factory=HostChainConfig)
|
|
118
|
+
injective: HostChainConfig = field(default_factory=HostChainConfig)
|
|
119
|
+
juno: HostChainConfig = field(default_factory=HostChainConfig)
|
|
120
|
+
stargaze: HostChainConfig = field(default_factory=HostChainConfig)
|
|
121
|
+
terra: HostChainConfig = field(default_factory=HostChainConfig)
|
|
122
|
+
umee: HostChainConfig = field(default_factory=HostChainConfig)
|
|
123
|
+
comdex: HostChainConfig = field(default_factory=HostChainConfig)
|
|
124
|
+
sommelier: HostChainConfig = field(default_factory=HostChainConfig)
|
|
125
|
+
dydx: HostChainConfig = field(default_factory=HostChainConfig)
|
|
126
|
+
saga: HostChainConfig = field(default_factory=HostChainConfig)
|
|
127
|
+
celestia: HostChainConfig = field(default_factory=HostChainConfig)
|
|
128
|
+
dymension: HostChainConfig = field(default_factory=HostChainConfig)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(repr=False)
|
|
132
|
+
class AppChainsConfig(ConfigObj):
|
|
133
|
+
neutron: ChainConfig = field(default_factory=ChainConfig)
|
|
134
|
+
kava: ChainConfig = field(default_factory=ChainConfig)
|
|
135
|
+
iris: ChainConfig = field(default_factory=ChainConfig)
|
|
136
|
+
akash: ChainConfig = field(default_factory=ChainConfig)
|
|
137
|
+
sentinel: ChainConfig = field(default_factory=ChainConfig)
|
|
138
|
+
persistence: ChainConfig = field(default_factory=ChainConfig)
|
|
139
|
+
cryptoorg: ChainConfig = field(default_factory=ChainConfig)
|
|
140
|
+
kichain: ChainConfig = field(default_factory=ChainConfig)
|
|
141
|
+
bitcanna: ChainConfig = field(default_factory=ChainConfig)
|
|
142
|
+
regen: ChainConfig = field(default_factory=ChainConfig)
|
|
143
|
+
gravity: ChainConfig = field(default_factory=ChainConfig)
|
|
144
|
+
desmos: ChainConfig = field(default_factory=ChainConfig)
|
|
145
|
+
chihuahua: ChainConfig = field(default_factory=ChainConfig)
|
|
146
|
+
secret: ChainConfig = field(default_factory=ChainConfig)
|
|
147
|
+
axelar: ChainConfig = field(default_factory=ChainConfig)
|
|
148
|
+
crescent: ChainConfig = field(default_factory=ChainConfig)
|
|
149
|
+
mantle: ChainConfig = field(default_factory=ChainConfig)
|
|
150
|
+
mars: ChainConfig = field(default_factory=ChainConfig)
|
|
151
|
+
canto: ChainConfig = field(default_factory=ChainConfig)
|
|
152
|
+
agoric: ChainConfig = field(default_factory=ChainConfig)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(repr=False)
|
|
156
|
+
class Config(ConfigObj):
|
|
157
|
+
ENV: str
|
|
158
|
+
timezone: str = "US/Eastern"
|
|
159
|
+
|
|
160
|
+
# Twilio
|
|
161
|
+
TWILIO_ALERTS_NUMBER: str = ""
|
|
162
|
+
TWILIO_ACCOUNT_ID: str = ""
|
|
163
|
+
TWILIO_API_TOKEN: str = ""
|
|
164
|
+
PHONE_NUMBERS: ConfigDict = field(default_factory=ConfigDict)
|
|
165
|
+
|
|
166
|
+
# Slack
|
|
167
|
+
STRIDEBOT_API_TOKEN: str = ""
|
|
168
|
+
|
|
169
|
+
# Gsheets - loads from str env var in PROD
|
|
170
|
+
PUBLICSHEETS_AUTH: Union[str, ConfigDict] = field(default_factory=ConfigDict)
|
|
171
|
+
|
|
172
|
+
# Coingecko
|
|
173
|
+
COINGECKO_API_TOKEN: str = ""
|
|
174
|
+
|
|
175
|
+
# Protocol Staking (dydx yield)
|
|
176
|
+
PROTOCOL_STAKING_API_TOKEN: str = ""
|
|
177
|
+
|
|
178
|
+
# Stride alerts
|
|
179
|
+
alerts_playbook: str = ""
|
|
180
|
+
slack_channels: ConfigDict = field(default_factory=ConfigDict)
|
|
181
|
+
|
|
182
|
+
# Upstash
|
|
183
|
+
UPSTASH_PUBLIC_PASSWORD: str = ""
|
|
184
|
+
UPSTASH_STRIDE_FRONTEND_PASSWORD: str = ""
|
|
185
|
+
UPSTASH_STRIDE_BACKEND_PASSWORD: str = ""
|
|
186
|
+
UPSTASH_STRIDE_DYDX_PUBLIC_PASSWORD: str = ""
|
|
187
|
+
|
|
188
|
+
# Stride internal secrets
|
|
189
|
+
SLACK_BEARER_TOKEN: str = ""
|
|
190
|
+
SLACK_SUCCESS_CHANNEL_ID: str = ""
|
|
191
|
+
SLACK_INVARIANT_FAILURE_CHANNEL_ID: str = ""
|
|
192
|
+
SLACK_PACKET_FAILURE_CHANNEL_ID: str = ""
|
|
193
|
+
NUMIA_API_TOKEN: str = ""
|
|
194
|
+
founders: List[str] = field(default_factory=lambda: ['riley', 'aidan', 'vishal'])
|
|
195
|
+
|
|
196
|
+
# Chain configs
|
|
197
|
+
stride: StrideChainConfig = field(default_factory=StrideChainConfig)
|
|
198
|
+
host_zones: HostChainsConfig = field(default_factory=HostChainsConfig)
|
|
199
|
+
app_chains: AppChainsConfig = field(default_factory=AppChainsConfig)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def stakeibc_host_zones(self) -> List[HostChainConfig]:
|
|
203
|
+
return [host_zone for host_zone in self.host_zones if host_zone.ica_enabled]
|
|
204
|
+
|
|
205
|
+
def _create_mappings(self) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Create a reverse map from attributes to ChainConfig if id, ticker, and denom are set.
|
|
208
|
+
"""
|
|
209
|
+
name_to_zone = ConfigDict()
|
|
210
|
+
id_to_zone = ConfigDict()
|
|
211
|
+
ticker_to_zone = ConfigDict()
|
|
212
|
+
denom_to_zone = ConfigDict()
|
|
213
|
+
denom_on_stride_to_zone = ConfigDict()
|
|
214
|
+
|
|
215
|
+
# Map host zones
|
|
216
|
+
for chain_info in itertools.chain(self.host_zones, self.app_chains):
|
|
217
|
+
try:
|
|
218
|
+
name_to_zone[chain_info.name] = chain_info
|
|
219
|
+
except AttributeError:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
id_to_zone[chain_info.id] = chain_info
|
|
224
|
+
except AttributeError:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
ticker_to_zone[chain_info.ticker] = chain_info
|
|
229
|
+
ticker_to_zone[chain_info.ticker.upper()] = chain_info
|
|
230
|
+
ticker_to_zone[chain_info.ticker.lower()] = chain_info
|
|
231
|
+
ticker_to_zone[chain_info.ticker.denom[1].upper()] = chain_info
|
|
232
|
+
except AttributeError:
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
denom_to_zone[chain_info.denom] = chain_info
|
|
237
|
+
except AttributeError:
|
|
238
|
+
pass
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
denom_on_stride_to_zone[chain_info.denom_on_stride] = chain_info
|
|
242
|
+
except AttributeError:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
self._name_to_zone = name_to_zone
|
|
246
|
+
self._id_to_zone = id_to_zone
|
|
247
|
+
self._ticker_to_zone = ticker_to_zone
|
|
248
|
+
self._denom_to_zone = denom_to_zone
|
|
249
|
+
self._denom_on_stride_to_zone = denom_on_stride_to_zone
|
|
250
|
+
|
|
251
|
+
def __post_init__(self):
|
|
252
|
+
self._create_mappings()
|
|
253
|
+
|
|
254
|
+
def get_chain(
|
|
255
|
+
self,
|
|
256
|
+
name: Optional[str] = None,
|
|
257
|
+
id: Optional[str] = None,
|
|
258
|
+
ticker: Optional[str] = None,
|
|
259
|
+
denom: Optional[str] = None,
|
|
260
|
+
denom_on_stride: Optional[str] = None,
|
|
261
|
+
) -> Union[ChainConfig, StrideChainConfig, HostChainConfig]:
|
|
262
|
+
"""
|
|
263
|
+
Fetch info about a host zone by name, id, ticker, denom, or token hash.
|
|
264
|
+
|
|
265
|
+
Raises
|
|
266
|
+
KeyError if a valid chain doesn't exist
|
|
267
|
+
"""
|
|
268
|
+
if sum(map(bool, [name, id, ticker, denom, denom_on_stride])) != 1:
|
|
269
|
+
raise KeyError("Exactly one of name, id, ticker, denom, or token hash must be specified.")
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
name == self.stride.name
|
|
273
|
+
or id == self.stride.id
|
|
274
|
+
or ticker == self.stride.ticker
|
|
275
|
+
or denom == self.stride.denom
|
|
276
|
+
):
|
|
277
|
+
return self.stride
|
|
278
|
+
elif name:
|
|
279
|
+
return self._name_to_zone[name]
|
|
280
|
+
elif id:
|
|
281
|
+
return self._id_to_zone[id]
|
|
282
|
+
elif ticker:
|
|
283
|
+
return self._ticker_to_zone[ticker]
|
|
284
|
+
elif denom:
|
|
285
|
+
return self._denom_to_zone[denom]
|
|
286
|
+
return self._denom_on_stride_to_zone[denom_on_stride]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _load_raw_config() -> dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Loads the raw config yaml into a dictionary
|
|
292
|
+
If STRIDEUTILS_CONFIG_PATH is set, it uses that for the config path, otherwise, it
|
|
293
|
+
looks for a config/config.yaml
|
|
294
|
+
"""
|
|
295
|
+
strideutils_config_path = os.environ.get('STRIDEUTILS_CONFIG_PATH', 'config/config.yaml')
|
|
296
|
+
if not os.path.exists(strideutils_config_path):
|
|
297
|
+
raise ValueError(
|
|
298
|
+
'Shell var STRIDEUTILS_CONFIG_PATH is missing or the file does not exist in the default location.'
|
|
299
|
+
'The default file and location is config/config.yaml in the current working directory'
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
with open(strideutils_config_path, 'r') as config_file:
|
|
303
|
+
raw_config = yaml.load(config_file, Loader)
|
|
304
|
+
|
|
305
|
+
return raw_config
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _load_raw_secrets() -> dict[str, Optional[str]]:
|
|
309
|
+
"""
|
|
310
|
+
Loads the secret variables into a dictionary
|
|
311
|
+
If this is running locally in dev mode, secrets are grabbed from the path defined by STRIDEUTILS_SECRETS_PATH
|
|
312
|
+
Otherwise, their loaded as environment variables
|
|
313
|
+
"""
|
|
314
|
+
if ENV == "PROD":
|
|
315
|
+
return {secret.name: os.environ.get(secret.name) for secret in fields(Config) if os.environ.get(secret.name)}
|
|
316
|
+
|
|
317
|
+
if not (strideutils_secrets_path := os.environ.get('STRIDEUTILS_SECRETS_PATH')):
|
|
318
|
+
raise ValueError(MISSING_CONFIG_MSG.format(x='STRIDEUTILS_SECRETS_PATH'))
|
|
319
|
+
|
|
320
|
+
with open(strideutils_secrets_path, 'r') as f:
|
|
321
|
+
raw_secrets = yaml.load(f, Loader)
|
|
322
|
+
raw_secrets.update({'ENV': ENV})
|
|
323
|
+
|
|
324
|
+
return raw_secrets
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _parse_raw_configs(raw_config: dict[str, Any], raw_secrets: dict[str, Optional[str]]) -> Config:
|
|
328
|
+
"""
|
|
329
|
+
Builds the relevant config data classes from the raw jsons
|
|
330
|
+
"""
|
|
331
|
+
host_chain_fields = [field.name for field in fields(HostChainsConfig)]
|
|
332
|
+
app_chain_fields = [field.name for field in fields(AppChainsConfig)]
|
|
333
|
+
|
|
334
|
+
host_chain_config_dict = {
|
|
335
|
+
name: HostChainConfig(**info) for name, info in raw_config["host_zones"].items() if name in host_chain_fields
|
|
336
|
+
}
|
|
337
|
+
app_chain_config_dict = {
|
|
338
|
+
name: ChainConfig(**info) for name, info in raw_config["app_chains"].items() if name in app_chain_fields
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
stride_chain_config = StrideChainConfig(**raw_config["stride"])
|
|
342
|
+
host_zone_config = HostChainsConfig(**host_chain_config_dict)
|
|
343
|
+
app_chain_config = AppChainsConfig(**app_chain_config_dict)
|
|
344
|
+
|
|
345
|
+
chain_configs = {
|
|
346
|
+
'stride': stride_chain_config,
|
|
347
|
+
'host_zones': host_zone_config,
|
|
348
|
+
'app_chains': app_chain_config,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# Load non-nested configs, then overwrite with the chain configs
|
|
352
|
+
config_dict = raw_secrets
|
|
353
|
+
config_dict.update(raw_config)
|
|
354
|
+
config_dict.update(chain_configs)
|
|
355
|
+
return Config(**config_dict) # type: ignore
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _init_config() -> Config:
|
|
359
|
+
"""
|
|
360
|
+
Initializes the main config files
|
|
361
|
+
"""
|
|
362
|
+
raw_config = _load_raw_config()
|
|
363
|
+
raw_secrets = _load_raw_secrets()
|
|
364
|
+
return _parse_raw_configs(raw_config, raw_secrets)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
config: Config = _init_config()
|
|
@@ -40,6 +40,9 @@ RATE_LIMIT_QUERY = "Stride-Labs/ibc-rate-limiting/ratelimit/ratelimits"
|
|
|
40
40
|
STAKETIA = "staketia"
|
|
41
41
|
STAKEDYM = "stakedym"
|
|
42
42
|
|
|
43
|
+
CELESTIA_CHAIN_ID = "celestia"
|
|
44
|
+
DYMENSION_CHAIN_ID = "dymension_1100-1"
|
|
45
|
+
|
|
43
46
|
MULTISIG_HOST_ZONE_QUERY = "Stride-Labs/stride/{module}/host_zone"
|
|
44
47
|
MULTISIG_DELEGATION_RECORDS_QUERY = "Stride-Labs/stride/{module}/delegation_records"
|
|
45
48
|
MULTISIG_UNBONDING_RECORDS_QUERY = "Stride-Labs/stride/{module}/unbonding_records"
|
|
@@ -138,9 +141,11 @@ def query_list_with_pagination(
|
|
|
138
141
|
return data
|
|
139
142
|
|
|
140
143
|
|
|
141
|
-
def get_all_host_zones() -> List[Dict]:
|
|
144
|
+
def get_all_host_zones(include_multisig_hosts: bool = True) -> List[Dict]:
|
|
142
145
|
"""
|
|
143
146
|
Queries all host zone structs, returning a list of each host zone json
|
|
147
|
+
By default, this queries both stakeibc and staketia/stakedym; however,
|
|
148
|
+
staketia/stakedym can be disabled by passing include_multisig_hosts = False
|
|
144
149
|
|
|
145
150
|
Returns:
|
|
146
151
|
A list of host zone structs
|
|
@@ -184,11 +189,19 @@ def get_all_host_zones() -> List[Dict]:
|
|
|
184
189
|
...more host zones]
|
|
185
190
|
|
|
186
191
|
"""
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
192
|
+
stakeibc_response = request(f"{config.stride.api_endpoint}/{HOST_ZONE_QUERY}")
|
|
193
|
+
stakeibc_host_zones = stakeibc_response["host_zone"]
|
|
194
|
+
|
|
195
|
+
if not include_multisig_hosts:
|
|
196
|
+
return stakeibc_host_zones
|
|
197
|
+
|
|
198
|
+
staketia_host_zone = get_host_zone(CELESTIA_CHAIN_ID, cast_types=False)
|
|
199
|
+
stakedym_host_zone = get_host_zone(DYMENSION_CHAIN_ID, cast_types=False)
|
|
200
|
+
|
|
201
|
+
stakedym_response = request(f"{config.stride.api_endpoint}/{MULTISIG_HOST_ZONE_QUERY.format(module=STAKEDYM)}")
|
|
202
|
+
stakedym_host_zone = stakedym_response["host_zone"]
|
|
190
203
|
|
|
191
|
-
return
|
|
204
|
+
return stakeibc_host_zones + [staketia_host_zone, stakedym_host_zone]
|
|
192
205
|
|
|
193
206
|
|
|
194
207
|
def _cast_host_zone_fields(host_zone: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -213,17 +226,17 @@ def _cast_host_zone_fields(host_zone: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
213
226
|
return host_zone
|
|
214
227
|
|
|
215
228
|
|
|
216
|
-
def
|
|
229
|
+
def get_host_zone(chain_id: str, cast_types: bool = True, **kwargs) -> Dict[str, Any]:
|
|
217
230
|
"""
|
|
218
231
|
Queries a stakeibc host zone, returning a dict with the host zone structure and fields casted
|
|
219
232
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
query = f"Stride-Labs/stride/stakeibc/host_zone/{chain_id}"
|
|
234
|
+
if chain_id in [CELESTIA_CHAIN_ID, DYMENSION_CHAIN_ID]:
|
|
235
|
+
query = MULTISIG_HOST_ZONE_QUERY.format(module=STAKETIA)
|
|
236
|
+
if chain_id == "dymension_1100-1":
|
|
237
|
+
query = MULTISIG_HOST_ZONE_QUERY.format(module=STAKEDYM)
|
|
224
238
|
|
|
225
|
-
|
|
226
|
-
host_zone = request(endpoint, **kwargs)["host_zone"]
|
|
239
|
+
host_zone = request(f"{config.stride.api_endpoint}/{query}", **kwargs)["host_zone"]
|
|
227
240
|
|
|
228
241
|
if cast_types:
|
|
229
242
|
host_zone = _cast_host_zone_fields(host_zone)
|
|
@@ -231,77 +244,6 @@ def get_host_zone_json(zone_id: str, cast_types: bool = True, **kwargs) -> Dict[
|
|
|
231
244
|
return host_zone
|
|
232
245
|
|
|
233
246
|
|
|
234
|
-
# TODO: Consolidate celestia and dymension queries (API breaking)
|
|
235
|
-
def get_celestia_host_zone(cast_types: bool = True) -> Dict[str, Any]:
|
|
236
|
-
"""
|
|
237
|
-
Queries the celestia host zone from staketia
|
|
238
|
-
|
|
239
|
-
Example response:
|
|
240
|
-
{
|
|
241
|
-
'chain_id': 'celestia',
|
|
242
|
-
'native_token_denom': 'utia',
|
|
243
|
-
'native_token_ibc_denom': 'ibc/BF3B4F53F3694B66E13C23107C84B6485BD2B96296BB7EC680EA77BBA75B4801',
|
|
244
|
-
'transfer_channel_id': 'channel-162',
|
|
245
|
-
'delegation_address': 'celestia1d6ntc7s8gs86tpdyn422vsqc6uaz9cejnxz5p5',
|
|
246
|
-
'reward_address': 'celestia15up3hegy8zuqhy0p9m8luh0c984ptu2g5p4xpf',
|
|
247
|
-
'deposit_address': 'stride1d6ntc7s8gs86tpdyn422vsqc6uaz9cejp8nc04',
|
|
248
|
-
'redemption_address': 'stride15up3hegy8zuqhy0p9m8luh0c984ptu2gxqy20g',
|
|
249
|
-
'claim_address': 'stride13nw9fm4ua8pwzmsx9kdrhefl4puz0tp7ge3gxd',
|
|
250
|
-
'operator_address_on_stride': 'stride1ghhu67ttgmxrsyxljfl2tysyayswklvxs7pepw',
|
|
251
|
-
'safe_address_on_stride': 'stride18p7xg4hj2u3zpk0v9gq68pjyuuua5wa387sjjc',
|
|
252
|
-
'last_redemption_rate': '1.000000000000000000',
|
|
253
|
-
'redemption_rate': '1.000000000000000000',
|
|
254
|
-
'min_redemption_rate': '0.950000000000000000',
|
|
255
|
-
'max_redemption_rate': '1.100000000000000000',
|
|
256
|
-
'min_inner_redemption_rate': '0.950000000000000000',
|
|
257
|
-
'max_inner_redemption_rate': '1.100000000000000000',
|
|
258
|
-
'delegated_balance': '300000',
|
|
259
|
-
'unbonding_period_seconds': '1814400',
|
|
260
|
-
'halted': False
|
|
261
|
-
}
|
|
262
|
-
"""
|
|
263
|
-
url = f"{config.stride.api_endpoint}/{MULTISIG_HOST_ZONE_QUERY.format(module=STAKETIA)}"
|
|
264
|
-
host_zone = request(url)["host_zone"]
|
|
265
|
-
if cast_types:
|
|
266
|
-
_cast_host_zone_fields(host_zone)
|
|
267
|
-
return host_zone
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def get_dymension_host_zone(cast_types: bool = True) -> Dict[str, Any]:
|
|
271
|
-
"""
|
|
272
|
-
Queries the dymension host zone from stakedym
|
|
273
|
-
|
|
274
|
-
Example response:
|
|
275
|
-
{
|
|
276
|
-
'chain_id': 'dymension',
|
|
277
|
-
'native_token_denom': 'adym',
|
|
278
|
-
'native_token_ibc_denom': 'ibc/E1C22332C083574F3418481359733BA8887D171E76C80AD9237422AEABB66018',
|
|
279
|
-
'transfer_channel_id': 'channel-197',
|
|
280
|
-
'delegation_address': 'dym1gl9j2hyyukqvlmzzcxl99mqfgu4y4frgzlv3zz',
|
|
281
|
-
'reward_address': 'dym1ww3m6h5e3dk2musft9w8f2zu4rkuxgh6zwu0d0',
|
|
282
|
-
'deposit_address': 'stride1e7j8d6sdq272fqe2jfxjpgcagn04j75w9695fj',
|
|
283
|
-
'redemption_address': 'stride1jpsnc0ynufa2aheflj6mxzzzsu7nlwqk7ff69n',
|
|
284
|
-
'claim_address': 'stride1q8juddwptg5yxyghh3n243pp4w8ctpvpmf6ras',
|
|
285
|
-
'operator_address_on_stride': 'stride1ghhu67ttgmxrsyxljfl2tysyayswklvxs7pepw',
|
|
286
|
-
'safe_address_on_stride': 'stride1sj8gyqeqecqhqu7em67hn2tjzhpkdf8wz5plh7',
|
|
287
|
-
'last_redemption_rate': '1.000000000000000000',
|
|
288
|
-
'redemption_rate': '1.000000000000000000',
|
|
289
|
-
'min_redemption_rate': '0.950000000000000000',
|
|
290
|
-
'max_redemption_rate': '1.100000000000000000',
|
|
291
|
-
'min_inner_redemption_rate': '0.950000000000000000',
|
|
292
|
-
'max_inner_redemption_rate': '1.100000000000000000',
|
|
293
|
-
'delegated_balance': '300000',
|
|
294
|
-
'unbonding_period_seconds': '1814400',
|
|
295
|
-
'halted': False
|
|
296
|
-
}
|
|
297
|
-
"""
|
|
298
|
-
url = f"{config.stride.api_endpoint}/{MULTISIG_HOST_ZONE_QUERY.format(module=STAKEDYM)}"
|
|
299
|
-
host_zone = request(url)["host_zone"]
|
|
300
|
-
if cast_types:
|
|
301
|
-
_cast_host_zone_fields(host_zone)
|
|
302
|
-
return host_zone
|
|
303
|
-
|
|
304
|
-
|
|
305
247
|
def get_multisig_delegation_records(module: str, include_archived: bool = False) -> List[Dict]:
|
|
306
248
|
"""
|
|
307
249
|
Queries the delegation records from staketia or stakedym
|
|
@@ -419,20 +361,18 @@ def get_redemption_rate(token: str) -> float:
|
|
|
419
361
|
"""
|
|
420
362
|
Queries the redemption rate for a particular token
|
|
421
363
|
"""
|
|
422
|
-
|
|
423
|
-
return float(
|
|
364
|
+
host_zone = get_host_zone(config.get_chain(ticker=token).id)
|
|
365
|
+
return float(host_zone["redemption_rate"])
|
|
424
366
|
|
|
425
367
|
|
|
426
368
|
def get_tvl_in_utokens(host_zone_id: str) -> float:
|
|
427
369
|
"""
|
|
428
370
|
Queries the stride TVL for a given zone (denominated in the native token)
|
|
429
371
|
"""
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
delegation_key = "total_delegations"
|
|
433
|
-
|
|
434
|
-
delegation_key = "delegated_balance"
|
|
435
|
-
return int(js[delegation_key]) / pow(10, zone_info.denom_decimals)
|
|
372
|
+
host_zone_config = config.get_chain(id=host_zone_id)
|
|
373
|
+
host_zone = get_host_zone(host_zone_id)
|
|
374
|
+
delegation_key = "total_delegations" if host_zone_config.ica_enabled else "delegated_balance"
|
|
375
|
+
return int(host_zone[delegation_key]) / pow(10, host_zone_config.denom_decimals)
|
|
436
376
|
|
|
437
377
|
|
|
438
378
|
def get_latest_block(rpc_endpoint: str = config.stride.rpc_endpoint) -> int:
|
|
@@ -460,7 +400,7 @@ def convert_cosmos_address(address: str, new_prefix: str) -> str:
|
|
|
460
400
|
def get_balances(
|
|
461
401
|
address: str,
|
|
462
402
|
block_height: int = 0,
|
|
463
|
-
api_endpoint: str = config.stride.
|
|
403
|
+
api_endpoint: str = config.stride.api_endpoint,
|
|
464
404
|
cache_response: bool = False,
|
|
465
405
|
) -> Dict[str, int]:
|
|
466
406
|
"""
|
|
@@ -476,7 +416,7 @@ def get_balance(
|
|
|
476
416
|
address: str,
|
|
477
417
|
denom: str,
|
|
478
418
|
block_height: int = 0,
|
|
479
|
-
api_endpoint: str = config.stride.
|
|
419
|
+
api_endpoint: str = config.stride.api_endpoint,
|
|
480
420
|
cache_response: bool = False,
|
|
481
421
|
) -> int:
|
|
482
422
|
"""
|
|
@@ -504,7 +444,7 @@ def get_supply(
|
|
|
504
444
|
|
|
505
445
|
|
|
506
446
|
@lru_cache
|
|
507
|
-
def get_block_time(height: int, rpc_endpoint: str = config.stride.
|
|
447
|
+
def get_block_time(height: int, rpc_endpoint: str = config.stride.library_rpc_endpoint) -> datetime.datetime:
|
|
508
448
|
"""
|
|
509
449
|
Returns the timestamp of the given block height on a chain
|
|
510
450
|
"""
|
|
@@ -615,7 +555,7 @@ def generate_vesting_account(
|
|
|
615
555
|
|
|
616
556
|
The output is a JSON object that can be used to create a vesting account on the Stride chain.
|
|
617
557
|
"""
|
|
618
|
-
timezone = pytz.timezone(config.
|
|
558
|
+
timezone = pytz.timezone(config.timezone)
|
|
619
559
|
start_time = timezone.localize(datetime.datetime.strptime(start_time_in_local_tz, "%Y-%m-%d %H:%M"))
|
|
620
560
|
start_time = start_time.astimezone(pytz.utc)
|
|
621
561
|
start_time = int(start_time.timestamp())
|
|
@@ -643,7 +583,7 @@ def get_block_height_in_past(
|
|
|
643
583
|
seconds_per_block: int = 6,
|
|
644
584
|
prev_time: Optional[str] = None,
|
|
645
585
|
verbose: bool = False,
|
|
646
|
-
rpc_endpoint: str = config.stride.
|
|
586
|
+
rpc_endpoint: str = config.stride.library_rpc_endpoint,
|
|
647
587
|
) -> int:
|
|
648
588
|
"""
|
|
649
589
|
Gets the block height at a specified time by binary searching at different heights
|
|
@@ -786,7 +726,7 @@ def get_host_zone_delegations(host_zone_id: str, ground_truth_included: bool = T
|
|
|
786
726
|
If `ground_truth_included=True`, then will also return the "true" delegations
|
|
787
727
|
on the host zone delegaiton account.
|
|
788
728
|
"""
|
|
789
|
-
host_zone =
|
|
729
|
+
host_zone = get_host_zone(host_zone_id)
|
|
790
730
|
vdf = pd.DataFrame(host_zone["validators"])
|
|
791
731
|
int_cols = [
|
|
792
732
|
"weight",
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Exposes an easy API to get prices from coingecko
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from strideutils import stride_requests
|
|
6
|
-
from strideutils.stride_config import config
|
|
7
|
-
|
|
8
|
-
COINGECKO_ENDPOINT = "https://pro-api.coingecko.com/"
|
|
9
|
-
COINGECKO_PRICE_QUERY = "api/v3/simple/price?ids={ticker_id}&vs_currencies=usd"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_token_price(
|
|
13
|
-
ticker: str,
|
|
14
|
-
api_token: str = config.COINGECKO_API_TOKEN,
|
|
15
|
-
cache_response: bool = False,
|
|
16
|
-
):
|
|
17
|
-
"""
|
|
18
|
-
Reads token price from coingecko
|
|
19
|
-
If a redis_client is provided, then store the value.
|
|
20
|
-
"""
|
|
21
|
-
# TODO: Consider using the coingecko ID for stTokens instead of the redemption rate
|
|
22
|
-
# Get redemption rate for calculating st token prices
|
|
23
|
-
redemption_rate = float(1)
|
|
24
|
-
if ticker.startswith('st') and ticker[3].isupper():
|
|
25
|
-
redemption_rate = stride_requests.get_redemption_rate(ticker[2:])
|
|
26
|
-
ticker = ticker[2:]
|
|
27
|
-
|
|
28
|
-
coingecko_name = config.get_chain(ticker=ticker).coingecko_name
|
|
29
|
-
endpoint = COINGECKO_ENDPOINT + COINGECKO_PRICE_QUERY.format(ticker_id=coingecko_name)
|
|
30
|
-
headers = {'x-cg-pro-api-key': api_token}
|
|
31
|
-
|
|
32
|
-
response = stride_requests.request(endpoint, headers=headers, cache_response=cache_response)
|
|
33
|
-
price = response[coingecko_name]["usd"] * redemption_rate
|
|
34
|
-
|
|
35
|
-
return price
|
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file loads and provide an object `config` to access all secrets, configs, and chain info
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import itertools
|
|
6
|
-
import os
|
|
7
|
-
from dataclasses import dataclass, field, fields
|
|
8
|
-
from typing import List, Optional, Union
|
|
9
|
-
|
|
10
|
-
import yaml
|
|
11
|
-
|
|
12
|
-
MISSING_CONFIG_MSG = "Configuration or environment variable {x} is not set or unknown"
|
|
13
|
-
|
|
14
|
-
ENV = None
|
|
15
|
-
try:
|
|
16
|
-
ENV = os.environ['ENV']
|
|
17
|
-
except KeyError:
|
|
18
|
-
raise ValueError(MISSING_CONFIG_MSG.format(x='ENV'))
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class ConfigObj:
|
|
22
|
-
"""Raise an error if a config is not set."""
|
|
23
|
-
|
|
24
|
-
def __getattr__(self, name):
|
|
25
|
-
raise AttributeError(MISSING_CONFIG_MSG.format(x=name))
|
|
26
|
-
|
|
27
|
-
def __getattribute__(self, name):
|
|
28
|
-
value = super().__getattribute__(name)
|
|
29
|
-
if value == "" or value is None:
|
|
30
|
-
raise AttributeError # induces a call to __getattr__
|
|
31
|
-
return value
|
|
32
|
-
|
|
33
|
-
def __iter__(self):
|
|
34
|
-
"""Allow iterating over set values"""
|
|
35
|
-
for subfield in fields(self):
|
|
36
|
-
try:
|
|
37
|
-
value = getattr(self, subfield.name)
|
|
38
|
-
except AttributeError:
|
|
39
|
-
continue
|
|
40
|
-
yield value
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class ConfigDict(dict):
|
|
44
|
-
def __getitem__(self, key):
|
|
45
|
-
"""Raise an error if an unset key is indexed."""
|
|
46
|
-
try:
|
|
47
|
-
value = super().__getitem__(key)
|
|
48
|
-
if value == "" or value is None:
|
|
49
|
-
raise KeyError
|
|
50
|
-
return value
|
|
51
|
-
except KeyError:
|
|
52
|
-
raise KeyError(MISSING_CONFIG_MSG.format(x=key))
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Use ConfigDict in the yaml parser
|
|
56
|
-
class Loader(yaml.FullLoader):
|
|
57
|
-
def construct_yaml_map(self, node):
|
|
58
|
-
data = ConfigDict()
|
|
59
|
-
yield data
|
|
60
|
-
value = self.construct_mapping(node)
|
|
61
|
-
data.update(value)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Loader.add_constructor('tag:yaml.org,2002:map', Loader.construct_yaml_map)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@dataclass(repr=False)
|
|
68
|
-
class ChainConfig(ConfigObj):
|
|
69
|
-
name: str = ""
|
|
70
|
-
id: str = ""
|
|
71
|
-
# Optional. For looking up prices on coingecko if it differs from name
|
|
72
|
-
coingecko_name: str = ""
|
|
73
|
-
denom: str = ""
|
|
74
|
-
denom_decimals: int = 6
|
|
75
|
-
inflation_frequency_hours: int = -1
|
|
76
|
-
ticker: str = ""
|
|
77
|
-
stride_token_hash: str = ""
|
|
78
|
-
api_endpoint: str = ""
|
|
79
|
-
rpc_endpoint: str = ""
|
|
80
|
-
library_api: str = ""
|
|
81
|
-
library_rpc: str = ""
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@dataclass(repr=False)
|
|
85
|
-
class HostZoneChainConfig(ConfigObj):
|
|
86
|
-
cosmoshub: ChainConfig = field(default_factory=ChainConfig)
|
|
87
|
-
osmosis: ChainConfig = field(default_factory=ChainConfig)
|
|
88
|
-
evmos: ChainConfig = field(default_factory=ChainConfig)
|
|
89
|
-
injective: ChainConfig = field(default_factory=ChainConfig)
|
|
90
|
-
juno: ChainConfig = field(default_factory=ChainConfig)
|
|
91
|
-
stargaze: ChainConfig = field(default_factory=ChainConfig)
|
|
92
|
-
terra: ChainConfig = field(default_factory=ChainConfig)
|
|
93
|
-
umee: ChainConfig = field(default_factory=ChainConfig)
|
|
94
|
-
comdex: ChainConfig = field(default_factory=ChainConfig)
|
|
95
|
-
sommelier: ChainConfig = field(default_factory=ChainConfig)
|
|
96
|
-
dydx: ChainConfig = field(default_factory=ChainConfig)
|
|
97
|
-
saga: ChainConfig = field(default_factory=ChainConfig)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@dataclass(repr=False)
|
|
101
|
-
class AppConfig(ConfigObj):
|
|
102
|
-
neutron: ChainConfig = field(default_factory=ChainConfig)
|
|
103
|
-
kava: ChainConfig = field(default_factory=ChainConfig)
|
|
104
|
-
iris: ChainConfig = field(default_factory=ChainConfig)
|
|
105
|
-
akash: ChainConfig = field(default_factory=ChainConfig)
|
|
106
|
-
sentinel: ChainConfig = field(default_factory=ChainConfig)
|
|
107
|
-
persistence: ChainConfig = field(default_factory=ChainConfig)
|
|
108
|
-
cryptoorg: ChainConfig = field(default_factory=ChainConfig)
|
|
109
|
-
kichain: ChainConfig = field(default_factory=ChainConfig)
|
|
110
|
-
bitcanna: ChainConfig = field(default_factory=ChainConfig)
|
|
111
|
-
regen: ChainConfig = field(default_factory=ChainConfig)
|
|
112
|
-
gravity: ChainConfig = field(default_factory=ChainConfig)
|
|
113
|
-
desmos: ChainConfig = field(default_factory=ChainConfig)
|
|
114
|
-
chihuahua: ChainConfig = field(default_factory=ChainConfig)
|
|
115
|
-
secret: ChainConfig = field(default_factory=ChainConfig)
|
|
116
|
-
axelar: ChainConfig = field(default_factory=ChainConfig)
|
|
117
|
-
crescent: ChainConfig = field(default_factory=ChainConfig)
|
|
118
|
-
mantle: ChainConfig = field(default_factory=ChainConfig)
|
|
119
|
-
mars: ChainConfig = field(default_factory=ChainConfig)
|
|
120
|
-
canto: ChainConfig = field(default_factory=ChainConfig)
|
|
121
|
-
agoric: ChainConfig = field(default_factory=ChainConfig)
|
|
122
|
-
celestia: ChainConfig = field(default_factory=ChainConfig)
|
|
123
|
-
dymension: ChainConfig = field(default_factory=ChainConfig)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@dataclass(repr=False)
|
|
127
|
-
class Config(ConfigObj):
|
|
128
|
-
ENV: str
|
|
129
|
-
timezone: str = "US/Eastern"
|
|
130
|
-
|
|
131
|
-
# Twilio
|
|
132
|
-
TWILIO_ALERTS_NUMBER: str = ""
|
|
133
|
-
TWILIO_ACCOUNT_ID: str = ""
|
|
134
|
-
TWILIO_API_TOKEN: str = ""
|
|
135
|
-
PHONE_NUMBERS: ConfigDict = field(default_factory=ConfigDict)
|
|
136
|
-
|
|
137
|
-
# Slack
|
|
138
|
-
STRIDEBOT_API_TOKEN: str = ""
|
|
139
|
-
|
|
140
|
-
# Gsheets - loads from str env var in PROD
|
|
141
|
-
PUBLICSHEETS_AUTH: Union[str, ConfigDict] = field(default_factory=ConfigDict)
|
|
142
|
-
|
|
143
|
-
# Coingecko
|
|
144
|
-
COINGECKO_API_TOKEN: str = ""
|
|
145
|
-
|
|
146
|
-
# Protocol Staking (dydx yield)
|
|
147
|
-
PROTOCOL_STAKING_API_TOKEN: str = ""
|
|
148
|
-
|
|
149
|
-
# Stride alerts
|
|
150
|
-
alerts_playbook: str = ""
|
|
151
|
-
slack_channels: ConfigDict = field(default_factory=ConfigDict)
|
|
152
|
-
|
|
153
|
-
# Upstash
|
|
154
|
-
UPSTASH_PUBLIC_PASSWORD: str = ""
|
|
155
|
-
UPSTASH_STRIDE_FRONTEND_PASSWORD: str = ""
|
|
156
|
-
UPSTASH_STRIDE_BACKEND_PASSWORD: str = ""
|
|
157
|
-
UPSTASH_STRIDE_DYDX_PUBLIC_PASSWORD: str = ""
|
|
158
|
-
|
|
159
|
-
# Stride internal secrets
|
|
160
|
-
SLACK_BEARER_TOKEN: str = ""
|
|
161
|
-
SLACK_SUCCESS_CHANNEL_ID: str = ""
|
|
162
|
-
SLACK_INVARIANT_FAILURE_CHANNEL_ID: str = ""
|
|
163
|
-
SLACK_PACKET_FAILURE_CHANNEL_ID: str = ""
|
|
164
|
-
NUMIA_API_TOKEN: str = ""
|
|
165
|
-
founders: List[str] = field(default_factory=lambda: ['riley', 'aidan', 'vishal'])
|
|
166
|
-
|
|
167
|
-
# Chain configs
|
|
168
|
-
stride: ChainConfig = field(default_factory=ChainConfig)
|
|
169
|
-
host_zones: HostZoneChainConfig = field(default_factory=HostZoneChainConfig)
|
|
170
|
-
app_chains: AppConfig = field(default_factory=AppConfig)
|
|
171
|
-
|
|
172
|
-
def _create_mappings(self) -> None:
|
|
173
|
-
"""
|
|
174
|
-
Create a reverse map from attributes to ChainConfig if id, ticker, and denom are set.
|
|
175
|
-
"""
|
|
176
|
-
name_to_zone = ConfigDict()
|
|
177
|
-
id_to_zone = ConfigDict()
|
|
178
|
-
ticker_to_zone = ConfigDict()
|
|
179
|
-
denom_to_zone = ConfigDict()
|
|
180
|
-
stride_token_hash_to_zone = ConfigDict()
|
|
181
|
-
|
|
182
|
-
# Map host zones
|
|
183
|
-
for chain_info in itertools.chain(self.host_zones, self.app_chains):
|
|
184
|
-
name_to_zone[chain_info.name] = chain_info
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
id_to_zone[chain_info.id] = chain_info
|
|
188
|
-
except AttributeError:
|
|
189
|
-
pass
|
|
190
|
-
|
|
191
|
-
try:
|
|
192
|
-
ticker_to_zone[chain_info.ticker] = chain_info
|
|
193
|
-
ticker_to_zone[chain_info.ticker.upper()] = chain_info
|
|
194
|
-
ticker_to_zone[chain_info.ticker.lower()] = chain_info
|
|
195
|
-
ticker_to_zone[chain_info.ticker.denom[1].upper()] = chain_info
|
|
196
|
-
except AttributeError:
|
|
197
|
-
pass
|
|
198
|
-
|
|
199
|
-
try:
|
|
200
|
-
denom_to_zone[chain_info.denom] = chain_info
|
|
201
|
-
except AttributeError:
|
|
202
|
-
pass
|
|
203
|
-
|
|
204
|
-
try:
|
|
205
|
-
stride_token_hash_to_zone[chain_info.stride_token_hash] = chain_info
|
|
206
|
-
except AttributeError:
|
|
207
|
-
pass
|
|
208
|
-
|
|
209
|
-
self._name_to_zone = name_to_zone
|
|
210
|
-
self._id_to_zone = id_to_zone
|
|
211
|
-
self._ticker_to_zone = ticker_to_zone
|
|
212
|
-
self._denom_to_zone = denom_to_zone
|
|
213
|
-
self._stride_token_hash = stride_token_hash_to_zone
|
|
214
|
-
|
|
215
|
-
def __post_init__(self):
|
|
216
|
-
self._create_mappings()
|
|
217
|
-
|
|
218
|
-
def get_chain( # type: ignore[return]
|
|
219
|
-
self,
|
|
220
|
-
name: Optional[str] = None,
|
|
221
|
-
id: Optional[str] = None,
|
|
222
|
-
ticker: Optional[str] = None,
|
|
223
|
-
denom: Optional[str] = None,
|
|
224
|
-
stride_token_hash: Optional[str] = None,
|
|
225
|
-
) -> ChainConfig:
|
|
226
|
-
"""
|
|
227
|
-
Fetch info about a host zone by name, id, ticker, denom, or token hash.
|
|
228
|
-
|
|
229
|
-
Raises
|
|
230
|
-
KeyError if a valid chain doesn't exist
|
|
231
|
-
"""
|
|
232
|
-
if sum(map(bool, [name, id, ticker, denom, stride_token_hash])) != 1:
|
|
233
|
-
raise ValueError("Exactly one of name, id, ticker, denom, or token hash must be specified.")
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
name == self.stride.name
|
|
237
|
-
or id == self.stride.id
|
|
238
|
-
or ticker == self.stride.ticker
|
|
239
|
-
or denom == self.stride.denom
|
|
240
|
-
):
|
|
241
|
-
return self.stride
|
|
242
|
-
elif name:
|
|
243
|
-
return self._name_to_zone[name]
|
|
244
|
-
elif id:
|
|
245
|
-
return self._id_to_zone[id]
|
|
246
|
-
elif ticker:
|
|
247
|
-
return self._ticker_to_zone[ticker]
|
|
248
|
-
elif denom:
|
|
249
|
-
return self._denom_to_zone[denom]
|
|
250
|
-
elif stride_token_hash:
|
|
251
|
-
return self._stride_token_hash[stride_token_hash]
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
# Load chain config
|
|
255
|
-
def _load() -> Config:
|
|
256
|
-
"""
|
|
257
|
-
Load config dataclasses based on environment
|
|
258
|
-
"""
|
|
259
|
-
STRIDEUTILS_CONFIG_PATH = os.environ.get('STRIDEUTILS_CONFIG_PATH', 'config/config.yaml')
|
|
260
|
-
if not os.path.exists(STRIDEUTILS_CONFIG_PATH):
|
|
261
|
-
raise ValueError(
|
|
262
|
-
'Shell var STRIDEUTILS_CONFIG_PATH is missing or the file does not exist.'
|
|
263
|
-
'The default file and location is config/config.yaml in the current working directory'
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
with open(STRIDEUTILS_CONFIG_PATH, 'r') as f:
|
|
267
|
-
raw_config = yaml.load(f, Loader)
|
|
268
|
-
stride_chain_config = ChainConfig(**raw_config.get('stride'))
|
|
269
|
-
host_zone_config = HostZoneChainConfig(
|
|
270
|
-
**{name: ChainConfig(**info) for name, info in raw_config.get('host_zones').items()}
|
|
271
|
-
)
|
|
272
|
-
app_chain_config = AppConfig(
|
|
273
|
-
**{name: ChainConfig(**info) for name, info in raw_config.get('app_chains').items()}
|
|
274
|
-
)
|
|
275
|
-
chain_configs = {
|
|
276
|
-
'stride': stride_chain_config,
|
|
277
|
-
'host_zones': host_zone_config,
|
|
278
|
-
'app_chains': app_chain_config,
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
# Load secrets
|
|
282
|
-
if ENV == 'DEV':
|
|
283
|
-
if not (STRIDEUTILS_SECRETS_PATH := os.environ.get('STRIDEUTILS_SECRETS_PATH')):
|
|
284
|
-
raise ValueError(MISSING_CONFIG_MSG.format(x='STRIDEUTILS_SECRETS_PATH'))
|
|
285
|
-
with open(STRIDEUTILS_SECRETS_PATH, 'r') as f:
|
|
286
|
-
raw_secrets = yaml.load(f, Loader)
|
|
287
|
-
raw_secrets.update({'ENV': ENV})
|
|
288
|
-
elif ENV == 'PROD':
|
|
289
|
-
raw_secrets = {
|
|
290
|
-
secret.name: os.environ.get(secret.name) for secret in fields(Config) if os.environ.get(secret.name)
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
# Load vanilla configs
|
|
294
|
-
raw_secrets.update(raw_config)
|
|
295
|
-
# Overwrite prepped ChainConfigs
|
|
296
|
-
raw_secrets.update(chain_configs)
|
|
297
|
-
config = Config(**raw_secrets)
|
|
298
|
-
return config
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
config: Config = _load()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|