-
+
PolyWeather Pro
-
+
{isEn
- ? "Full access to the institutional weather-signal terminal. Live METAR, DEB forecasts, probability distribution, AI decision cards, and real-time alerts."
- : "完整访问机构级天气信号终端。实时 METAR、DEB 预报、概率分布、AI 决策卡片、实时通知。"}
+ ? "Full access to the institutional weather intelligence workspace. Live METAR, DEB forecasts, probability distribution, AI decision cards, and real-time alerts."
+ : "完整访问机构级天气决策分析台。实时 METAR、DEB 预报、概率分布、AI 决策卡片、实时通知。"}
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts
index a6206b44..c43677f5 100644
--- a/frontend/tailwind.config.ts
+++ b/frontend/tailwind.config.ts
@@ -77,6 +77,26 @@ const config: Config = {
"elevation-2": "var(--shadow-elevation-2)",
"elevation-3": "var(--shadow-elevation-3)",
},
+ keyframes: {
+ "fade-up": {
+ "0%": { opacity: "0", transform: "translateY(20px)" },
+ "100%": { opacity: "1", transform: "translateY(0)" },
+ },
+ "fade-in": {
+ "0%": { opacity: "0" },
+ "100%": { opacity: "1" },
+ },
+ gradient: {
+ "0%": { backgroundPosition: "0% 50%" },
+ "50%": { backgroundPosition: "100% 50%" },
+ "100%": { backgroundPosition: "0% 50%" },
+ },
+ },
+ animation: {
+ "fade-up": "fade-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards",
+ "fade-in": "fade-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards",
+ gradient: "gradient 8s ease infinite",
+ },
},
},
plugins: [],
diff --git a/scripts/debug_market_scan.py b/scripts/debug_market_scan.py
deleted file mode 100644
index 7e4a9990..00000000
--- a/scripts/debug_market_scan.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import importlib
-import json
-import os
-import sys
-from pathlib import Path
-from typing import Any, Dict, Optional
-
-
-PROJECT_ROOT = Path(__file__).resolve().parents[1]
-if str(PROJECT_ROOT) not in sys.path:
- sys.path.insert(0, str(PROJECT_ROOT))
-
-
-def _safe_float(value: Optional[str]) -> Optional[float]:
- if value is None:
- return None
- try:
- return float(value)
- except Exception:
- return None
-
-
-def _bucket_from_temp(value: Optional[str]) -> Optional[Dict[str, Any]]:
- temp = _safe_float(value)
- if temp is None:
- return None
- return {
- "value": temp,
- "temp": temp,
- "label": f"{temp:g}°C",
- }
-
-
-def main() -> int:
- parser = argparse.ArgumentParser(
- description="Print a read-only Polymarket market-scan diagnostic payload.",
- )
- parser.add_argument("--city", required=True, help="City key or display name.")
- parser.add_argument("--date", required=True, help="Target date, YYYY-MM-DD.")
- parser.add_argument(
- "--bucket-temp",
- help="Optional model/probability bucket temperature in Celsius.",
- )
- parser.add_argument(
- "--model-probability",
- help="Optional model probability. Accepts 0-1 or 0-100.",
- )
- parser.add_argument(
- "--market-slug",
- help="Optional Polymarket event/market slug.",
- )
- parser.add_argument(
- "--debug-log",
- action="store_true",
- help="Also enable POLYMARKET_MARKET_SCAN_DEBUG for this process.",
- )
- args = parser.parse_args()
-
- if args.debug_log:
- os.environ["POLYMARKET_MARKET_SCAN_DEBUG"] = "true"
-
- model_probability = _safe_float(args.model_probability)
- if model_probability is not None and model_probability > 1.0:
- model_probability = model_probability / 100.0
-
- module = importlib.import_module("src.data_collection.polymarket_readonly")
- layer = module.PolymarketReadOnlyLayer()
- scan = layer.build_market_scan(
- city=args.city,
- target_date=args.date,
- temperature_bucket=_bucket_from_temp(args.bucket_temp),
- model_probability=model_probability,
- forced_market_slug=args.market_slug,
- )
-
- print(json.dumps(scan, ensure_ascii=False, indent=2, default=str))
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/src/bot/runtime_coordinator.py b/src/bot/runtime_coordinator.py
index 3da7d9b7..fa9e6ee5 100644
--- a/src/bot/runtime_coordinator.py
+++ b/src/bot/runtime_coordinator.py
@@ -84,7 +84,6 @@ class StartupCoordinator:
def start_all(self) -> RuntimeStatus:
loops = [
self._start_airport_high_freq_loop(),
- self._start_polygon_wallet_loop(),
self._start_weekly_reward_loop(),
self._start_payment_event_loop(),
self._start_payment_confirm_loop(),
@@ -188,36 +187,6 @@ class StartupCoordinator:
),
)
- def _start_polygon_wallet_loop(self) -> LoopStatus:
- enabled = _env_bool("POLYGON_WALLET_WATCH_ENABLED", False)
- chat_ids = get_telegram_chat_ids_from_env()
- rpc_url = str(os.getenv("POLYGON_RPC_URL") or "").strip()
- wallets_count = _parse_csv_count(os.getenv("POLYGON_WALLET_WATCH_ADDRESSES"))
- poll = max(3, _env_int("POLYGON_WALLET_WATCH_INTERVAL_SEC", 8))
- details = {
- "poll_sec": poll,
- "wallets_count": wallets_count,
- "polymarket_only": _env_bool("POLYGON_WALLET_WATCH_POLYMARKET_ONLY", True),
- "chat_targets": len(chat_ids),
- }
- validation_error = None
- if not chat_ids:
- validation_error = "missing_TELEGRAM_CHAT_IDS"
- elif not rpc_url:
- validation_error = "missing_POLYGON_RPC_URL"
- elif wallets_count == 0:
- validation_error = "missing_POLYGON_WALLET_WATCH_ADDRESSES"
- return self._start_with_validation(
- key="polygon_wallet_watch",
- label="Polygon 钱包监听",
- configured_enabled=enabled,
- details=details,
- validation_error=validation_error,
- starter=lambda: import_module(
- "src.onchain.polygon_wallet_watcher"
- ).start_polygon_wallet_watch_loop(self.bot),
- )
-
def _start_weekly_reward_loop(self) -> LoopStatus:
enabled = _env_bool("POLYWEATHER_WEEKLY_REWARD_ENABLED", True)
chat_ids = get_telegram_chat_ids_from_env()
diff --git a/src/onchain/polygon_wallet_watcher.py b/src/onchain/polygon_wallet_watcher.py
deleted file mode 100644
index fc58a916..00000000
--- a/src/onchain/polygon_wallet_watcher.py
+++ /dev/null
@@ -1,549 +0,0 @@
-import json
-import os
-import threading
-import time
-from datetime import datetime, timezone
-from decimal import Decimal, InvalidOperation
-from typing import Any, Dict, List, Optional, Set, Tuple
-
-from loguru import logger
-from web3 import Web3
-
-from src.utils.telegram_chat_ids import get_telegram_chat_ids_from_env
-
-TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
-APPROVAL_TOPIC = "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
-ERC20_ABI = [
- {
- "constant": True,
- "inputs": [],
- "name": "symbol",
- "outputs": [{"name": "", "type": "string"}],
- "type": "function",
- },
- {
- "constant": True,
- "inputs": [],
- "name": "decimals",
- "outputs": [{"name": "", "type": "uint8"}],
- "type": "function",
- },
-]
-
-# Source: Polymarket official developer docs (Polygon contract addresses)
-# https://docs.polymarket.com/developers/market-makers/setup
-DEFAULT_POLYMARKET_CONTRACTS: Dict[str, str] = {
- "USDC": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
- "pUSD": "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb",
- "CTF": "0x4d97dcd97ec945f40cf65f87097ace5ea0476045",
- "CTF_EXCHANGE": "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E",
- "NEG_RISK_CTF_EXCHANGE": "0xC5d563A36AE78145C45a50134d48A1215220f80a",
- "NEG_RISK_ADAPTER": "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296",
-}
-
-
-def _env_bool(name: str, default: bool) -> bool:
- raw = os.getenv(name)
- if raw is None:
- return default
- return raw.strip().lower() in {"1", "true", "yes", "on"}
-
-
-def _env_int(name: str, default: int) -> int:
- raw = os.getenv(name)
- if raw is None:
- return default
- try:
- return int(raw)
- except Exception:
- return default
-
-
-def _short(addr: str, left: int = 6, right: int = 4) -> str:
- if not addr:
- return "unknown"
- if len(addr) <= left + right + 2:
- return addr
- return f"{addr[:left + 2]}...{addr[-right:]}"
-
-
-def _state_file() -> str:
- root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- return os.path.join(root, "data", "polygon_wallet_watch_state.json")
-
-
-def _load_state(path: str) -> Dict[str, Any]:
- if not os.path.exists(path):
- return {"last_scanned_block": 0, "seen_tx": {}}
- try:
- with open(path, "r", encoding="utf-8") as fh:
- data = json.load(fh)
- if isinstance(data, dict):
- data.setdefault("last_scanned_block", 0)
- data.setdefault("seen_tx", {})
- return data
- except Exception as exc:
- logger.warning(f"failed to load polygon watch state: {exc}")
- return {"last_scanned_block": 0, "seen_tx": {}}
-
-
-def _save_state(path: str, state: Dict[str, Any]) -> None:
- os.makedirs(os.path.dirname(path), exist_ok=True)
- tmp_path = f"{path}.tmp"
- with open(tmp_path, "w", encoding="utf-8") as fh:
- json.dump(state, fh, ensure_ascii=False, indent=2)
- os.replace(tmp_path, path)
-
-
-def _cleanup_seen_tx(state: Dict[str, Any], now_ts: int, keep_sec: int) -> None:
- seen = state.get("seen_tx", {})
- if not isinstance(seen, dict):
- state["seen_tx"] = {}
- return
- stale = [key for key, value in seen.items() if now_ts - int(value or 0) > keep_sec]
- for tx_hash in stale:
- seen.pop(tx_hash, None)
-
-
-def _normalize_addr(value: Any) -> str:
- if value is None:
- return ""
- text = str(value).strip().lower()
- if text.startswith("0x") and len(text) == 42:
- return text
- return ""
-
-
-def _parse_addresses(raw: Optional[str]) -> Set[str]:
- out: Set[str] = set()
- if not raw:
- return out
- for part in raw.split(","):
- addr = _normalize_addr(part)
- if addr:
- out.add(addr)
- return out
-
-
-def _parse_polymarket_contracts(raw: Optional[str]) -> Dict[str, str]:
- """
- Parse env like:
- - "0xabc...,0xdef..."
- - "CTF:0xabc...,EXCHANGE:0xdef..."
- """
- result: Dict[str, str] = {}
- if not raw:
- return result
- for part in raw.split(","):
- segment = str(part).strip()
- if not segment:
- continue
- label = "CUSTOM_PM"
- address_part = segment
- if ":" in segment:
- maybe_label, maybe_addr = segment.split(":", 1)
- maybe_addr_n = _normalize_addr(maybe_addr)
- if maybe_addr_n:
- label = (maybe_label or "CUSTOM_PM").strip() or "CUSTOM_PM"
- address_part = maybe_addr_n
- addr = _normalize_addr(address_part)
- if not addr:
- continue
- if addr not in result:
- result[addr] = label
- return result
-
-
-def _build_polymarket_contract_map() -> Dict[str, str]:
- include_defaults = _env_bool("POLYGON_WALLET_WATCH_INCLUDE_DEFAULT_PM_CONTRACTS", True)
- merged: Dict[str, str] = {}
-
- if include_defaults:
- for label, addr in DEFAULT_POLYMARKET_CONTRACTS.items():
- normalized = _normalize_addr(addr)
- if normalized:
- merged[normalized] = label
-
- custom = _parse_polymarket_contracts(os.getenv("POLYGON_WALLET_WATCH_POLYMARKET_CONTRACTS"))
- for addr, label in custom.items():
- merged[addr] = label
-
- return merged
-
-
-def _polygon_scan_tx_url(tx_hash: str) -> str:
- base = os.getenv("POLYGON_WALLET_WATCH_TX_BASE") or "https://polygonscan.com/tx/"
- return f"{base.rstrip('/')}/{tx_hash}"
-
-
-def _polygon_scan_addr_url(address: str) -> str:
- base = os.getenv("POLYGON_WALLET_WATCH_ADDR_BASE") or "https://polygonscan.com/address/"
- return f"{base.rstrip('/')}/{address}"
-
-
-def _format_matic(wei_value: int) -> str:
- try:
- matic = Decimal(wei_value) / Decimal(10**18)
- except (InvalidOperation, ValueError):
- return "0"
-
- if matic == matic.to_integral_value():
- return f"{int(matic)}"
- return f"{matic.normalize():f}".rstrip("0").rstrip(".")
-
-
-def _format_amount(amount: Decimal) -> str:
- if amount == amount.to_integral_value():
- return str(int(amount))
- return f"{amount.normalize():f}".rstrip("0").rstrip(".")
-
-
-def _safe_lower(value: Any) -> str:
- if value is None:
- return ""
- return str(value).lower()
-
-
-def _topic_to_addr(topic: Any) -> str:
- try:
- return _normalize_addr("0x" + topic.hex()[-40:])
- except Exception:
- return ""
-
-
-def _get_token_meta(
- w3: Web3,
- token_addr: str,
- token_meta_cache: Dict[str, Tuple[str, int]],
-) -> Tuple[str, int]:
- symbol, decimals = token_meta_cache.get(token_addr, ("ERC20", 18))
- if token_addr in token_meta_cache:
- return symbol, decimals
-
- try:
- token = w3.eth.contract(address=Web3.to_checksum_address(token_addr), abi=ERC20_ABI)
- symbol_raw = token.functions.symbol().call()
- decimals_raw = token.functions.decimals().call()
- symbol = str(symbol_raw or "ERC20")
- decimals = int(decimals_raw)
- except Exception:
- symbol = "ERC20"
- decimals = 18
-
- token_meta_cache[token_addr] = (symbol, decimals)
- return symbol, decimals
-
-
-def _extract_receipt_signals(
- w3: Web3,
- receipt: Any,
- watch_set: Set[str],
- pm_contracts: Dict[str, str],
- token_meta_cache: Dict[str, Tuple[str, int]],
-) -> Dict[str, Any]:
- transfer_lines: List[str] = []
- approval_lines: List[str] = []
- touched_labels: Set[str] = set()
- pm_hit = False
-
- for log in receipt.logs or []:
- try:
- log_addr = _normalize_addr(log.address)
- if log_addr in pm_contracts:
- pm_hit = True
- touched_labels.add(pm_contracts[log_addr])
-
- topics = log.topics or []
- if not topics:
- continue
- topic0 = topics[0].hex().lower()
-
- if topic0 == TRANSFER_TOPIC and len(topics) >= 3:
- from_addr = _topic_to_addr(topics[1])
- to_addr = _topic_to_addr(topics[2])
- if from_addr not in watch_set and to_addr not in watch_set:
- continue
-
- other_addr = to_addr if from_addr in watch_set else from_addr
- other_label = pm_contracts.get(other_addr)
- if other_label:
- pm_hit = True
- touched_labels.add(other_label)
-
- symbol, decimals = _get_token_meta(w3, log_addr, token_meta_cache)
- amount_int = int(log.data.hex(), 16) if log.data else 0
- amount = Decimal(amount_int) / (Decimal(10) ** Decimal(max(decimals, 0)))
-
- if from_addr in watch_set and to_addr in watch_set:
- direction = "SELF"
- elif to_addr in watch_set:
- direction = "IN"
- else:
- direction = "OUT"
-
- # Keep transfer line only when it is clearly Polymarket related.
- if other_label or log_addr in pm_contracts:
- target = other_label or pm_contracts.get(log_addr) or _short(other_addr)
- transfer_lines.append(
- f"- {direction} {symbol}: {_format_amount(amount)} (对手: {target})"
- )
-
- if topic0 == APPROVAL_TOPIC and len(topics) >= 3:
- owner = _topic_to_addr(topics[1])
- spender = _topic_to_addr(topics[2])
- if owner not in watch_set:
- continue
- spender_label = pm_contracts.get(spender)
- if not spender_label:
- continue
-
- pm_hit = True
- touched_labels.add(spender_label)
-
- symbol, decimals = _get_token_meta(w3, log_addr, token_meta_cache)
- amount_int = int(log.data.hex(), 16) if log.data else 0
- amount = Decimal(amount_int) / (Decimal(10) ** Decimal(max(decimals, 0)))
- approval_lines.append(
- f"- APPROVE {symbol}: {_format_amount(amount)} -> {spender_label}"
- )
- except Exception:
- continue
-
- return {
- "pm_hit": pm_hit,
- "transfer_lines": transfer_lines,
- "approval_lines": approval_lines,
- "touched_labels": sorted(touched_labels),
- }
-
-
-def _build_message(
- tx: Any,
- block_ts: int,
- matched_wallet: str,
- touched_labels: List[str],
- transfer_lines: List[str],
- approval_lines: List[str],
- tx_to_label: Optional[str],
-) -> str:
- tx_hash = tx["hash"].hex()
- from_addr = _safe_lower(tx.get("from"))
- to_addr = _safe_lower(tx.get("to"))
-
- if from_addr == matched_wallet and to_addr == matched_wallet:
- direction = "SELF"
- elif to_addr == matched_wallet:
- direction = "IN"
- elif from_addr == matched_wallet:
- direction = "OUT"
- else:
- direction = "RELATED"
-
- matic_value = int(tx.get("value", 0) or 0)
- block_time = datetime.fromtimestamp(block_ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
- selector = str(tx.get("input") or "")[:10] if tx.get("input") else "0x"
-
- lines = [
- "⛓ Polymarket 钱包动作",
- f"钱包: {_short(matched_wallet)}",
- f"方向: {direction}",
- f"MATIC: {_format_matic(matic_value)}",
- f"区块: {tx.get('blockNumber')}",
- f"时间: {block_time}",
- f"方法选择器: {selector}",
- ]
-
- if tx_to_label:
- lines.append(f"直连合约: {tx_to_label}")
-
- if touched_labels:
- lines.append(f"相关合约: {', '.join(touched_labels)}")
-
- if transfer_lines:
- lines.append("Token 动作:")
- lines.extend(transfer_lines[:6])
-
- if approval_lines:
- lines.append("授权动作:")
- lines.extend(approval_lines[:4])
-
- lines.append(f"Tx: {tx_hash}")
- lines.append(f"交易链接: {_polygon_scan_tx_url(tx_hash)}")
- lines.append(f"钱包链接: {_polygon_scan_addr_url(matched_wallet)}")
- return "\n".join(lines)
-
-
-def start_polygon_wallet_watch_loop(bot: Any) -> Optional[threading.Thread]:
- enabled = _env_bool("POLYGON_WALLET_WATCH_ENABLED", False)
- chat_ids = get_telegram_chat_ids_from_env()
- rpc_url = os.getenv("POLYGON_RPC_URL")
- watch_set = _parse_addresses(os.getenv("POLYGON_WALLET_WATCH_ADDRESSES"))
- polymarket_only = _env_bool("POLYGON_WALLET_WATCH_POLYMARKET_ONLY", True)
- pm_contracts = _build_polymarket_contract_map()
-
- if not enabled:
- logger.info("polygon wallet watcher disabled")
- return None
- if not chat_ids:
- logger.warning("polygon wallet watcher skipped: TELEGRAM_CHAT_IDS is not set")
- return None
- if not rpc_url:
- logger.warning("polygon wallet watcher skipped: POLYGON_RPC_URL is not set")
- return None
- if not watch_set:
- logger.warning("polygon wallet watcher skipped: POLYGON_WALLET_WATCH_ADDRESSES is empty")
- return None
- if polymarket_only and not pm_contracts:
- logger.warning("polygon wallet watcher skipped: no polymarket contracts configured")
- return None
-
- poll_sec = max(3, _env_int("POLYGON_WALLET_WATCH_INTERVAL_SEC", 8))
- confirmations = max(0, _env_int("POLYGON_WALLET_WATCH_CONFIRMATIONS", 2))
- max_blocks_per_cycle = max(1, _env_int("POLYGON_WALLET_WATCH_MAX_BLOCKS_PER_CYCLE", 30))
- seen_ttl_sec = max(3600, _env_int("POLYGON_WALLET_WATCH_SEEN_TTL_SEC", 7 * 86400))
- state_path = _state_file()
-
- provider_timeout = max(5, _env_int("POLYGON_WALLET_WATCH_RPC_TIMEOUT_SEC", 10))
- w3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": provider_timeout}))
-
- def _runner() -> None:
- token_meta_cache: Dict[str, Tuple[str, int]] = {}
- state = _load_state(state_path)
-
- if not w3.is_connected():
- logger.error("polygon wallet watcher failed: cannot connect to POLYGON_RPC_URL")
- return
-
- try:
- chain_id = int(w3.eth.chain_id)
- if chain_id != 137:
- logger.warning(f"polygon wallet watcher connected to unexpected chain_id={chain_id}")
- except Exception as exc:
- logger.warning(f"polygon wallet watcher cannot read chain id: {exc}")
-
- latest_block = int(w3.eth.block_number)
- if int(state.get("last_scanned_block") or 0) <= 0:
- state["last_scanned_block"] = max(0, latest_block - confirmations)
- _save_state(state_path, state)
-
- logger.info(
- f"polygon wallet watcher started wallets={len(watch_set)} "
- f"polymarket_only={polymarket_only} pm_contracts={len(pm_contracts)} "
- f"poll={poll_sec}s confirmations={confirmations} chat_targets={len(chat_ids)} "
- f"state_path={state_path}"
- )
-
- while True:
- cycle_ts = int(time.time())
- try:
- _cleanup_seen_tx(state, cycle_ts, seen_ttl_sec)
-
- latest = int(w3.eth.block_number)
- safe_latest = latest - confirmations
- last_scanned = int(state.get("last_scanned_block") or 0)
-
- if safe_latest <= last_scanned:
- time.sleep(poll_sec)
- continue
-
- from_block = last_scanned + 1
- to_block = min(safe_latest, from_block + max_blocks_per_cycle - 1)
-
- for block_num in range(from_block, to_block + 1):
- block = w3.eth.get_block(block_num, full_transactions=True)
- block_ts = int(block.get("timestamp") or cycle_ts)
-
- for tx in block.transactions or []:
- tx_hash = tx["hash"].hex().lower()
- from_addr = _safe_lower(tx.get("from"))
- to_addr = _safe_lower(tx.get("to"))
-
- matched_wallet = ""
- if from_addr in watch_set:
- matched_wallet = from_addr
- elif to_addr in watch_set:
- matched_wallet = to_addr
-
- if not matched_wallet:
- continue
-
- if tx_hash in state.get("seen_tx", {}):
- continue
-
- tx_to = _normalize_addr(tx.get("to"))
- tx_to_label = pm_contracts.get(tx_to)
- pm_hit = bool(tx_to_label)
- transfer_lines: List[str] = []
- approval_lines: List[str] = []
- touched_labels: List[str] = [tx_to_label] if tx_to_label else []
-
- try:
- receipt = w3.eth.get_transaction_receipt(tx["hash"])
- parsed = _extract_receipt_signals(
- w3=w3,
- receipt=receipt,
- watch_set=watch_set,
- pm_contracts=pm_contracts,
- token_meta_cache=token_meta_cache,
- )
- pm_hit = pm_hit or bool(parsed.get("pm_hit"))
- transfer_lines = parsed.get("transfer_lines") or []
- approval_lines = parsed.get("approval_lines") or []
- touched = parsed.get("touched_labels") or []
- touched_labels = sorted(set(touched_labels + touched))
- except Exception:
- transfer_lines = []
- approval_lines = []
-
- if polymarket_only and not pm_hit:
- continue
-
- message = _build_message(
- tx=tx,
- block_ts=block_ts,
- matched_wallet=matched_wallet,
- touched_labels=touched_labels,
- transfer_lines=transfer_lines,
- approval_lines=approval_lines,
- tx_to_label=tx_to_label,
- )
- sent_count = 0
- for chat_id in chat_ids:
- try:
- bot.send_message(
- chat_id,
- message,
- disable_web_page_preview=True,
- )
- sent_count += 1
- except Exception as exc:
- logger.warning(
- "polygon wallet alert push failed wallet={} chat_id={} error={}",
- matched_wallet,
- chat_id,
- exc,
- )
- if sent_count <= 0:
- continue
- state.setdefault("seen_tx", {})[tx_hash] = cycle_ts
- logger.info(
- f"polygon wallet alert pushed wallet={matched_wallet} "
- f"tx={tx_hash} block={block_num} polymarket={pm_hit} chat_targets={sent_count}"
- )
-
- state["last_scanned_block"] = block_num
- _save_state(state_path, state)
-
- time.sleep(1)
- except Exception:
- logger.exception("polygon wallet watcher cycle failed")
- time.sleep(poll_sec)
-
- thread = threading.Thread(
- target=_runner,
- name="polygon-wallet-watcher",
- daemon=True,
- )
- thread.start()
- return thread
-
diff --git a/src/payments/contract_checkout.py b/src/payments/contract_checkout.py
index a4b92e42..55c45271 100644
--- a/src/payments/contract_checkout.py
+++ b/src/payments/contract_checkout.py
@@ -22,7 +22,6 @@ DEFAULT_POLYGON_CHAIN_ID = 137
DEFAULT_USDC_E_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
DEFAULT_NATIVE_USDC_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
DEFAULT_USDT_ADDRESS = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"
-DEFAULT_PUSD_ADDRESS = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb"
PAYMENT_CONTRACT_ABI = [
{
@@ -392,8 +391,6 @@ class PaymentContractCheckoutService:
return {"code": "usdc", "symbol": "USDC", "name": "Native USDC"}
if normalized == _normalize_address(DEFAULT_USDT_ADDRESS):
return {"code": "usdt", "symbol": "USDT", "name": "USDT"}
- if normalized == _normalize_address(DEFAULT_PUSD_ADDRESS):
- return {"code": "pusd", "symbol": "pUSD", "name": "Polymarket pUSD"}
if normalized == _normalize_address(DEFAULT_USDC_E_ADDRESS):
return {"code": "usdc_e", "symbol": "USDC.e", "name": "USDC.e (PoS)"}
short = f"{normalized[:6]}...{normalized[-4:]}"
diff --git a/src/utils/config_validation.py b/src/utils/config_validation.py
index 85c73b09..e834d4f9 100644
--- a/src/utils/config_validation.py
+++ b/src/utils/config_validation.py
@@ -71,7 +71,6 @@ def validate_runtime_env(
entitlement_guard = _env_bool("POLYWEATHER_REQUIRE_ENTITLEMENT", False)
payment_enabled = _env_bool("POLYWEATHER_PAYMENT_ENABLED", False)
weekly_reward_enabled = _env_bool("POLYWEATHER_WEEKLY_REWARD_ENABLED", False)
- polygon_watch_enabled = _env_bool("POLYGON_WALLET_WATCH_ENABLED", False)
if component_key == "bot":
missing = _missing(["TELEGRAM_BOT_TOKEN"])
@@ -111,11 +110,6 @@ def validate_runtime_env(
"已启用支付,但未配置 POLYWEATHER_PAYMENT_RECEIVER_CONTRACT 或 POLYWEATHER_PAYMENT_ACCEPTED_TOKENS_JSON"
)
- if polygon_watch_enabled:
- if not _has("POLYGON_WALLET_WATCH_ADDRESSES"):
- report.warnings.append(
- "已启用 polygon watcher,但未配置 POLYGON_WALLET_WATCH_ADDRESSES"
- )
if component_key == "web":
if auth_enabled and not auth_required:
diff --git a/tests/test_bot_runtime_coordinator.py b/tests/test_bot_runtime_coordinator.py
index 570e1e60..3bbfd76d 100644
--- a/tests/test_bot_runtime_coordinator.py
+++ b/tests/test_bot_runtime_coordinator.py
@@ -6,7 +6,6 @@ class DummyBot:
def test_startup_coordinator_respects_disable_flags(monkeypatch):
- monkeypatch.setenv("POLYGON_WALLET_WATCH_ENABLED", "false")
monkeypatch.delenv("TELEGRAM_CHAT_ID", raising=False)
coordinator = StartupCoordinator(
@@ -19,7 +18,8 @@ def test_startup_coordinator_respects_disable_flags(monkeypatch):
runtime = coordinator.start_all()
loop_map = runtime.loop_map()
- assert loop_map["polygon_wallet_watch"].reason == "disabled_by_env"
+ assert "weekly_reward" in loop_map
+ assert "polygon_wallet_watch" not in loop_map
def test_render_runtime_status_html_contains_key_fields():