-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_utils.py
More file actions
123 lines (102 loc) · 3.94 KB
/
Copy pathapi_utils.py
File metadata and controls
123 lines (102 loc) · 3.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import os
import json
import requests
import logging
from dotenv import load_dotenv
from typing import Optional
load_dotenv()
ETHERSCAN_API_KEY = os.getenv("ETHERSCAN_API_KEY")
ETHERSCAN_CHAIN_ID = os.getenv("ETHERSCAN_CHAIN_ID", "1") # default to mainnet
def safe_api_request(url: str, key: str):
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
except requests.RequestException as e:
logging.error("[%s] network error: %s", key, e)
return None
text = resp.text or ""
# Prefer JSON parse
try:
return resp.json()
except ValueError:
# Not valid JSON — wrap raw text so callers get a dict and we keep diagnostics
txt = text.strip()
if not txt:
logging.error("[%s] empty response body", key)
return None
logging.warning("[%s] response not JSON; returning wrapped raw text (first 300 chars): %s", key, txt[:300])
# try to salvage if it *looks* like JSON stored as a string
try:
return json.loads(txt)
except Exception:
return {"_raw_text": txt}
def get_gas_prices(chain_id: Optional[str] = None):
"""
Fetch gas prices using Etherscan API v2 (gasoracle). Returns normalized dict of floats:
"""
if not ETHERSCAN_API_KEY:
logging.error("ETHERSCAN_API_KEY not found in .env")
return {"safe": 0.0, "average": 0.0, "fast": 0.0}
chain_id = chain_id or ETHERSCAN_CHAIN_ID or "1"
# Etherscan v2 migration: use /v2/api and include chainid param
url = (
f"https://api.etherscan.io/v2/api"
f"?chainid={chain_id}"
f"&module=gastracker"
f"&action=gasoracle"
f"&apikey={ETHERSCAN_API_KEY}"
)
result = safe_api_request(url, "get_gas_prices")
if not result or not isinstance(result, dict):
logging.error("[get_gas_prices] bad result: %r", result)
return {"safe": 0.0, "average": 0.0, "fast": 0.0}
if "_raw_text" in result:
logging.error("[get_gas_prices] upstream returned non-JSON: %s", result["_raw_text"][:300])
return {"safe": 0.0, "average": 0.0, "fast": 0.0}
r = result.get("result")
if isinstance(r, str):
logging.error("[get_gas_prices] unexpected 'result' structure (string): %s", r[:300])
return {"safe": 0.0, "average": 0.0, "fast": 0.0}
if not isinstance(r, dict):
logging.error("[get_gas_prices] unexpected 'result' structure: %r", r)
return {"safe": 0.0, "average": 0.0, "fast": 0.0}
def safe_float(x):
try:
return float(x)
except Exception:
try:
return float(str(x).replace(",", ""))
except Exception:
return 0.0
return {
"safe": safe_float(r.get("SafeGasPrice", 0)),
"average": safe_float(r.get("ProposeGasPrice", 0)),
"fast": safe_float(r.get("FastGasPrice", 0)),
}
def get_eth_prices(currency="usd"):
"""
Fetch ETH price from CoinGecko. Return the inner dict for `ethereum` normalized to floats.
Returns {} on failure.
"""
url = f"https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies={currency}"
result = safe_api_request(url, "get_eth_prices")
if not result or not isinstance(result, dict):
logging.error("[get_eth_prices] bad result: %r", result)
return {}
if "_raw_text" in result:
logging.error("[get_eth_prices] raw text from upstream: %s", result["_raw_text"][:300])
return {}
eth = result.get("ethereum")
if not isinstance(eth, dict):
logging.error("[get_eth_prices] missing or unexpected 'ethereum' key: %r", result)
return {}
normalized = {}
for k, v in eth.items():
try:
normalized[k] = float(v)
except Exception:
try:
normalized[k] = float(str(v).replace(",", ""))
except Exception:
normalized[k] = 0.0
return normalized