75f50fe083
Co-authored-by: Cursor <cursoragent@cursor.com>
95 lines
2.5 KiB
Python
95 lines
2.5 KiB
Python
"""交易所 API 凭证规范化.
|
|
|
|
新机 .env 密钥应为空;示例占位符不得注入 ccxt,否则鉴权失败且
|
|
(尤其 Gate)反复签名请求易触发 IP 封禁.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Optional
|
|
|
|
|
|
_PLACEHOLDER_EXACT = frozenset(
|
|
{
|
|
"你的密钥",
|
|
"your-api-key",
|
|
"your_api_key",
|
|
"your-api-secret",
|
|
"your_api_secret",
|
|
"todo",
|
|
"xxx",
|
|
"changeme",
|
|
}
|
|
)
|
|
|
|
|
|
def normalize_api_credential(value: Optional[str]) -> str:
|
|
"""去空白;空/占位符一律视为未配置."""
|
|
s = (value or "").strip().strip('"').strip("'")
|
|
if not s:
|
|
return ""
|
|
upper = s.upper()
|
|
if upper.startswith("REPLACE_WITH"):
|
|
return ""
|
|
if upper.startswith("CHANGE_TO"):
|
|
return ""
|
|
if s.lower() in _PLACEHOLDER_EXACT:
|
|
return ""
|
|
return s
|
|
|
|
|
|
def credentials_configured(*parts: Optional[str]) -> bool:
|
|
return all(bool(normalize_api_credential(p)) for p in parts)
|
|
|
|
|
|
def is_exchange_auth_error(exc: BaseException) -> bool:
|
|
"""鉴权/无效 Key 类错误(用于停掉后续签名请求,避免 Gate 封 IP)."""
|
|
name = type(exc).__name__
|
|
if name in ("AuthenticationError", "PermissionDenied", "InvalidNonce"):
|
|
return True
|
|
msg = str(exc)
|
|
markers = (
|
|
"Invalid Api-Key",
|
|
"Invalid API-key",
|
|
"Invalid API Key",
|
|
"INVALID_KEY",
|
|
"Invalid key",
|
|
"API key is invalid",
|
|
"api key not found",
|
|
"Signature",
|
|
"INVALID_SIGNATURE",
|
|
"401",
|
|
"-2008",
|
|
"-2014",
|
|
"-2015",
|
|
"10003", # Gate: invalid key often
|
|
"INVALID_KEY",
|
|
)
|
|
low = msg.lower()
|
|
if "api" in low and ("key" in low or "sign" in low) and (
|
|
"invalid" in low or "incorrect" in low or "not found" in low
|
|
):
|
|
return True
|
|
return any(m in msg for m in markers)
|
|
|
|
|
|
def strip_ccxt_credentials(exchange: Any) -> None:
|
|
"""内存中清空密钥,后续只走公开接口,避免继续带坏钥签名."""
|
|
try:
|
|
exchange.apiKey = ""
|
|
except Exception:
|
|
pass
|
|
try:
|
|
exchange.secret = ""
|
|
except Exception:
|
|
pass
|
|
try:
|
|
exchange.password = ""
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def load_markets_public_fallback(exchange: Any, *, reload: bool = False) -> None:
|
|
"""鉴权失败后去掉密钥再拉公开 markets(最多再请求一次)."""
|
|
strip_ccxt_credentials(exchange)
|
|
exchange.load_markets(reload=reload)
|