410 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			410 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import requests
 | 
						||
import csv
 | 
						||
from datetime import datetime
 | 
						||
import subprocess
 | 
						||
import time
 | 
						||
import logging
 | 
						||
 | 
						||
# 配置常量
 | 
						||
GIT_INTERVAL = 86400  # Git提交间隔(秒)
 | 
						||
STATS_INTERVAL = 3600  # BTC Plus统计数据获取间隔(秒)
 | 
						||
REWARDS_INTERVAL = 86400  # BTC Plus奖励数据获取间隔(秒)
 | 
						||
ALLOCATIONS_INTERVAL = 3600  # BTC Plus分配数据获取间隔(秒)
 | 
						||
POINTS_INTERVAL = 86400  # Phase2积分系统数据获取间隔(秒)
 | 
						||
FETCH_INTERVAL = 600  # 数据获取间隔(秒)
 | 
						||
LOG_FILE = "solv_btc_plus.log"
 | 
						||
 | 
						||
 | 
						||
def timestamp_to_datetime(timestamp):
 | 
						||
    """将时间戳转换为可读时间字符串"""
 | 
						||
    return (
 | 
						||
        datetime.fromtimestamp(timestamp).isoformat()
 | 
						||
        if timestamp
 | 
						||
        else datetime.now().isoformat()
 | 
						||
    )
 | 
						||
 | 
						||
 | 
						||
def get_current_time():
 | 
						||
    """获取当前时间字符串"""
 | 
						||
    return datetime.now().isoformat()
 | 
						||
 | 
						||
 | 
						||
def fetch_btc_plus_reward(address, stage_no=1):
 | 
						||
    """从API获取BTC Plus奖励数据"""
 | 
						||
    url = "https://graphql.sft-api.com/graphql"
 | 
						||
 | 
						||
    headers = {
 | 
						||
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
 | 
						||
        "Accept": "*/*",
 | 
						||
        "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
 | 
						||
        "Accept-Encoding": "gzip, deflate, br, zstd",
 | 
						||
        "Referer": "https://app.solv.finance/",
 | 
						||
        "content-type": "application/json",
 | 
						||
        "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined",
 | 
						||
        "x-amz-user-agent": "aws-amplify/3.0.7",
 | 
						||
        "Origin": "https://app.solv.finance",
 | 
						||
        "DNT": "1",
 | 
						||
        "Connection": "keep-alive",
 | 
						||
        "Sec-Fetch-Dest": "empty",
 | 
						||
        "Sec-Fetch-Mode": "cors",
 | 
						||
        "Sec-Fetch-Site": "cross-site",
 | 
						||
        "Sec-GPC": "1",
 | 
						||
        "Priority": "u=4",
 | 
						||
    }
 | 
						||
 | 
						||
    query = """
 | 
						||
    query BtcPlusRewardByAddress($address: String!, $stageNo: Int!) {
 | 
						||
      btcPlusRewardByAddress(address: $address, stageNo: $stageNo) {
 | 
						||
        balance
 | 
						||
        balanceUSD
 | 
						||
        rewardPower
 | 
						||
        currentTotalRewardPower
 | 
						||
        estimatedReward
 | 
						||
        __typename
 | 
						||
      }
 | 
						||
    }
 | 
						||
    """
 | 
						||
 | 
						||
    variables = {"stageNo": stage_no, "address": address}
 | 
						||
 | 
						||
    payload = {
 | 
						||
        "operationName": "BtcPlusRewardByAddress",
 | 
						||
        "query": query,
 | 
						||
        "variables": variables,
 | 
						||
    }
 | 
						||
 | 
						||
    response = requests.post(url, headers=headers, json=payload)
 | 
						||
    response.raise_for_status()
 | 
						||
    data = response.json()
 | 
						||
    data["timestamp"] = get_current_time()  # 添加当前时间
 | 
						||
    return data
 | 
						||
 | 
						||
 | 
						||
def fetch_btc_plus_allocations():
 | 
						||
    """从API获取BTC Plus分配数据"""
 | 
						||
    url = "https://graphql.sft-api.com/graphql"
 | 
						||
    headers = {
 | 
						||
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
 | 
						||
        "Accept": "*/*",
 | 
						||
        "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
 | 
						||
        "Accept-Encoding": "gzip, deflate, br, zstd",
 | 
						||
        "Referer": "https://app.solv.finance/",
 | 
						||
        "content-type": "application/json",
 | 
						||
        "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined",
 | 
						||
        "x-amz-user-agent": "aws-amplify/3.0.7",
 | 
						||
        "Origin": "https://app.solv.finance",
 | 
						||
        "DNT": "1",
 | 
						||
        "Connection": "keep-alive",
 | 
						||
        "Sec-Fetch-Dest": "empty",
 | 
						||
        "Sec-Fetch-Mode": "cors",
 | 
						||
        "Sec-Fetch-Site": "cross-site",
 | 
						||
        "Sec-GPC": "1",
 | 
						||
        "Priority": "u=4",
 | 
						||
        "Pragma": "no-cache",
 | 
						||
        "Cache-Control": "no-cache",
 | 
						||
        "TE": "trailers",
 | 
						||
    }
 | 
						||
 | 
						||
    query = """
 | 
						||
    query BtcPlusAllocations {
 | 
						||
      btcPlusAllocations {
 | 
						||
        tvl
 | 
						||
        allocations {
 | 
						||
          assetName
 | 
						||
          percentage
 | 
						||
          color
 | 
						||
          __typename
 | 
						||
        }
 | 
						||
        __typename
 | 
						||
      }
 | 
						||
    }
 | 
						||
    """
 | 
						||
 | 
						||
    payload = {
 | 
						||
        "operationName": "BtcPlusAllocations",
 | 
						||
        "query": query,
 | 
						||
        "variables": {},
 | 
						||
    }
 | 
						||
 | 
						||
    response = requests.post(url, headers=headers, json=payload)
 | 
						||
    response.raise_for_status()
 | 
						||
    data = response.json()
 | 
						||
    data["timestamp"] = get_current_time()  # 添加当前时间
 | 
						||
    return data
 | 
						||
 | 
						||
 | 
						||
def fetch_btc_plus_stats(stage_no=1):
 | 
						||
    """从API获取BTC Plus统计数据"""
 | 
						||
    url = "https://graphql.sft-api.com/graphql"
 | 
						||
    headers = {
 | 
						||
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
 | 
						||
        "Accept": "*/*",
 | 
						||
        "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
 | 
						||
        "Accept-Encoding": "gzip, deflate, br, zstd",
 | 
						||
        "Referer": "https://app.solv.finance/",
 | 
						||
        "content-type": "application/json",
 | 
						||
        "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined",
 | 
						||
        "x-amz-user-agent": "aws-amplify/3.0.7",
 | 
						||
        "Origin": "https://app.solv.finance",
 | 
						||
        "DNT": "1",
 | 
						||
        "Connection": "keep-alive",
 | 
						||
        "Sec-Fetch-Dest": "empty",
 | 
						||
        "Sec-Fetch-Mode": "cors",
 | 
						||
        "Sec-Fetch-Site": "cross-site",
 | 
						||
        "Sec-GPC": "1",
 | 
						||
        "Priority": "u=4",
 | 
						||
    }
 | 
						||
 | 
						||
    query = """
 | 
						||
    query BtcPlusStats($stageNo: Int) {
 | 
						||
      btcPlusStats(stageNo: $stageNo) {
 | 
						||
        baseApy
 | 
						||
        rewardApy
 | 
						||
        tvl
 | 
						||
        cap
 | 
						||
        lastUpdatedTime
 | 
						||
        startDate
 | 
						||
        endDate
 | 
						||
        currentTotalRewardPower
 | 
						||
        totalRewards
 | 
						||
        __typename
 | 
						||
      }
 | 
						||
    }
 | 
						||
    """
 | 
						||
 | 
						||
    variables = {"stageNo": stage_no}
 | 
						||
    payload = {
 | 
						||
        "operationName": "BtcPlusStats",
 | 
						||
        "query": query,
 | 
						||
        "variables": variables,
 | 
						||
    }
 | 
						||
 | 
						||
    response = requests.post(url, headers=headers, json=payload)
 | 
						||
    response.raise_for_status()
 | 
						||
    data = response.json()
 | 
						||
    data["timestamp"] = get_current_time()  # 添加当前时间
 | 
						||
    return data
 | 
						||
 | 
						||
 | 
						||
def fetch_phase2_points(address):
 | 
						||
    """从API获取Phase2积分系统数据"""
 | 
						||
    url = "https://graphql.sft-api.com/graphql"
 | 
						||
    headers = {
 | 
						||
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0",
 | 
						||
        "Accept": "*/*",
 | 
						||
        "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
 | 
						||
        "Accept-Encoding": "gzip, deflate, br, zstd",
 | 
						||
        "Referer": "https://app.solv.finance/",
 | 
						||
        "content-type": "application/json",
 | 
						||
        "authorization": "dW5kZWZpbmVkfHx1bmRlZmluZWR8fHVuZGVmaW5lZHx8.undefined",
 | 
						||
        "x-amz-user-agent": "aws-amplify/3.0.7",
 | 
						||
        "Origin": "https://app.solv.finance",
 | 
						||
        "DNT": "1",
 | 
						||
        "Connection": "keep-alive",
 | 
						||
        "Sec-Fetch-Dest": "empty",
 | 
						||
        "Sec-Fetch-Mode": "cors",
 | 
						||
        "Sec-Fetch-Site": "cross-site",
 | 
						||
        "Sec-GPC": "1",
 | 
						||
        "Priority": "u=4",
 | 
						||
    }
 | 
						||
 | 
						||
    query = """
 | 
						||
    query Phase2PointSysAccountInfo($address: String) {
 | 
						||
      phase2PointSysAccountInfo(address: $address) {
 | 
						||
        address
 | 
						||
        isRegistered
 | 
						||
        seedUserInviteCode
 | 
						||
        inviteCode
 | 
						||
        inviteCount
 | 
						||
        totalPointsEarned
 | 
						||
        availablePoints
 | 
						||
        isPointsAccelerationActive
 | 
						||
        todayHoldingTVL
 | 
						||
        todayHoldingAccelerationRatio
 | 
						||
        nextLevelHoldingTVL
 | 
						||
        nextLevelHoldingAccelerationRatio
 | 
						||
        activityCards {
 | 
						||
          type
 | 
						||
          accelerationRatio
 | 
						||
          startTime
 | 
						||
          endTime
 | 
						||
          __typename
 | 
						||
        }
 | 
						||
        isHighestLevel
 | 
						||
        __typename
 | 
						||
      }
 | 
						||
    }
 | 
						||
    """
 | 
						||
 | 
						||
    variables = {"address": address}
 | 
						||
    payload = {
 | 
						||
        "operationName": "Phase2PointSysAccountInfo",
 | 
						||
        "query": query,
 | 
						||
        "variables": variables,
 | 
						||
    }
 | 
						||
 | 
						||
    response = requests.post(url, headers=headers, json=payload)
 | 
						||
    response.raise_for_status()
 | 
						||
    data = response.json()
 | 
						||
    data["timestamp"] = get_current_time()
 | 
						||
    return data
 | 
						||
 | 
						||
 | 
						||
def save_to_csv(data, filename="btc_plus_rewards.csv"):
 | 
						||
    """将数据保存到CSV文件"""
 | 
						||
    logger = logging.getLogger(__name__)
 | 
						||
    timestamp = data.get("timestamp", get_current_time())
 | 
						||
 | 
						||
    if "btcPlusRewardByAddress" in data.get("data", {}):
 | 
						||
        reward_data = data["data"]["btcPlusRewardByAddress"]
 | 
						||
        row = {
 | 
						||
            "timestamp": timestamp,
 | 
						||
            "address": data.get("variables", {}).get("address", "unknown"),
 | 
						||
            "stageNo": data.get("variables", {}).get("stageNo", "unknown"),
 | 
						||
            "balance": reward_data["balance"],
 | 
						||
            "balanceUSD": reward_data["balanceUSD"],
 | 
						||
            "rewardPower": reward_data["rewardPower"],
 | 
						||
            "currentTotalRewardPower": reward_data["currentTotalRewardPower"],
 | 
						||
            "estimatedReward": reward_data["estimatedReward"],
 | 
						||
        }
 | 
						||
        filename = "btc_plus_rewards.csv"
 | 
						||
 | 
						||
    elif "btcPlusAllocations" in data.get("data", {}):
 | 
						||
        allocation_data = data["data"]["btcPlusAllocations"]
 | 
						||
        row = {
 | 
						||
            "timestamp": timestamp,
 | 
						||
            "tvl": allocation_data["tvl"],
 | 
						||
            "allocations": ";".join(
 | 
						||
                f"{a['assetName']}:{a['percentage']}"
 | 
						||
                for a in allocation_data["allocations"]
 | 
						||
            ),
 | 
						||
        }
 | 
						||
        filename = "btc_plus_allocations.csv"
 | 
						||
 | 
						||
    elif "phase2PointSysAccountInfo" in data.get("data", {}):
 | 
						||
        points_data = data["data"]["phase2PointSysAccountInfo"]
 | 
						||
        row = {
 | 
						||
            "timestamp": timestamp,
 | 
						||
            "address": points_data["address"],
 | 
						||
            "isRegistered": points_data["isRegistered"],
 | 
						||
            "inviteCode": points_data["inviteCode"],
 | 
						||
            "inviteCount": points_data["inviteCount"],
 | 
						||
            "totalPointsEarned": points_data["totalPointsEarned"],
 | 
						||
            "availablePoints": points_data["availablePoints"],
 | 
						||
            "isPointsAccelerationActive": points_data["isPointsAccelerationActive"],
 | 
						||
            "todayHoldingTVL": points_data["todayHoldingTVL"],
 | 
						||
            "todayHoldingAccelerationRatio": points_data["todayHoldingAccelerationRatio"],
 | 
						||
            "nextLevelHoldingTVL": points_data["nextLevelHoldingTVL"],
 | 
						||
            "nextLevelHoldingAccelerationRatio": points_data["nextLevelHoldingAccelerationRatio"],
 | 
						||
            "isHighestLevel": points_data["isHighestLevel"],
 | 
						||
        }
 | 
						||
        filename = "phase2_points.csv"
 | 
						||
 | 
						||
    else:
 | 
						||
        stats_data = data["data"]["btcPlusStats"]
 | 
						||
        row = {
 | 
						||
            "timestamp": timestamp,
 | 
						||
            "stageNo": data.get("variables", {}).get("stageNo", "unknown"),
 | 
						||
            "baseApy": stats_data["baseApy"],
 | 
						||
            "rewardApy": stats_data["rewardApy"],
 | 
						||
            "tvl": stats_data["tvl"],
 | 
						||
            "cap": stats_data["cap"],
 | 
						||
            "lastUpdatedTime": timestamp_to_datetime(stats_data["lastUpdatedTime"]),
 | 
						||
            "startDate": timestamp_to_datetime(stats_data["startDate"]),
 | 
						||
            "endDate": timestamp_to_datetime(stats_data["endDate"]),
 | 
						||
            "currentTotalRewardPower": stats_data["currentTotalRewardPower"],
 | 
						||
            "totalRewards": stats_data["totalRewards"],
 | 
						||
        }
 | 
						||
        filename = "btc_plus_stats.csv"
 | 
						||
 | 
						||
    # 写入CSV文件
 | 
						||
    try:
 | 
						||
        with open(filename, "a", newline="") as csvfile:
 | 
						||
            writer = csv.DictWriter(csvfile, fieldnames=row.keys())
 | 
						||
 | 
						||
            # 如果是新文件,写入表头
 | 
						||
            if csvfile.tell() == 0:
 | 
						||
                writer.writeheader()
 | 
						||
 | 
						||
            writer.writerow(row)
 | 
						||
    except Exception as e:
 | 
						||
        logger.error("保存到CSV时出错: %s", e)
 | 
						||
 | 
						||
 | 
						||
# 初始化日志
 | 
						||
def setup_logging():
 | 
						||
    """配置日志记录,只记录错误信息"""
 | 
						||
    logging.basicConfig(
 | 
						||
        level=logging.WARNING,  # 只记录WARNING及以上级别
 | 
						||
        format="%(asctime)s - %(levelname)s - %(message)s",
 | 
						||
        handlers=[
 | 
						||
            logging.FileHandler(LOG_FILE, encoding="utf-8"),
 | 
						||
        ],
 | 
						||
    )
 | 
						||
    return logging.getLogger(__name__)
 | 
						||
 | 
						||
 | 
						||
def git_commit_and_push():
 | 
						||
    """自动提交并推送数据更新到Git仓库"""
 | 
						||
    logger = logging.getLogger(__name__)
 | 
						||
    try:
 | 
						||
        subprocess.run(["git", "add", "."], check=True)
 | 
						||
        subprocess.run(
 | 
						||
            ["git", "commit", "-m", str(datetime.now())],
 | 
						||
            check=True,
 | 
						||
        )
 | 
						||
        subprocess.run(["git", "push"], check=True)
 | 
						||
        logger.info("Git提交成功")
 | 
						||
    except subprocess.CalledProcessError as e:
 | 
						||
        logger.error("Git操作失败: %s", e)
 | 
						||
 | 
						||
 | 
						||
def main_loop():
 | 
						||
    """主循环,定期获取数据并更新"""
 | 
						||
    logger = setup_logging()
 | 
						||
    logger.info("BTC Plus数据收集程序启动")
 | 
						||
    last_git_time = last_points_time = last_stats_time = last_rewards_time = last_allocations_time = 0
 | 
						||
 | 
						||
    while True:
 | 
						||
        try:
 | 
						||
            current_time = time.time()
 | 
						||
            # 获取并保存所有数据
 | 
						||
            address = "0x1bFD4C5bBD6d3eabDeec1Ece32Fb2dFC8f28C6b0"
 | 
						||
            stage_no = 1
 | 
						||
 | 
						||
            if current_time - last_rewards_time >= REWARDS_INTERVAL:
 | 
						||
                reward_data = fetch_btc_plus_reward(address, stage_no)
 | 
						||
                reward_data["variables"] = {"address": address, "stageNo": stage_no}
 | 
						||
                save_to_csv(reward_data)
 | 
						||
                last_rewards_time = current_time
 | 
						||
 | 
						||
            if current_time - last_allocations_time >= ALLOCATIONS_INTERVAL:
 | 
						||
                allocation_data = fetch_btc_plus_allocations()
 | 
						||
                save_to_csv(allocation_data)
 | 
						||
                last_allocations_time = current_time
 | 
						||
 | 
						||
            if current_time - last_stats_time >= STATS_INTERVAL:
 | 
						||
                stats_data = fetch_btc_plus_stats(stage_no)
 | 
						||
                stats_data["variables"] = {"stageNo": stage_no}
 | 
						||
                save_to_csv(stats_data)
 | 
						||
                last_stats_time = current_time
 | 
						||
 | 
						||
            if current_time - last_points_time >= POINTS_INTERVAL:
 | 
						||
                points_data = fetch_phase2_points(address)
 | 
						||
                save_to_csv(points_data)
 | 
						||
                last_points_time = current_time
 | 
						||
 | 
						||
            # 定时Git提交
 | 
						||
            if current_time - last_git_time >= GIT_INTERVAL:
 | 
						||
                git_commit_and_push()
 | 
						||
                last_git_time = current_time
 | 
						||
 | 
						||
            time.sleep(FETCH_INTERVAL)
 | 
						||
        except Exception as e:
 | 
						||
            logger.error("发生错误: %s,1秒后重试...", e)
 | 
						||
            time.sleep(1)
 | 
						||
 | 
						||
 | 
						||
if __name__ == "__main__":
 | 
						||
    main_loop()
 |