Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions server_py/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Flask>=3.0.0
flask-cors>=4.0.0
requests>=2.31.0
pysha3>=1.0.5
web3>=7.11.0
Binary file modified server_py/server/__pycache__/chain.cpython-312.pyc
Binary file not shown.
145 changes: 64 additions & 81 deletions server_py/server/chain.py
Original file line number Diff line number Diff line change
@@ -1,102 +1,85 @@
# chain.py
# chain.py —— 直接覆盖此文件
import os
import json
import re
import requests
import json
from decimal import Decimal, getcontext

getcontext().prec = 78 # 大整数/小数精度

# 优先使用 web3 的 ABI 调用;不可用时再走原始 eth_call
_USE_WEB3 = True
# 尝试引入 web3;没有则置为不可用
try:
from web3 import Web3
_USE_WEB3 = True
except Exception:
_USE_WEB3 = False

# ---------- 工具 ----------

def _clean_addr(addr: str) -> str:
if not isinstance(addr, str):
return ""
addr = addr.strip()
if not re.fullmatch(r"0x[0-9a-fA-F]{40}", addr or ""):
return ""
return addr


def _is_active_via_web3(rpc: str, contract_addr: str, address: str) -> bool:
if isinstance(addr, str) and re.fullmatch(r"0x[0-9a-fA-F]{40}", addr or ""):
return addr
return ""

_ABI_CACHE = None
def _load_abi():
"""读取并缓存同目录下 StakePass.abi.json"""
global _ABI_CACHE
if _ABI_CACHE is None:
abi_path = os.path.join(os.path.dirname(__file__), "StakePass.abi.json")
with open(abi_path, "r", encoding="utf-8") as f:
_ABI_CACHE = json.load(f)
return _ABI_CACHE

def _get_contract():
"""返回 (w3, contract) 或 (None, None)"""
if not _USE_WEB3:
return None, None
rpc = os.getenv("RPC") or ""
contract_addr = _clean_addr(os.getenv("CONTRACT", ""))
if not rpc or not contract_addr:
return None, None
w3 = Web3(Web3.HTTPProvider(rpc, request_kwargs={"timeout": 30}))
# checksum 地址(等价于 ethers 的处理)
caddr = Web3.to_checksum_address(contract_addr)
aaddr = Web3.to_checksum_address(address)

# 读取同目录 ABI(与 Node 版一致)
abi_path = os.path.join(os.path.dirname(__file__), "StakePass.abi.json")
with open(abi_path, "r", encoding="utf-8") as f:
abi = json.load(f)

contract = w3.eth.contract(address=caddr, abi=abi)
try:
return bool(contract.functions.isActive(aaddr).call())
except Exception as e:
print("[isActive web3 error]", e)
return False
contract = w3.eth.contract(address=Web3.to_checksum_address(contract_addr), abi=_load_abi())
return w3, contract

# ---------- 业务函数 ----------

# 原始 eth_call 作为兜底(尽量不走它)
def _keccak_selector(signature: str) -> str:
try:
import sha3 # pysha3
return sha3.keccak_256(signature.encode()).digest()[:4].hex()
except Exception:
# isActive(address) 的选择器常量
return "3c610c22"
def stake_amount(address: str) -> dict:
"""
返回当前地址的质押总额(基于 balanceOf(address)):
{ "raw": "uint256", "unit": "uint256", "formatted": "字符串小数" }
"""
address = _clean_addr(address)
if not address:
return {"raw": "0", "unit": "1", "formatted": "0"}

w3, c = _get_contract()
if not c:
# web3 不可用或环境变量缺失
return {"raw": "0", "unit": "1", "formatted": "0"}

def _is_active_via_eth_call(rpc: str, contract_addr: str, address: str) -> bool:
selector = _keccak_selector("isActive(address)")
# address 是静态类型,20 字节,左填充到 32 字节
data = "0x" + selector + address[2:].rjust(64, "0")
a = Web3.to_checksum_address(address)
amt = int(c.functions.balanceOf(a).call())

# 读取单位:优先 unit(),其次 UNIT(),最后兜底 1e18
try:
r = requests.post(
rpc,
json={
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{"to": contract_addr, "data": data}, "latest"],
"id": 1,
},
timeout=30,
)
r.raise_for_status()
result = r.json().get("result")
# 部分节点在异常/找不到函数时可能返回 "0x" 或空,这里统一按 False
if not isinstance(result, str) or not result.startswith("0x") or len(result) < 3:
return False
# bool 编码为 32 字节,非零即 True
return int(result, 16) != 0
except Exception as e:
print("[isActive eth_call error]", getattr(e, "response", None) or e)
return False
unit = int(c.functions.unit().call())
except Exception:
try:
unit = int(c.functions.UNIT().call())
except Exception:
unit = 10 ** 18

formatted = str(Decimal(amt) / Decimal(unit)) if unit else str(amt)
return {"raw": str(amt), "unit": str(unit), "formatted": formatted}

def isActive(address: str) -> bool:
address = _clean_addr(address)
rpc = os.getenv("RPC") or ""
contract_addr = _clean_addr(os.getenv("CONTRACT", ""))

if not address or not rpc or not contract_addr:
"""
是否解锁:balanceOf(address) >= unit
与合约 StakePassFixed::isActive 等价
"""
info = stake_amount(address)
try:
return int(info["raw"]) >= int(info["unit"])
except Exception:
return False

# DEBUG:需要时打印原始调用信息
if os.getenv("DEBUG_CHAIN") == "1":
print("[DEBUG_CHAIN] rpc:", rpc)
print("[DEBUG_CHAIN] contract:", contract_addr)
print("[DEBUG_CHAIN] address:", address)

if _USE_WEB3:
ok = _is_active_via_web3(rpc, contract_addr, address)
if ok:
return True
# web3 路径失败时尝试一次 eth_call 兜底
return _is_active_via_eth_call(rpc, contract_addr, address)
else:
return _is_active_via_eth_call(rpc, contract_addr, address)
17 changes: 15 additions & 2 deletions server_py/server/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
from chain import isActive
from aiProxy import callAI

from chain import isActive, stake_amount



# === DB ===
from db import db_session, engine
from models import init_db, Topic, Message, now_utc
Expand Down Expand Up @@ -69,8 +73,17 @@ def access_status():
return jsonify({"ok": False, "msg": "address required"}), 400
active = isActive(address)
used = _daily_usage.get(f"{dayKey()}:{address}", 0)
daily_limit = int(os.getenv("DAILY_TOKENS", "20000"))
return jsonify({"ok": True, "data": {"active": active, "used": used, "daily_limit": daily_limit}})
stake_amounts = stake_amount(address)
use_number = float(stake_amounts["formatted"]) * 10000 # 使用的数量
return jsonify({"ok": True, "data": {"active": active, "used": used, "daily_limit": use_number}})

@app.get("/stake/amount")
def read_stake_amount():
addr = (request.args.get("address") or request.headers.get("x-address") or "").strip()
if not addr:
return jsonify({"ok": False, "msg": "address required"}), 400
return jsonify({"ok": True, "data": stake_amount(addr)})


# ===== 主题:创建 / 列表 / 重命名 / 归档 =====

Expand Down